JDK源码分析 多线程

作者: 被称为L的男人 | 来源:发表于2017-08-07 08:33 被阅读226次

    说明

    对于JDK源码分析的文章,仅仅记录我认为重要的地方。源码的细节实在太多,不可能面面俱到地写清每个逻辑。所以我的JDK源码分析,着重在JDK的体系架构层面,具体源码可以参考:http://www.cnblogs.com/skywang12345/category/455711.html

    实现多线程的两种方式

    • Thread
    • Runnable

    优先使用 Runnable

    因为 Thread 是类,一个类只能有一个父类,但是可以有多个接口。Runnable 有更好的扩展性。

    Runnable 还可以用于“资源的共享”,如果多个线程是基于同一个 Runnable 对象建立的,它们会共享 Runnable 对象上的资源。

    start() 和 run()的区别说明

    • start: 启动一个新的线程,新线程调用 run 方法。start 不能重复调用
    • run:和普通方法一样,可以重复调用。但是并不会启动新线程

    测试代码

    public class jdk {
        public static class MyThread extends Thread {
            public MyThread(String name) {
                super(name);
            }
            
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " is running.");
            }
        }
        
        public static void main(String[] args) {
            Thread thread = new MyThread("myThread");
            
            System.out.println(Thread.currentThread().getName()+" call mythread.run()");
            thread.run();
            
            System.out.println(Thread.currentThread().getName()+" call mythread.start()");
            thread.start();
        }
    }
    

    输出:

    main call mythread.run()
    main is running
    main call mythread.start()
    mythread is running
    

    分析:

    main中,直接调用thread.run()时,仅仅是调用了MyThread实例的run()方法,并没有新建线程。所以run()方法中的Thread.currentThread().getName(),仍是main()!

    而在调用thread.start()后,就会启动新的MyThread线程,并调用其中的run()方法,此时Thread.currentThread().getName()为新启动线程。

    start 源码

    public synchronized void start() {
        // 如果线程不是"就绪状态",则抛出异常!
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        // 将线程添加到ThreadGroup中
        group.add(this);
        boolean started = false;
        try {
            // 通过start0()启动线程
            start0();
            // 设置started标记
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }
    

    run 源码

    public void run() {
        if (target != null) {
            target.run();
        }
    }
    

    wait(), notify(), notifyAll()

    • wait():让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
    • wait(long timeout):让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
    • notify():唤醒在此对象监视器上等待的单个线程
    • notifyAll():唤醒在此对象监视器上等待的所有线程

    notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?

    答案是:依据“对象的同步锁”。

    多线程循环打印 ABC

    思路:考察多线程间通信。首先需要一个线程共有的变量,来标识打印的状态。在 run 方法中,首先获取一个公共的对象,相当于是把「多线程变成顺序执行」,如果获取锁后,并不是需要打印的状态,就释放锁,进入等待;其它线程会得到锁,然后判断,如果是打印状态,就打印,然后通知所有线程,并释放锁。

    public class PrintABC {
        private static final int PRINT_A = 0;
        private static final int PRINT_B = 1;
        private static final int PRINT_C = 2;
        
        private static class MyThread extends Thread {
            int which; // 0:打印A;1:打印B;2:打印C
            static volatile int state; // 线程共有,判断所有的打印状态
            static final Object t = new Object();
            
            public MyThread(int which) {
                this.which = which;
            }
            
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    synchronized (t) {
                        while (state % 3 != which) {
                            try {
                                t.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.print(toABC(which)); // 执行到这里,表明满足条件,打印
                        state++;
                        t.notifyAll(); // 调用notifyAll方法
                    }
                }
            }
        }
        
        public static void main(String[] args) {
            new MyThread(PRINT_A).start();
            new MyThread(PRINT_B).start();
            new MyThread(PRINT_C).start();
        }
        private static char toABC(int which) {
            return (char) ('A' + which);
        }
    }
    

    yield

    不会释放锁

    sleep

    让当前线程休眠,由“运行状态”→“阻塞状态”。并不会释放任何锁,到时间后线程会进入“就绪状态”。

    测试代码

    // SleepLockTest.java的源码
    public class SleepLockTest{ 
        private static Object obj = new Object();
        public static void main(String[] args){ 
            ThreadA t1 = new ThreadA("t1"); 
            ThreadA t2 = new ThreadA("t2"); 
            t1.start(); 
            t2.start();
        } 
        static class ThreadA extends Thread{
            public ThreadA(String name){ 
                super(name); 
            } 
            public void run(){ 
                // 获取obj对象的同步锁
                synchronized (obj) {
                    try {
                        for(int i=0; i <10; i++){ 
                            System.out.printf("%s: %d\n", this.getName(), i); 
                            // i能被4整除时,休眠100毫秒
                            if (i%4 == 0)
                                Thread.sleep(100);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } 
        } 
    }
    

    输出的结果(t1 和 t2 的顺序不定,但是必定是同时执行0~9,因为 sleep 并没有释放锁):

    t1: 0
    t1: 1
    t1: 2
    t1: 3
    t1: 4
    t1: 5
    t1: 6
    t1: 7
    t1: 8
    t1: 9
    t2: 0
    t2: 1
    t2: 2
    t2: 3
    t2: 4
    t2: 5
    t2: 6
    t2: 7
    t2: 8
    t2: 9
    

    join

    作用

    让“主线程”等待“子线程”结束之后才能继续运行。

    // 主线程
    public class Father extends Thread {
        public void run() {
            Son s = new Son();
            s.start();
            s.join();
            ...
        }
    }
    // 子线程
    public class Son extends Thread {
        public void run() {
            ...
        }
    }
    

    在上述代码中,是 Father 线程,在 son 子线程运行结束后,再继续运行。

    源码

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {
            while (isAlive()) { // isAlive() 通过子线程调用,则判断的就是子线程
                wait(0); // 等待的是当前线程(CPU 正则运行的线程),而不是子线程!
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    

    测试代码

    public class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("MyThread start ...");
            for (int i = 0; i < 1000000; i++) {
                
            }
            System.out.println("MyThread end ...");
        }
    }
    
    @Test
    public void testJoin() throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
        thread.join();
        System.out.println("testJoin end ...");
    }
    

    输出:

    MyThread start ...
    MyThread end ...
    testJoin end ...
    

    我们可以看到,调用了 join() 后,主线程输出的testJoin end ...是在MyThread end ...之后,这是因为main线程等待thread线程执行结束后,才继续执行。

    问题:为什么在main中调用的thread.join(),却是main线程等待?

    虽然是调用子线程的wait()方法,但是它是通过“主线程”去调用的;所以,休眠的是主线程,而不是“子线程”!

    wait() 源码的注释:Causes the current thread to wait

    用户线程 守护线程

    • 用户线程:用户执行用户级任务
    • 守护线程:“后台线程”,一般用来执行后台任务。

    需要注意的是:Java虚拟机在“用户线程”都结束后会后退出。

    问题:main() 方法,是守护线程?

    答案:不是。main()方法启动的是非守护线程,如果只有这个线程,在mian()的最后一行代码,JVM 会退出(因为所有非守护进程都死了)。

    验证代码:

    public static void main(String[] args){ 
        System.out.println(Thread.currentThread().getName());
        System.out.println(Thread.currentThread().isDaemon());
    } 
    

    输出:

    main
    false
    

    JVM终止运行条件

    当Java虚拟机启动时,通常有一个单一的非守护线程(该线程通过是通过main()方法启动)。JVM会一直运行直到下面的任意一个条件发生,JVM就会终止运行:

    1. 调用了exit()方法,并且exit()有权限被正常执行。
    2. 所有的“非守护线程”都死了(即JVM中仅仅只有“守护线程”)。

    相关文章

      网友评论

        本文标题:JDK源码分析 多线程

        本文链接:https://www.haomeiwen.com/subject/urdolxtx.html