JDK源码分析 多线程

说明

对于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中仅仅只有“守护线程”)。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,423评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,147评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,019评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,443评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,535评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,798评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,941评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,704评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,152评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,494评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,629评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,295评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,901评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,978评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,333评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,499评论 2 348

推荐阅读更多精彩内容

  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,444评论 1 15
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,336评论 3 87
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,952评论 1 18
  • 写在前面的话: 这篇博客是我从这里“转载”的,为什么转载两个字加“”呢?因为这绝不是简单的复制粘贴,我花了五六个小...
    SmartSean阅读 4,715评论 12 45
  • 时间过去2个月了、该放下的还是放下吧。自己努力了,也争取了、不留遗憾就好。 社会现实。多努力、🏠、🚗、你想要的也是...
    devleoper_rui阅读 176评论 0 0