为了账号安全,请及时绑定邮箱和手机立即绑定

目录

手机
阅读

扫一扫 手机阅读

开篇:什么是线程

原价 ¥ 39.90

立即订阅
开篇:什么是线程
作者:追梦 字数:5953 更新时间:2019-01-30 15:12:12

如果说编写正确的程序很难,那么编写正确的并发程序更是难上加难,相比Java技术栈中的其他知识点,Java 多线程并发编程的学习门槛较高,导致很多人望而却步。但无论是职场面试,还是高并发/高流量系统的实现,都离不开并发编程,于是能够真正掌握并发编程的人成为了市场迫切需求的人才。
 
线程是进程中的一个执行路径,线程本身是不会独立存在的,而必须存在于某一个进程中,那么什么是进程呢?你打开的微信 App 就是一个进程、打开的手淘 App 是一个进程,打开的记事本也是一个进程。学过操作系统的朋友应该都知道,进程是代码+数据集合,是代码在数据集合上的运行活动。在操作系统中,进程是资源分配和调度的基本单位,也就是说操作系统是以进程为单位进行资源分配的,但是 CPU 这个资源却比较特殊,CPU 的分配是以线程为单位的,这是因为具体占用 CPU 运行的是进程中的线程。
 
在 Java 的世界中,当你在 IDE 里面启动一个 main 函数时候,其实就是开启了一个 JVM 进程,在 Mac 下你可以使用 ps -eaf |grep java 查看进程的存在,在这个进程中其实是存在好多线程,大家可以使用 jstack pid 查看,例如下面是一个查看结果:

图片描述

 
上图红框部分是线程堆栈里面 main 函数所在的线程,这个线程目前阻塞到 Thread.sleep 这个函数。
图片描述

 
上图红色框里的是一个 gc 线程,这也说明了一个进程中是存在多个线程的,每个线程只是进程的一个执行单元。
 
下面我们使用一张图来解释进程与线程的关系:

图片描述

如上图所示,Thread1 到 ThreadN 这 N 个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器(PC)和栈(stack)区域。

  • 其中 PC 计数器本质上是一块内存区域,用来记录线程当前要执行的指令地址,CPU 一般是使用时间片轮转方式让线程轮询占用的,因此当前线程 CPU 时间片用完后,要让出 CPU,这时 PC 计数器就会记录下当前线程下次要执行的命令的地址,等下次轮到该线程占有 CPU 执行时,就从 PC 计数器获取自己将要执行的命令的地址继续执行。

  • 每个线程有自己的栈资源,用于存储该线程的局部变量,这些局部变量是该线程私有的,其它线程是访问不了的,比如当一个线程调用下面的 test 方法的时候,在 test 方法体内创建的局部变量 ab 就是在该线程的栈中分配的:

    void test() {
        int a = 5;
        int b = 6;
    }
    

    另外,栈还用来存放线程的调用栈帧,那么什么是栈帧呢?如下代码,当我们调用 test() 方法时,就会把当前方法的一些信息封装为栈帧压入到栈顶,栈顶的栈帧就是活跃的 test 方法。当执行到 say() 方法时就会在栈顶新加一个关于 say() 方法的栈帧,这时候 say() 方法所在栈帧就是活跃栈帧。当 say() 方法执行完毕后,say() 方法所在的栈顶帧就会出栈,这时候栈顶活跃帧就是 test 方法的了。
     

    void test(int x,int y) {
        int a = 5;
        int b = 6;
        say();
    }
    
  • 堆(heap)是一个进程中最大的一块内存,是进程创建时候创建的,堆是被进程中的所有线程共享的。堆里面主要存放使用new 操作创建的对象实例,如下操作就是在堆上创建了一个 ArrayList 对象,这里需要注意的是,指向堆对象的 list 变量本身是在线程的栈上保存的,只是 list 指向了堆上的 ArrayList 的地址。

    void test(int x,int y) {
        List<String> list = new ArrayList<String>();
    }
    
  • 方法区(method area)用来存放 JVM 加载的类信息、常量、静态变量等信息,也是线程共享的。

在 Java 中创建线程有两种方式:

  • 实现 Runable() 接口
public class TestRxJava {

    private static final String THREAD_NUM = "sub-thread";

    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {

            public void run() {

                System.out.println("--" + Thread.currentThread().getName() + "--");
                try {
                    Thread.sleep(200000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, THREAD_NUM);

        thread.start();

        System.out.println("--" + Thread.currentThread().getName() + "--");

        try {
            Thread.sleep(200000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

 
以上代码里创建了一个线程 thread,并且指定了该线程的名字为 sub-thread(注:创建线程时候给每个线程指定业务相关的名称,有利于问题排查),并调用 thread 的 start 方法启动了该线程,线程内部首先打印一行,然后调用 sleep 方法挂起自己。运行该代码就会创建一个 JVM 进程,该进程里面包含 main 函数所在的线程和我们自己创建的 thread 线程,当然不止这两个线程,运行后会输出:
–sub-thread–
–main–
 
这时候你使用 jstack pid 查看线程堆栈会发现:
 
图片描述

 
由以上可知,main 函数所在线程阻塞了 sleep 方法。

图片描述

 
由以上可知,我们创建的 sub-thread 线程也阻塞到了 sleep 方法。

  • 继承 Thread 类并重写 run 方法
public class Test {

    private static final String THREAD_NUM = "sub-thread";

    static class MyThread extends Thread {

        public MyThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            System.out.println("--" + Thread.currentThread().getName() + "--");
            try {
                Thread.sleep(200000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        MyThread thread = new MyThread(THREAD_NUM);

        thread.start();

        System.out.println("--" + Thread.currentThread().getName() + "--");

        try {
            Thread.sleep(200000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

在 Java 中线程被分为了两大类:daemon 线程和 User 线程。默认我们创建的线程都是 User 线程,在 Java 中当进程中不存在任何 User 线程时候 jvm 就会退出:

public class TestRxJava {

    private static final String THREAD_NUM = "sub-thread";

    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {

            public void run() {

                System.out.println("--" + Thread.currentThread().getName() + "--");
                try {
                    Thread.sleep(200000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }, THREAD_NUM);

        thread.start();

        System.out.println("--" + Thread.currentThread().getName() + "--");

    }

}

 
如上代码中 sub-thread 线程和 main 函数所在线程就是 User 线程,运行上面代码,你会发现 jvm 进程会一直存在,直到 sub-thread 线程执行完毕。并且执行 jstack pid 查看线程堆栈,会发现 main 函数所在线程已经不存在了,但是 sub-thread 线程还是存在的,这说明 main 函数退出后,jvm 函数并没退出。

Java 中创建 daemon 线程:
 

public class TestRxJava {

    private static final String THREAD_NUM = "sub-thread";

    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {

            public void run() {

                System.out.println("--" + Thread.currentThread().getName() + "--");
                try {
                    Thread.sleep(200000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }, THREAD_NUM);

        thread.setDaemon(true);
        thread.start();

        System.out.println("--" + Thread.currentThread().getName() + "--");

    }

}

 
创建 daemon 线程只需要设置 thread.setDaemon(true),这时候你再运行上面的代码,会发现 jvm 进程直接退出了,这是因为这里 main 函数所在线程是唯一的 User 线程,当 main 函数运行完毕后,当前进程就不存在 User 线程,所以 jvm 进程就退出了。

}

此小节为试读小节,订阅后立即解锁全部内容

立即订阅 ¥ 39.90
深度剖析 Java 多线程核心技术
立即订阅 ¥ 39.90

举报

0/150
提交
取消
意见反馈 去赚学费 帮助中心 APP下载
官方微信