精华内容
下载资源
问答
  • Java线程的本质

    2020-10-27 14:16:07
    文章目录序言模拟一个线程Java线程的本质调试JDK源码配置jdk8环境编译openjdk9linux安装clion开始调试java的start方法其他资料vmware设置固定ipcentos安装图形界面xming+xshell打开命令行 序言 模拟一个线程 我们...

    序言

    模拟一个线程

    我们知道,java为我们提供了Thread线程类操作线程,那么我们如何不使用Thread实现线程呢?我们可以手动去调用操作系统的线程方法,操作流程图如下:

    Created with Raphaël 2.2.0ThreadTest.java#start1()CThreadTest.c#start1()操作系统的pthread_create()
    • com.cbl.thread.ThreadTest.java
    public class ThreadTest {
    
        // java使用jni调用native方法,lib名为libCThreadTestNative.so去掉lib前缀,lib由CThreadTest.c在以下步骤生成
        static {
            System.loadLibrary("CThreadTestNative");
        }
    
        public native void start1();
    
        public static void main(String[] args) {
            ThreadTest threadTest = new ThreadTest();
            threadTest.start1();
        }
    }
    
    • 编译ThreadTest.java得到com_cbl_thread_ThreadTest.h
    javac -h . ThreadTest.java
    
    • com.cbl.thread.CThreadTest.c
    // 该类的核心是pthread_create方法,该方法传递一个回调方法,可以把这个回调方法看作是线程的执行逻辑,类似run方法
    #include <pthread.h>
    #include <stdio.h>
    #include "com_cbl_thread_ThreadTest.h" // 该文件由ThreadTest.java编译过来
    
    pthread_t pid;
    
    // 该方法为回调方法,类似于java启动线程后去执行run方法。
    void* thread_entity(void* args) {
        while (1) {
            usleep(100);
            printf("开启线程");
        }
    }
    
    // 查看com_cbl_thread_ThreadTest.h并把JNIEXPORT方法体复制到这里
    JNIEXPORT void JNICALL Java_com_cbl_thread_ThreadTest_start1(JNIEnv *env, jobject c1) {
        pthread_create(&pid, NULL, thread_entity, NULL);
        while (1) {
            usleep(1000);
            printf("main\n");
        }
    }
    
    int main() {
        return 0;
    }
    
    • 编译CThreadTest.c得到.so文件
    gcc -fPIC -I /home/cbl/develop/jdk/jdk1.8.0_251/include -I /home/cbl/develop/jdk/jdk1.8.0_251/include/linux -shared -o libCThreadTestNative.so CThreadTest.c
    
    • 把libCThreadTestNative.so文件放到linux库
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/cbl/proj/threadTest/com/cbl/thread/libCThreadTestNative.so
    
    • 执行ThreadTest.class,最终结果是主线程和子线程交替打印。
    java -classpath /home/cbl/proj/threadTest com.cbl.thread.ThreadTest
    

    Java线程的本质

    当我们不用Thread类去启动线程时,我们应该都可以猜到,Thread.start()方法其实调用了操作系统的p_thread_create()方法,run方法又作为p_thread_create()的回调方法,所以能够被执行到。

    查看start方法源码,是一个本地方法,是不是跟我们上面的start1()很像?

    private native void start0();
    

    调试JDK源码

    这里我们尝试去编译openjdk9,需要jdk8环境的支持,即官网所说的boot jdk,openjdk9的源码可以在gitee下载。编译成功后可以看到build/linux-x86_64-normal-server-release目录。

    配置jdk8环境

    • sudo vim /etc/profile
    JAVA_HOME=/home/cbl/develop/jdk/jdk1.8.0_251
    PATH=$JAVA_HOME/bin:$PATH
    CLASSPATH=$JAVA_HOME/jre/lib/ext:$JAVA_HOME/lib/tools.jar
    export PATH JAVA_HOME CLASSPATH
    
    source /etc/profile
    

    编译openjdk9

    需要上一个版本的环境,官网说的Boot JDK即jdk1.8。gitee下载openjdk9的源码,解压进入openjdk9目录,执行。

    为什么不用openjdk11+呢,因为执行make images时踩过坑,找不到jre/lib/ext目录

    bash configure
    

    按照提示安装缺失的依赖

    sudo yum -y groupinstall "Development Tools"
    sudo yum  -y install libXtst-devel libXt-devel libXrender-devel libXrandr-devel libXi-devel
    sudo yum  -y install cups-devel
    sudo yum  -y install fontconfig-devel
    sudo yum  -y install alsa-lib-devel
    sudo yum  -y install elfutils-libelf-devel
    
    make images
    

    遇到了class JdiDefaultExecutionControl is public, should be declared in a file named JdiDefaultExecutionControl.java错误,只需要把java源文件名的JDI改成Jdi即可,算是jdk9的bug?

    编译完成后,使用以下命令验证

    ./build/*/images/jdk/bin/java -version
    

    测试(好像报错了)

    make run-test-tier1
    

    linux安装clion

    官网下载clion的linux包,解压

    • 安装cmake
    wget "https://cmake.org/files/v3.19/cmake-3.19.0-rc1.tar.gz"
    cd cmake-3.10.0-rc3/
    ./bootstrap
    gmake
    gmake install
    
    • 启动(需要配置xming),然后系统自动打开xming,即可看到图形界面了
    ./home/cbl/develop/clion/bin/clion.sh
    

    使用Clion打开jdk9目录,设置run configuration

    –> cmake application

    –> executable:build/linux-x86_64-normal-server-release/jdk/bin/java

    –> program arguments:-version

    开始调试java的start方法

    创建一个简单的thread类,javac xxx.java编译,然后放到clion的jdk12目录下jdk12/build/linux-x86_64-server-slowdebug/jdk/bin替换com文件夹,修改run configuration,program arguments改为com.xxx.xxx.Xxx

    找到os_linux.cpp#pthread_attr_setguardsize方法,打断点

    jvm.cpp#JVM_ENTRY

    ​ JVM_StartThread

    Created with Raphaël 2.2.0java start()Thread.c start0()JVM实例化一个C++对象JavaThread操作系统的pthread_create()

    其他资料

    vmware设置固定ip

    • 设置NAT网络

    • 修改虚拟网络编辑器:

      编辑->虚拟网络编辑器->VMnet8->子网IP:192.168.118.0 子网掩码->255.255.255.0
      NAT设置->网关IP:192.168.118.2

    • 修改ifcfg-ens33

    sudo vim /etc/sysconfig/network-scripts/ifcfg-ens33
    
    BOOTPROTO=static
    IPADDR=192.168.118.10
    NETMASK=255.255.255.0
    GATEWAY=192.168.118.2
    DNS1=114.114.114.114
    ONBOOT=yes
    
    • 重启网络
    service network restart
    

    centos安装图形界面

    yum groupinstall "GNOME Desktop" "Graphical Administration Tools"
    # 下面几个命令其实我不怎么用,都是使用xming运行图形界面的
    systemctl set-default multi-user.target
    systemctl set-default graphical.target
    startx
    

    xming+xshell打开命令行

    • 安装xming,启动xlaunch,一路next
    • 打开xshell会话->属性->SSH->隧道->X DISPLAY,改成localhost:0.0
    • 修改xming目录下的X0.hosts,加上centos的ip
    • 进入xshell,执行
    export DISPLAY=192.168.118.10:0.0
    
    展开全文
  • Java是如何启动线程的呢?当Java调用了Thread.start()方法做了些什么?Java中的线程和操作系统中的线程是什么关系呢?Linux 开启线程首先,我们先看一下Linux是如何开启一个线程的,这里涉及的知识是很复杂的,我只...

    Java是如何启动线程的呢?当Java调用了Thread.start()方法做了些什么?Java中的线程和操作系统中的线程是什么关系呢?

    Linux 开启线程

    首先,我们先看一下Linux是如何开启一个线程的,这里涉及的知识是很复杂的,我只说一下大概的流程。

    如下面的代码,是Linux的底层的源码,主要通过pthread_create() 方法是glibc库提供的,该方法的作用就是去创建一个线程。

    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

    void *(*start_routine) (void *), void *arg);

    复制代码

    如果你有linxu 系统可以输入:man 3 pthread_create 来查询这个方法的作用,如下图所示:描述 create a new thread 创建一个线程

    a31bb64a5d02a0aa45470c8d8f1b3711.png

    从Linux的帮助文档中,可以看出pthread_create会创建一个线程,这是Linux系统的函数,可以用C或者C++直接调用,下面看一下这个方法中的四个参数的意义

    参数名字参数定义参数解释pthread_t  *thread传出参数,调用之后会传出被创建线程的id定义 pthread_t pid 继而取地址 &pid

    const pthread_attr_t  *attr线程属性,关于线程属性是Linux的知识一般传null,保持默认属性

    void (start_routine) (void *)线程的启动后的主体函数需要你定义一个函数,然后传函数名

    void *arg主体函数的参数没有可以传null

    通过上面的解释,可以得出Linux启动线程的一个测试代码

    //头文件

    #include

    #include

    //定义一个变量,接受创建线程后的线程id pthread_t pid;

    //定义线程的主体函数

    void* thread_entity(void* arg){

    printf("i am new Thread! from c");

    }

    //main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程

    int main(){

    //调用操作系统的函数创建线程,注意四个参数

    pthread_create(&pid,NULL,thread_entity,NULL);

    //usleep是睡眠的意思,那么这里的睡眠是让谁睡眠呢?

    //为什么需要睡眠?如果不睡眠会出现什么情况

    usleep(100);

    printf("main\n");

    return 0;

    }

    复制代码

    Java 启动线程

    java启动线程的代码如下:

    Thread thread = new Thread(new Runnable() {

    @Override

    public void run(){

    System.out.println("java thread");

    }

    });

    //调用start方法 --> run方法

    //private native void start0();

    thread.start();

    复制代码

    进入Thread的源码,可以找到真正开启线程的是:private native void start0(); 方法,start0方法会调用JVM的底层方法也就是Hotspot的源码,而JVM的源码对调用系统OS层的xxx.c文件中的pthread_create 方法创建线程。

    为了进一步证明,我们可以模拟一下Java代码通过JNI直接调用.c代码,然后.c实现调用Linux函数pthread_create 创建一个线程。

    #include

    #include //定义变量接受线程id

    pthread_t pid;

    //线程的主体方法相当于 java当中的run 这里我们不去探究JVM是如何调用Java中的run方法的,因为涉及的都是C/C++层面的 我们只需要知道,Linux创建的线程会执行到Java中的run方法就可以了

    void* thread_entity(void* arg){

    //子线程死循环

    while(1){

    //睡眠100毫秒

    usleep(100);

    //打印

    printf("I am new Thread\n");

    }

    }

    //c语言的主方法入口方法,相当于java的main

    int main(){

    //调用linux的系统的函数创建一个线程

    pthread_create(&pid,NULL,thread_entity,NULL); //主线程死循环

    while(1){

    //睡眠100毫秒

    usleep(100);

    //打印

    printf("I am main\n");

    }

    return 0;

    }

    复制代码

    下面我们来测试一下,上述的c程序,是否可以在Linux中运行,执行命令:

    gcc thread.c - thread.out -pthread

    运行程序:./thread.out

    f4e1a95cc0b5c7081d0bd1aacb32fff1.png

    模拟Java的线程,调用start1本地方法,在本地方法里面去启动一个系统线程。

    public class ZLThread{

    static {

    System.loadLibrary("EnjoyThreadNative");

    }

    public native void start1();

    public static void main(String[] args){

    ZLThread zlThread = new ZLThread();

    zlThread.start1();

    }

    }

    复制代码

    在Linux系统上面,利用javac ZLThread.java命令编译这个Java类,利用java -h xx.java命令 来编译一个头文件,然后找到头文件中的方法,复制给上述的.c文件,我们需要Java来调用c程序,就需要将方法设置正确,并且编译为so文件

    #include

    #include //定义变量接受线程id

    pthread_t pid; //线程的主体方法相当于 java当中的run

    void* thread_entity(void* arg){

    //子线程死循环

    while(1){

    //睡眠100毫秒

    usleep(100);

    //打印

    printf("I am new Thread\n");

    }

    }

    //这个方法名字需要参考.h当中的方法名字,打开.h文件,复制方法名过来

    //参数固定

    Java_com_shadow_app_EnjoyThread_start0(JNIEnv *env, jobject c1) {

    //调用linux的系统的函数创建一个线程

    pthread_create(&pid,NULL,thread_entity,NULL); //主线程死循环

    while(1){

    //睡眠100毫秒

    usleep(100);

    //打印

    printf("I am main\n");

    }

    }

    复制代码

    编译so文件的命令:

    gcc -fPIC -I /home/shadows/soft/jdk11/include -I/home/shadows/soft/jdk11/include/linux -shared -o libxxx.so threadNew.c

    注意生成的so文件名称要和Java程序中的相对应

    static { //如果你是libxxx;这里就写xxx

    System.loadLibrary( "xxx" );

    }

    复制代码

    然后把so文件目录添加到系统变量,否则Java是找不到so文件的

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/shadows/tools/enjoy/com/shadow/app/

    然后通过Java命令去运行Java文件,可以看到Java创建了线程。

    这也就佐证了,Java调用Thread.start()方法其实就是jvm调用OS层的底层方法去创建线程。这也是为什么调用了start 方法,Java的线程状态会进入就绪状态,不会立即启动,因为通过OS,CPU 创建线程,创建线程完毕之后通过JNI调用Java的run方法。

    OK,至此也就证明了文章开头的三个问题:

    Java是通过JVM调用OS系统创建线程

    Thread.start()经历了:Thread.start() --> navtive start0() --> JVM ---> Thread.c : start0() --> JVM 实例化一个C++对象 JavaThread --> OS pthread_create() 创建线程 --> 线程创建完毕 --> JVM --> Thread.run()方法

    Java级别中的线程其实就是操作系统级别的线程

    下面通过一个流程图总结:

    5ce26b574e6f928dfba41905c5ea90e7.png

    补充说明(只做补充)

    man命令 是Linux下的帮助指令,通过man指令可以查看Linux中的指令帮助、配置文件帮助和编程帮助等信息。

    语法

    man(选项)(参数)

    复制代码

    选项

    -a:在所有的man帮助手册中搜索;

    -f:等价于whatis指令,显示给定关键字的简短描述信息;

    -P:指定内容时使用分页程序;

    -M:指定man手册搜索的路径。

    复制代码

    参数

    数字:指定从哪本man手册中搜索帮助;

    关键字:指定要搜索帮助的关键字。

    数字代表内容

    1:用户在shell环境可操作的命令或执行文件;

    2:系统内核可调用的函数与工具等

    3:一些常用的函数(function)与函数库(library),大部分为C的函数库(libc)

    4:设备文件说明,通常在/dev下的文件

    5:配置文件或某些文件格式

    6:游戏(games)

    7:惯例与协议等,如Linux文件系统,网络协议,ASCII code等说明

    8:系统管理员可用的管理命令

    9:跟kernel有关的文件

    复制代码

    实例

    我们输入man ls,它会在最左上角显示“LS(1)”,在这里,“LS”表示手册名称,而“(1)”表示该手册位于第一节章,同样,我们输man ifconfig它会在最左上角显示“IFCONFIG(8)”。也可以这样输入命令:“man [章节号] 手册名称”。

    man是按照手册的章节号的顺序进行搜索的,比如:

    man sleep

    复制代码

    只会显示sleep命令的手册,如果想查看库函数sleep,就要输入:

    man 3 sleep

    复制代码

    展开全文
  • 线程1.... linux的线程控制原语2.1 centos安装man手册2.2 查看pthread_create...是通过JNI调用的系统方法,这个start0方法对应的系统创建线程的方法是pthread_create。 2. linux的线程控制原语 2.1 centos安装man手册

    1. 创建一个线程类

    • 可以正常运行
      在这里插入图片描述

    1.1 点击start方法

    • start方法内有一个start0方法,而且start0方法是一个native本地方法。是通过JNI调用的系统方法,这个start0方法对应的系统创建线程的方法是pthread_create。
      在这里插入图片描述

    2. linux的线程控制原语

    2.1 centos安装man手册

    yum install -y man-pages
    

    在这里插入图片描述

    2.2 查看pthread_create函数定义

    man pthread_create
    

    在这里插入图片描述

    • 根据man配置的信息可以得出pthread_create会创建一个线程,这个函数是linux系统的函数,可以用C或者C++直接调用,这个函数在pthread.h, 这个函数有四个参数
    1. pthread_t *thread:指向线程id的指针(传出参数,调用之后会传出被创建线程的id)
    2. const pthread_attr_t*attr:线程属性(一般传NULL,保持默认属性)
    3. void *(*start_routine)(void *):线程的启动后的主体函数(需要你定义一个函数,然后传函数名即可)
    4. void *arg:主体函数的参数(没有可以传nulll)
    • start_routine类似于Thread的run方法,具体的方法实现,都在run方法里。

    3. 线程本质

    • Java中的线程与操作系统有什么关系?
      Java中的线程是通过JNI调用操作系统pthread_create函数创建的线程,Java的线程模型与操作系统一一对应。

    4. 线程模型

    • java的线程(Thread)应该叫做用户线程,对应到操作系统,还有另外一种线程叫作内核线程
    • 用户线程和内核线程之间存在3种对应关系
    1. 一对一模型(HotSpot)
      即一个用户线程对应一个内核线程,内核负责每个线程的调度
      优点:
      (比如JVM几乎把所有对线程的操作都交给了内核)实现线程模型的容器(jvm)简单,所以我们经常听到在java中使用线程一定要慎重就是这个原因;
      缺点:
      对用户线程的大部分操作都会映射到内核线程上,引起用户态和内核态的频繁切换;
      内核为每个线程都映射调度实体,如果系统出现大量线程,会对系统性能有影响;
    2. 多对一线程模型
      多个用户线程对应到同一个内核线程上,线程的创建、调度、同步的所有细节全部由进程的用户空间线程库来处理。
      优点:
      用户线程的很多操作对内核来说都是透明的,不需要用户态和内核态的频繁切换,使线程的创建、调度、同步等非常快;
      缺点:
      由于多个用户线程对应到同一个内核线程,如果其中一个用户线程阻塞,那么该其他用户线程也无法执行;
      内核并不知道用户态有哪些线程,无法像内核线程一样实现较完整的调度、优先级等;
    3. 多对多模型
      结合了多对一模型和一对一模型的特点,将多个用户线程映射到少数但不止一个内核线程上。

    5. 线程的状态

    • 线程是用户态还是内核态
      需要看cpu的状态,ring0(内核态),ring1,ring2,ring3(用户态)

    6. 上下文切换

    1. 进程内部发生系统调用(只保存进程的私有信息),进程从用户态切换到内核态;
    2. 进程与进程之间的切换(保存进程信息,用于切换还原后程序继续执行);
    3. 线程与线程之间的切换
      3.1 进程内部线程与线程之间的切换;
      3.2 线程与外部进程里的线程之间的切换。
    展开全文
  • 线程的优化:合理利用CPU,使用多线程并行取代单线程串行,可以显著提升系统性能。多线程线程安全问题,本质上就是资源共享问题。如果多线程之间彼此独立,互不影响,这种情况是不存在线程安全问题的。如果多线程...

    多线程是Java开发中的重中之重,其重要性和难度,可见一斑。掌握并精通多线程开发,是每一个程序员的必修之课。哪怕中间的过程很痛苦,只要坚持了,并最终豁然开朗了,都是一种升华。

    多线程的优化:合理利用CPU,使用多线程并行取代单线程串行,可以显著提升系统性能。

    多线程线程安全问题,本质上就是资源共享问题。如果多线程之间彼此独立,互不影响,这种情况是不存在线程安全问题的。如果多线程之间涉及共享资源,此时处理不当,就存在线程安全问题。举个简单的例子:A、B、C三人各自有一块蛋糕,他们之间不存在竞争关系,不会出现矛盾;A、B、C共享一块蛋糕,则他们之间存在竞争关系,会出现矛盾。

    那么,如何解决多线程线程安全问题呢?两种思路:隔离资源;同步共享资源。

    一、隔离资源:使用ThreadLocal隔离资源,即将共享资源备份到各自的线程单独处理。

    二、同步共享资源:既然是共享资源,就想办法控制,在同一时刻,只有一个线程来操作共享资源。使用的技术有:使用synchronized同步关键字;使用reentrantlock等更加灵活的锁工具;使用CAS无锁思想。

    本篇只是引题,后面的篇幅将会详细讲述具体的实践案例。

    展开全文
  • java当中的线程和操作系统的线程是什么关系?关于操作系统的线程linux操作系统的线程控制原语int pthread create(pthread t *thread, const pthread attr t *attr,void *(*start_routine) (void *), void *arg);可以...
  • JAVA线程本质

    2020-11-03 17:07:28
    java中并发编程的本质是什么,首先说道并发编程就就必须说一下什么是线程 在java中是没有自己的线程,java对于线程的调用都是调用的操作系统本身的线程、并且java中的线程和操作系统的线程都是1v1对应 那么java是...
  • 探究Java多线程中线程的本质

    万次阅读 2020-09-01 15:17:31
    线程的本质 如果想要理解java中的线程,需要学习linux系统中线程的原语,然后自定义MyThread实现Thread的功能更加深入的理解。 linux系统中线程的原语 在linux中,创建一个线程的函数为pthread_create,其定义如下:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,024
精华内容 1,209
关键字:

java线程的本质

java 订阅