[关闭]
@Yano 2017-08-07T08:32:56.000000Z 字数 5037 阅读 1781

JDK源码分析 多线程

Java


说明

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

实现多线程的两种方式

优先使用 Runnable

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

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

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

测试代码

  1. public class jdk {
  2. public static class MyThread extends Thread {
  3. public MyThread(String name) {
  4. super(name);
  5. }
  6. @Override
  7. public void run() {
  8. System.out.println(Thread.currentThread().getName() + " is running.");
  9. }
  10. }
  11. public static void main(String[] args) {
  12. Thread thread = new MyThread("myThread");
  13. System.out.println(Thread.currentThread().getName()+" call mythread.run()");
  14. thread.run();
  15. System.out.println(Thread.currentThread().getName()+" call mythread.start()");
  16. thread.start();
  17. }
  18. }

输出:

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 源码

  1. public synchronized void start() {
  2. // 如果线程不是"就绪状态",则抛出异常!
  3. if (threadStatus != 0)
  4. throw new IllegalThreadStateException();
  5. // 将线程添加到ThreadGroup中
  6. group.add(this);
  7. boolean started = false;
  8. try {
  9. // 通过start0()启动线程
  10. start0();
  11. // 设置started标记
  12. started = true;
  13. } finally {
  14. try {
  15. if (!started) {
  16. group.threadStartFailed(this);
  17. }
  18. } catch (Throwable ignore) {
  19. }
  20. }
  21. }

run 源码

  1. public void run() {
  2. if (target != null) {
  3. target.run();
  4. }
  5. }

wait(), notify(), notifyAll()

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

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

多线程循环打印 ABC

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

  1. public class PrintABC {
  2. private static final int PRINT_A = 0;
  3. private static final int PRINT_B = 1;
  4. private static final int PRINT_C = 2;
  5. private static class MyThread extends Thread {
  6. int which; // 0:打印A;1:打印B;2:打印C
  7. static volatile int state; // 线程共有,判断所有的打印状态
  8. static final Object t = new Object();
  9. public MyThread(int which) {
  10. this.which = which;
  11. }
  12. @Override
  13. public void run() {
  14. for (int i = 0; i < 10; i++) {
  15. synchronized (t) {
  16. while (state % 3 != which) {
  17. try {
  18. t.wait();
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. System.out.print(toABC(which)); // 执行到这里,表明满足条件,打印
  24. state++;
  25. t.notifyAll(); // 调用notifyAll方法
  26. }
  27. }
  28. }
  29. }
  30. public static void main(String[] args) {
  31. new MyThread(PRINT_A).start();
  32. new MyThread(PRINT_B).start();
  33. new MyThread(PRINT_C).start();
  34. }
  35. private static char toABC(int which) {
  36. return (char) ('A' + which);
  37. }
  38. }

yield

不会释放锁

sleep

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

测试代码

  1. // SleepLockTest.java的源码
  2. public class SleepLockTest{
  3. private static Object obj = new Object();
  4. public static void main(String[] args){
  5. ThreadA t1 = new ThreadA("t1");
  6. ThreadA t2 = new ThreadA("t2");
  7. t1.start();
  8. t2.start();
  9. }
  10. static class ThreadA extends Thread{
  11. public ThreadA(String name){
  12. super(name);
  13. }
  14. public void run(){
  15. // 获取obj对象的同步锁
  16. synchronized (obj) {
  17. try {
  18. for(int i=0; i <10; i++){
  19. System.out.printf("%s: %d\n", this.getName(), i);
  20. // i能被4整除时,休眠100毫秒
  21. if (i%4 == 0)
  22. Thread.sleep(100);
  23. }
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }
  30. }

输出的结果(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

作用

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

  1. // 主线程
  2. public class Father extends Thread {
  3. public void run() {
  4. Son s = new Son();
  5. s.start();
  6. s.join();
  7. ...
  8. }
  9. }
  10. // 子线程
  11. public class Son extends Thread {
  12. public void run() {
  13. ...
  14. }
  15. }

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

源码

  1. public final synchronized void join(long millis)
  2. throws InterruptedException {
  3. long base = System.currentTimeMillis();
  4. long now = 0;
  5. if (millis < 0) {
  6. throw new IllegalArgumentException("timeout value is negative");
  7. }
  8. if (millis == 0) {
  9. while (isAlive()) { // isAlive() 通过子线程调用,则判断的就是子线程
  10. wait(0); // 等待的是当前线程(CPU 正则运行的线程),而不是子线程!
  11. }
  12. } else {
  13. while (isAlive()) {
  14. long delay = millis - now;
  15. if (delay <= 0) {
  16. break;
  17. }
  18. wait(delay);
  19. now = System.currentTimeMillis() - base;
  20. }
  21. }
  22. }

测试代码

  1. public class MyThread extends Thread {
  2. @Override
  3. public void run() {
  4. System.out.println("MyThread start ...");
  5. for (int i = 0; i < 1000000; i++) {
  6. }
  7. System.out.println("MyThread end ...");
  8. }
  9. }
  10. @Test
  11. public void testJoin() throws InterruptedException {
  12. Thread thread = new MyThread();
  13. thread.start();
  14. thread.join();
  15. System.out.println("testJoin end ...");
  16. }

输出:

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 会退出(因为所有非守护进程都死了)。

验证代码:

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

输出:

main
false

JVM终止运行条件

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

  1. 调用了exit()方法,并且exit()有权限被正常执行。
  2. 所有的“非守护线程”都死了(即JVM中仅仅只有“守护线程”)。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注