1 概念

1.1 进程

进程是程序被操作系统内核加载到内存中后的一种内存中的数据结构,是系统进行资源分配和调度的基本单位。
思考:如何开启一个java进程?
每当执行 java命令(C:\Program Files\Java\jdk1.8.0_281\bin\java.exe)时,都会开启一个java进程。
java进程是否可以开启多个?
进程包括:

  • 进程的指令序列(代码段)
  • 进程号 pid
  • 进程的内存空间(堆空间,栈空间),
  • 系统资源(打开的文件描述符、网络IO Socket等)

image.png
什么是ava进程的堆空间,什么栈空间?

1.2 线程

  • 在一个进程内部,可以有多个线程(每个线程对应一组指令序列,可由cpu执行),由操作系统进行调度
  • 这些线程共享进程的堆内存,共享进程打开的系统资源
  • 每个线程有自己的栈空间

JVM-Architecture.png

1.3 主线程、子线程、守护(deamon)线程、非守护线程

  1. main线程结束后,java进程是否会结束?

取决与main方法中新开启的线程是不是 守护线程,如果不是守护线程,则java进程不会退出,如果是守护线程,则java程序会退出。

  1. // 如果不是守护线程,则java进程不会退出
  2. public class ThreadDemo01 {
  3. public static void main(String[] args) {
  4. System.out.println("main 方法执行开始");
  5. final Thread thread = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. while (true){
  9. try {
  10. TimeUnit.SECONDS.sleep(1);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println(Thread.currentThread().getName());
  15. }
  16. }
  17. });
  18. //开启新的线程
  19. thread.start();
  20. System.out.println("main 方法执行结束");
  21. }
  22. }
  1. // 如果是守护线程,则java程序会退出
  2. public class ThreadDemo01 {
  3. public static void main(String[] args) throws InterruptedException {
  4. System.out.println("main 方法执行开始");
  5. final Thread thread = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. while (true){
  9. try {
  10. TimeUnit.SECONDS.sleep(1);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println(Thread.currentThread().getName());
  15. }
  16. }
  17. });
  18. // 设置线程为守护(后台)线程
  19. thread.setDaemon(true);
  20. //开启新的线程
  21. thread.start();
  22. TimeUnit.SECONDS.sleep(3);
  23. System.out.println("main 方法执行结束");
  24. }
  25. }
  1. java进程退出的条件?

java程序是否退出,取决于当前是否还有至少一个非守护线程存活。

1.4 main方法启动时,开启了几个线程?

  1. private static void printAllThread() {
  2. ThreadGroup group = Thread.currentThread().getThreadGroup();
  3. ThreadGroup topGroup = group;
  4. // 遍历线程组树,获取根线程组
  5. while (group != null) {
  6. topGroup = group;
  7. group = group.getParent();
  8. }
  9. // 激活的线程数再加一倍,防止枚举时有可能刚好有动态线程生成
  10. int slackSize = topGroup.activeCount() * 2;
  11. Thread[] slackThreads = new Thread[slackSize];
  12. // 获取根线程组下的所有线程,返回的actualSize便是最终的线程数
  13. int actualSize = topGroup.enumerate(slackThreads);
  14. Thread[] atualThreads = new Thread[actualSize];
  15. // 复制slackThreads中有效的值到atualThreads
  16. System.arraycopy(slackThreads, 0, atualThreads, 0, actualSize);
  17. System.out.println("Threads size is " + atualThreads.length);
  18. for (Thread thread : atualThreads) {
  19. System.out.println("Thread name : " + thread.getName());
  20. }
  21. }
  22. private static void dumpAllThreadsInfo() {
  23. Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
  24. for (Thread thread : threadSet) {
  25. System.out.println("dumpAllThreadsInfo thread.name=" + thread.getName()
  26. + ";group=" + thread.getThreadGroup()
  27. + ";isDaemon=" + thread.isDaemon()
  28. + ";priority=" + thread.getPriority());
  29. }
  30. }

1.5 串行、并行、并发

串行执行任务

  1. public class ThreadDemo02 {
  2. public void task01() throws InterruptedException {
  3. // 封装要执行的任务的代码逻辑
  4. TimeUnit.SECONDS.sleep(1);
  5. System.out.println("task01 is done");
  6. }
  7. public void task02() throws InterruptedException {
  8. // 封装要执行的任务的代码逻辑
  9. TimeUnit.SECONDS.sleep(2);
  10. System.out.println("task02 is done");
  11. }
  12. public void task03() throws InterruptedException {
  13. // 封装要执行的任务的代码逻辑
  14. TimeUnit.SECONDS.sleep(3);
  15. System.out.println("task03 is done");
  16. }
  17. // 如何执行这三个任务呢?
  18. // 串行执行: 用一个线程把三个任务都执行完
  19. public static void main(String[] args) throws InterruptedException {
  20. new Thread(new Runnable() {
  21. @Override
  22. public void run() {
  23. final ThreadDemo02 threadDemo02 = new ThreadDemo02();
  24. try {
  25. threadDemo02.task01();
  26. threadDemo02.task02();
  27. threadDemo02.task03();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }).start();
  33. System.out.println("main finished");
  34. }
  35. }

并行执行任务

  1. /**
  2. * 串行、并行(并发)
  3. */
  4. public class ThreadDemo03 {
  5. public void task01() throws InterruptedException {
  6. // 封装要执行的任务的代码逻辑
  7. TimeUnit.SECONDS.sleep(1);
  8. System.out.println("task01 is done");
  9. }
  10. public void task02() throws InterruptedException {
  11. // 封装要执行的任务的代码逻辑
  12. TimeUnit.SECONDS.sleep(2);
  13. System.out.println("task02 is done");
  14. }
  15. public void task03() throws InterruptedException {
  16. // 封装要执行的任务的代码逻辑
  17. TimeUnit.SECONDS.sleep(3);
  18. System.out.println("task03 is done");
  19. }
  20. // 如何执行这三个任务呢?
  21. // 并行执行: 用三个线程分别去执行三个任务,每个线程执行一个任务
  22. public static void main(String[] args) throws InterruptedException {
  23. final ThreadDemo03 threadDemo03 = new ThreadDemo03();
  24. // 创建三个线程执行任务
  25. final Thread thread1 = new Thread(new Runnable() {
  26. @Override
  27. public void run() {
  28. try {
  29. final long start = System.currentTimeMillis();
  30. threadDemo03.task01();
  31. final long end = System.currentTimeMillis();
  32. System.out.println(Thread.currentThread().getName() + ":job finished,time used:" + (end - start));
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. });
  38. final Thread thread2 = new Thread(new Runnable() {
  39. @Override
  40. public void run() {
  41. try {
  42. final long start = System.currentTimeMillis();
  43. threadDemo03.task02();
  44. final long end = System.currentTimeMillis();
  45. System.out.println(Thread.currentThread().getName() + ":job finished,time used:" + (end - start));
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. });
  51. final Thread thread3 = new Thread(new Runnable() {
  52. @Override
  53. public void run() {
  54. try {
  55. final long start = System.currentTimeMillis();
  56. threadDemo03.task03();
  57. final long end = System.currentTimeMillis();
  58. System.out.println(Thread.currentThread().getName() + ":job finished,time used:" + (end - start));
  59. } catch (InterruptedException e) {
  60. e.printStackTrace();
  61. }
  62. }
  63. });
  64. // 启动执行任务的线程
  65. thread1.setName("thread1");
  66. thread2.setName("thread2");
  67. thread3.setName("thread3");
  68. final long start = System.currentTimeMillis();
  69. thread1.start();
  70. thread2.start();
  71. thread3.start();
  72. // 让main线程等待其他三个线程都结束后再继续执行后面的代码
  73. thread1.join(); // 等thread1结束
  74. thread2.join();
  75. thread3.join();
  76. final long end = System.currentTimeMillis();
  77. System.out.println("main finished,time used:"+(end-start));
  78. }
  79. }

image.png


2 创建线程

2.1 直接使用Thread类的构造函数

  1. final Thread t1 = new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. try {
  5. threadDemo02.task1();
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. });

2.2 继承Thread类覆盖run方法

  1. /**
  2. * 创建线程的方式
  3. */
  4. public class ThreadDemo04 extends Thread{
  5. // 覆盖run 方法
  6. @Override
  7. public void run() {
  8. System.out.println(Thread.currentThread().getName()+ ": ThreadDemo04 started....");
  9. }
  10. public static void main(String[] args) {
  11. final ThreadDemo04 threadDemo04 = new ThreadDemo04();
  12. threadDemo04.start();
  13. System.out.println(Thread.currentThread().getName()+ " main finished");
  14. }
  15. }

3 启动线程

3.1 启动线程的是run方法还是start方法?

  1. public class ThreadDemo04 extends Thread{
  2. // 覆盖run 方法
  3. @Override
  4. public void run() {
  5. System.out.println(Thread.currentThread().getName()+ ": ThreadDemo04 started....");
  6. }
  7. public static void main(String[] args) {
  8. final ThreadDemo04 threadDemo04 = new ThreadDemo04();
  9. // threadDemo04.start();
  10. // 在当前线程中进行的一个对象的实例方法的调用而已,run就是一个普通的实例方法
  11. // 属于当前线程内的串行方法调用
  12. // threadDemo04.run();
  13. // start方法,内部有特殊的逻辑,会让jvm跟底层操作系统交互,创建出一个新的线程
  14. threadDemo04.start();
  15. System.out.println(Thread.currentThread().getName()+ " main finished");
  16. }
  17. }
  • start方法是启动线程的方法
  • run方法定义了线程启动之后要执行的业务逻辑
  • 如果在main线程中直接调用了线程对象的run方法,则相当于串行模式调用所有线程的run方法了,不会开启新的线程。

    3.2 java真的能启动一个线程吗?

    实际上线程的启动是由操作系统完成的,java语言中的Thread对象的start方法,最后会调用一个navtive方法start0,该方法会借由jvm进行底层的创建线程的系统调用,最后由操作系统内核创建出一个新的线程,并且把新线程的方法入口,设置为线程对象的run方法。
    1. private native void start0();

4 线程的调度

4.1 线程的调度由操作系统决定

4.2 多线程程序可能出现不同的执行结果

在没有采用任何同步措施的情况下,多线程程序的运行结果可能不同。

  1. package com.qf.sy2103.thread01;
  2. public class ThreadDemo04 {
  3. public static void main(String[] args) {
  4. final Thread t1 = new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. System.out.println("t1 started ...");
  8. }
  9. });
  10. final Thread t2 = new Thread(new Runnable() {
  11. @Override
  12. public void run() {
  13. System.out.println("t2 started ...");
  14. }
  15. });
  16. final Thread t3 = new Thread(new Runnable() {
  17. @Override
  18. public void run() {
  19. System.out.println("t3 started ...");
  20. }
  21. });
  22. t1.start();
  23. t2.start();
  24. t3.start();
  25. }
  26. }

image.png

5 Thread对象API

  • currentThread
    • 获取到执行当前方法的Thread对象
    • 是一个native方法
  • isAlive ```java package com.qf.sy2103.thread01;

import java.util.concurrent.TimeUnit;

public class ThreadApiDemo {

  1. public static void main(String[] args) throws InterruptedException {
  2. final Thread t1 = new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. System.out.println(Thread.currentThread().isAlive());
  6. System.out.println("t1 started ...");
  7. }
  8. });
  9. System.out.println(t1.isAlive());
  10. t1.start();
  11. TimeUnit.SECONDS.sleep(1);
  12. System.out.println(t1.isAlive());
  13. }

}

  1. - sleep
  2. - 让执行当前方法的线程,进入休眠状态,休眠方法参数指定的毫秒数。
  3. - getid
  4. ```java
  5. System.out.println("main 线程的id为:"+Thread.currentThread().getId());
  6. System.out.println("t1 线程的id为:"+t1.getId());
  • interrupt

中断失败的例子如下

  1. public class ThreadDemo08 {
  2. public static void main(String[] args) throws InterruptedException {
  3. final Thread thread1 = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. while (true){
  7. try {
  8. TimeUnit.SECONDS.sleep(1);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. System.out.println(Thread.currentThread().getName()+"is running....");
  13. }
  14. }
  15. });
  16. thread1.setName("thread1");
  17. thread1.start();
  18. TimeUnit.SECONDS.sleep(2);
  19. // 中断 thread1
  20. thread1.interrupt();
  21. }
  22. }
  1. public class ThreadInterruptDemo {
  2. public static void main(String[] args) throws InterruptedException {
  3. final Thread t1 = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. while (true){
  7. System.out.println("t1 is busy ...");
  8. // 监听线程自身是否被中断
  9. if (Thread.currentThread().isInterrupted()) {
  10. return;
  11. }
  12. }
  13. }
  14. });
  15. t1.start();
  16. TimeUnit.SECONDS.sleep(1);
  17. t1.interrupt(); // 打断t1线程
  18. }
  19. }
  1. // 打断 sleep中的线程 会抛出异常 InterruptedException
  2. public class ThreadInterruptDemo {
  3. public static void main(String[] args) throws InterruptedException {
  4. final Thread t1 = new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. while (true){
  8. System.out.println("t1 is busy ...");
  9. try {
  10. Thread.sleep(1000000);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. });
  17. t1.start();
  18. TimeUnit.SECONDS.sleep(1);
  19. t1.interrupt(); // 打断t1线程
  20. }
  21. }
  • setDeamon

设置线程是否为守护线程,默认是false 。java应用中必须要有至少一个非守护线程存活,程序才不会退出。

  • join
    • 在某个线程对象上调用join方法,会把执行当前方法的线程进行阻塞,等到目标线程执行完毕后才可以继续运行。
    • 注意:调用join方法时,存在两个线程,一个是发起调用的线程,例如main线程,另外一个是执行join方法的线程对象所代表的线程A。执行的效果是让main线程进入等待,等待线程A执行完毕。 ```java package com.qf.sy2103.thread01;

import java.util.concurrent.TimeUnit;

public class ThreadJoinDemo {

  1. public static void main(String[] args) throws InterruptedException {
  2. final Thread t1 = new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. try {
  6. TimeUnit.SECONDS.sleep(1);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. });
  12. final Thread t2 = new Thread(new Runnable() {
  13. @Override
  14. public void run() {
  15. try {
  16. TimeUnit.SECONDS.sleep(2);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. });
  22. final long start = System.currentTimeMillis();
  23. t1.start();
  24. t2.start();
  25. t1.join(); // 等待,直到 t1线程 运行结束
  26. t2.join();
  27. final long end = System.currentTimeMillis();
  28. System.out.println(end - start);
  29. }

}

  1. 思考题:有三个线程ABC,希望以如下顺序执行,A先执行,A执行完之后B执行,B执行完之后C执行?
  2. ```java
  3. /**
  4. * 思考题:有三个线程A、B和C,希望以如下顺序执行,A先执行,A执行完之后B执行,B执行完之后C执行?
  5. */
  6. public class ThreadDemo09 {
  7. public static void main(String[] args) throws InterruptedException {
  8. final Thread threadA = new Thread(new Runnable() {
  9. @Override
  10. public void run() {
  11. System.out.println(Thread.currentThread().getName()+": 完成了任务A");
  12. }
  13. });
  14. final Thread threadB = new Thread(new Runnable() {
  15. @Override
  16. public void run() {
  17. try {
  18. threadA.join(); // 等待 A线程执行完成
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. System.out.println(Thread.currentThread().getName()+": 完成了任务B");
  23. }
  24. });
  25. final Thread threadC = new Thread(new Runnable() {
  26. @Override
  27. public void run() {
  28. try {
  29. threadB.join();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. System.out.println(Thread.currentThread().getName()+": 完成了任务C");
  34. }
  35. });
  36. threadA.setName("A");
  37. threadB.setName("B");
  38. threadC.setName("C");
  39. // 启动三个线程
  40. threadA.start();
  41. threadB.start();
  42. threadC.start();
  43. }
  44. }

6 线程的生命周期

6.1 Thread类中定义的线程状态

  1. public enum State {
  2. /**
  3. * Thread state for a thread which has not yet started.
  4. */
  5. NEW,
  6. /**
  7. * Thread state for a runnable thread. A thread in the runnable
  8. * state is executing in the Java virtual machine but it may
  9. * be waiting for other resources from the operating system
  10. * such as processor.
  11. */
  12. RUNNABLE,
  13. /**
  14. * Thread state for a thread blocked waiting for a monitor lock.
  15. * A thread in the blocked state is waiting for a monitor lock
  16. * to enter a synchronized block/method or
  17. * reenter a synchronized block/method after calling
  18. * {@link Object#wait() Object.wait}.
  19. */
  20. BLOCKED,
  21. /**
  22. * Thread state for a waiting thread.
  23. * A thread is in the waiting state due to calling one of the
  24. * following methods:
  25. * <ul>
  26. * <li>{@link Object#wait() Object.wait} with no timeout</li>
  27. * <li>{@link #join() Thread.join} with no timeout</li>
  28. * <li>{@link LockSupport#park() LockSupport.park}</li>
  29. * </ul>
  30. *
  31. * <p>A thread in the waiting state is waiting for another thread to
  32. * perform a particular action.
  33. *
  34. * For example, a thread that has called <tt>Object.wait()</tt>
  35. * on an object is waiting for another thread to call
  36. * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
  37. * that object. A thread that has called <tt>Thread.join()</tt>
  38. * is waiting for a specified thread to terminate.
  39. */
  40. WAITING,
  41. /**
  42. * Thread state for a waiting thread with a specified waiting time.
  43. * A thread is in the timed waiting state due to calling one of
  44. * the following methods with a specified positive waiting time:
  45. * <ul>
  46. * <li>{@link #sleep Thread.sleep}</li>
  47. * <li>{@link Object#wait(long) Object.wait} with timeout</li>
  48. * <li>{@link #join(long) Thread.join} with timeout</li>
  49. * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
  50. * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
  51. * </ul>
  52. */
  53. TIMED_WAITING,
  54. /**
  55. * Thread state for a terminated thread.
  56. * The thread has completed execution.
  57. */
  58. TERMINATED;
  59. }

6.2 生命周期状态转换

线程基础 - 图5

6.3 案例 利用jconsole观察线程状态

  1. // 线程的NEW RUNNABLE TERMINATED
  2. public class ThreadStateDemo {
  3. public static void main(String[] args) throws InterruptedException {
  4. final Thread t1 = new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. int a = 0;
  8. for (int i = 0; i < 100000000 ; i++) {
  9. a++;
  10. }
  11. System.out.println(a);
  12. }
  13. });
  14. System.out.println("线程对象刚创建好:"+t1.getState());
  15. t1.start();
  16. Thread.sleep(1);
  17. System.out.println("线程对象已经start:"+t1.getState());
  18. Thread.sleep(2000);
  19. System.out.println("线程已经运行完成:"+t1.getState());
  20. }
  21. }
  1. /**
  2. * 如果线程执行了 休眠方法 , Thread.sleep TimeUnit.SECONDS.sleep
  3. * 则线程的状态是 TIMED_WAITING
  4. */
  5. public class ThreadStateDemo3 {
  6. public static void main(String[] args) throws InterruptedException {
  7. final Thread t1 = new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. try {
  11. TimeUnit.SECONDS.sleep(10000);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16. });
  17. t1.start();
  18. TimeUnit.SECONDS.sleep(2);
  19. System.out.println(t1.getState());
  20. }
  21. }
  1. // 观察主线程和子线程的状态变化
  2. public class ThreadStateDemo2 {
  3. public static void main(String[] args) throws InterruptedException, IOException {
  4. Thread mainThread = Thread.currentThread();
  5. Object lock = new Object();
  6. Thread subthread = new Thread(new Runnable() {
  7. @Override
  8. public void run() {
  9. try {
  10. Thread.sleep(1000);
  11. synchronized (lock) {
  12. System.out.println("mainThread state is "+mainThread.getState());
  13. lock.notify();
  14. }
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. });
  20. subthread.start();
  21. synchronized (lock) {
  22. System.in.read();
  23. System.out.println("subthread state is "+subthread.getState());
  24. lock.wait();
  25. }
  26. }
  27. }

观察主线程由waitting状态转换为Block状态

  1. public class ThreadStateDemo2 {
  2. public static void main(String[] args) throws InterruptedException, IOException {
  3. Thread mainThread = Thread.currentThread();
  4. Object lock = new Object();
  5. Thread t1 = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. try {
  9. Thread.sleep(1000);
  10. synchronized (lock) {
  11. System.out.println("t1 mainThread state is "+mainThread.getState());
  12. lock.notify();
  13. }
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. });
  19. Thread t2 = new Thread(new Runnable() {
  20. @Override
  21. public void run() {
  22. try {
  23. Thread.sleep(1000);
  24. synchronized (lock) {
  25. System.out.println("t2 mainThread state is "+mainThread.getState());
  26. lock.notify();
  27. }
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. });
  33. t1.start();
  34. t2.start();
  35. synchronized (lock) {
  36. System.in.read();
  37. System.out.println("t1 state is "+t1.getState());
  38. System.out.println("t2 state is "+t2.getState());
  39. lock.wait();
  40. }
  41. }
  42. }

观察主线程由Timed-waitting状态转换为Block状态

  1. public class ThreadStateDemo3 {
  2. public static void main(String[] args) throws InterruptedException, IOException {
  3. Thread mainThread = Thread.currentThread();
  4. Object lock = new Object();
  5. Thread t1 = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. try {
  9. Thread.sleep(1000);
  10. synchronized (lock) {
  11. System.out.println("t1 mainThread state is "+mainThread.getState());
  12. Thread.sleep(3000);
  13. System.out.println("t1 mainThread state is "+mainThread.getState());
  14. lock.notify();
  15. }
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. });
  21. t1.start();
  22. synchronized (lock) {
  23. System.in.read();
  24. System.out.println("t1 state is "+t1.getState());
  25. // 主线程等待2s后醒来,继续争抢锁
  26. // 注意:不是wait达到超时时间后就无条件继续向下执行代码!而是要继续去枪锁!
  27. lock.wait(2000);
  28. // 如果醒来后争抢锁失败,则线程直接进入blcok状态
  29. // 当持有锁的线程释放锁后,主线程会继续去争抢锁,如果抢到,就可以继续执行
  30. System.out.println("main finished ");
  31. }
  32. }
  33. }