1. Thread

1.1 线程属性与状态流

  • 线程ID:线程用ID来标识出不同线程
  • 线程名字(Name):让用户或者程序猿开发调试或运行中定位线程的问题等。
  • 守护线程(isDaemon):当为true时,代表该线程为守护线程,false为非守护线程,也可以称作用户线程
  • 线程优先级(Priority):作用是告诉线程调度器,希望那个线程多运行,那个线程少运行。

1.1.1 线程ID

  • 线程ID从1开始,JVM运行起来后,我们自己创建的线程Id 早已不是0
  1. /**
  2. * @author yiren
  3. */
  4. public class ID {
  5. public static void main(String[] args) {
  6. Thread thread = new Thread();
  7. System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
  8. System.out.println(thread.getName() + ": " + thread.getId());
  9. }
  10. }
  1. main: 1
  2. Thread-0: 13
  • 在线程初始化中,有tid赋值
  1. /* For generating thread ID */
  2. private static long threadSeqNumber;
  3. private void init(ThreadGroup g, Runnable target, String name,
  4. long stackSize, AccessControlContext acc,
  5. boolean inheritThreadLocals) {
  6. ......
  7. /* Set thread ID */
  8. tid = nextThreadID();
  9. }
  10. private static synchronized long nextThreadID() {
  11. return ++threadSeqNumber;
  12. }
  • threadSeqNumber没有赋值,所以为0,而nextThreadID()++threadSeqNumber使得线程从ID从1开始.
  • 那么为什么答应出来的子线程ID是13呢?
    • 如果你是用的IDEA,可以通过debug下个断点在最后一句上面,然后看一下当前线程的情况。
    • 实际就是JVM帮我们起了一些线程。

image.png

  • Finalizer线程: JVM进行GC时,首先使用可达性分析算法,找出不在GC Roots引用链上的对象,这时进行一次标记(标记出需要回收的对象)并筛选(对需要回收对象进行筛选),筛选条件就是是否有必要执行finalize方法。当对象没有覆盖或已执行过finalize方法,则没有必要执行;否则,将对象放到由JVM创建的Finalizer线程维护的F-Queue(java.lang.ref.Finalizer.ReferenceQueue)队列中,Finalizer线程会遍历执行队列中对象的finalize方法,只有当F-Queue中对象finalize执行完成后,并且下次GC时可达性分析不再GC Roots的引用链上,则这些对象占用的内存才能被真正回收。重写finalize方法可以方便我们去重新建立对象的引用关系,避免被回收。

  • Reference Handler 线程:ReferenceHandler线程是一个拥有最高优先级的守护线程,它是Reference类的一个内部类,在Reference类加载执行cinit的时候被初始化并启动;它的任务就是当pending队列不为空的时候,循环将pending队列里面的头部的Reference移除出来,如果这个对象是个Cleaner实例,那么就直接执行它的clean方法来执行清理工作;

  • Signal Dispatcher 线程: Attach Listener 线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始化工作。类似的还有Attach Listner线程。

详情见:https://blog.csdn.net/clamaa/article/details/70045983

1.1.2 线程名字

  1. 默认线程名字源码分析:
  • 在没有指定线程名字的时候,线程默认传一个"Thread-" + nextThreadNum()作为名字
  • 而nextThreadNum获取到的threadInitNumber则是一个从0自增的一个静态变量。因为用了synchronized,所以是不会重复的。
    1. /* For autonumbering anonymous threads. */
    2. private static int threadInitNumber;
    3. private static synchronized int nextThreadNum() {
    4. return threadInitNumber++;
    5. }
    6. public Thread() {
    7. init(null, null, "Thread-" + nextThreadNum(), 0);
    8. }
  1. 如何修改线程名字
  • 一个是通过构造方法
  • 另一个是通过setName(String name)设置

    1. public Thread(String name) {
    2. init(null, null, name, 0);
    3. }
    4. private void init(ThreadGroup g, Runnable target, String name,
    5. long stackSize) {
    6. init(g, target, name, stackSize, null, true);
    7. }
    8. private void init(ThreadGroup g, Runnable target, String name,
    9. long stackSize, AccessControlContext acc,
    10. boolean inheritThreadLocals) {
    11. if (name == null) {
    12. throw new NullPointerException("name cannot be null");
    13. }
    14. this.name = name;
    15. ...
    16. }
    17. public final synchronized void setName(String name) {
    18. checkAccess();
    19. if (name == null) {
    20. throw new NullPointerException("name cannot be null");
    21. }
    22. this.name = name;
    23. if (threadStatus != 0) {
    24. setNativeName(name);
    25. }
    26. }
  • 注意setName(String name)调用的时候,当线程是NEW状态的时候,设置name会一同把JVM里面的CPP的线程名字设置好,而如果不是NEW状态则只能修改java中我们可以看到的名称。

1.1.3 守护线程(Daemon)

  • 作用:给用户线程提供服务
  • 三个特性:
    • 线程默认类型继承自父线程
    • 被谁启动
    • 不影响JVM退出
  • 守护线程和普通线程的区别
    • 整体没有太大区别
    • 唯一的区别是是否影响JVM的退出
  • 常见面试问题
    • 守护线程和用户线程的区别
    • 我们是否需要给线程设置为守护线程?
      • 不需要,如果设置生守护线程,在JVM退出时会忽略你正在执行的任务,如果你正在执行一些数据操作,那么就会造成数据不一致了。

1.1.4 线程优先级

  • 在Java中优先级有个10个等级,默认为5,通过setPriority(int newPriority)设置
  • 但是我们程序在编码的时候,不应该依赖于优先级
    • 高度依赖于操作系统,不同的操作系统在实现优先级执行的时候不一样(windows中只有7个等级,更甚有的没有优先级。)设置优先级是不可靠的
    • 优先级可能会被操作系统改变
  1. /**
  2. * The minimum priority that a thread can have.
  3. */
  4. public final static int MIN_PRIORITY = 1;
  5. /**
  6. * The default priority that is assigned to a thread.
  7. */
  8. public final static int NORM_PRIORITY = 5;
  9. /**
  10. * The maximum priority that a thread can have.
  11. */
  12. public final static int MAX_PRIORITY = 10;
  13. public final void setPriority(int newPriority) {
  14. ThreadGroup g;
  15. checkAccess();
  16. if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
  17. throw new IllegalArgumentException();
  18. }
  19. if((g = getThreadGroup()) != null) {
  20. if (newPriority > g.getMaxPriority()) {
  21. newPriority = g.getMaxPriority();
  22. }
  23. setPriority0(priority = newPriority);
  24. }
  25. }

1.1.5 状态流

  1. Java线程的6个状态

image-20200210205922062.png

  • NEW 已创建还未启动 对应new Thread()过后就是这个状态
  • RUNNABLE 调用了start() 过后 马上进入RUNNABLE 对应操作系统READY和RUNNING
  • BLOCKED 当一个线程进入synchronized代码块的时候并且该锁被其他线程占用就是BLOCKED状态
  • WAITING 没有设置time参数的wait()、join()等方法
  • TIMED-WATING 设置time参数的wait()、join()、sleep()等方法
  • TERMINATED
    1. 阻塞状态
    • 一般习惯把 BLOCKED WAITING TIME_WAITING 都称为阻塞状态

1.2. 线程的启动

1.2.1 启动线程 - start()run()方法调用对比

  1. /**
  2. * 对比start和run两种启动线程的方式
  3. * @author yiren
  4. */
  5. public class StartAndRunThread {
  6. public static void main(String[] args) {
  7. // 直接使用run方法
  8. Runnable runnable = () -> System.out.println(Thread.currentThread().getName());
  9. runnable.run();
  10. Thread thread = new Thread(runnable);
  11. thread.run();
  12. // 使用start
  13. thread.start();
  14. }
  15. }
  1. main
  2. main
  3. Thread-0
  4. Process finished with exit code 0
  • 由上可知, 无论是Runnable还是Thread的调用run()方法都是在当前线程直接运行,就是方法调用。
  • 而调用start()方法的时候,则是另起线程来运行run()方法中的内容。

1.2.2 关于start()方法

  • 启动新线程:
    1. 他会涉及到两个线程,要有一个当前线程调用start()方法,常见的为主线程main,也可以是其他线程;另外一个新线程在核实的时候执行run()方法
    2. Thread的对象通过start()方法告诉JVM,我有一个线程需要启动,你在合适的时候运行它。
  • 准备工作
    1. 首先它要让自己进入就绪状态,就绪状态是指我已经获取到除了CPU意外的其他资源(如上下文、栈、线程状态、PC程序计数器等)
  • 不能重复的start()
    1. /**
    2. * @author yiren
    3. */
    4. public class DoubleStartThread {
    5. public static void main(String[] args) {
    6. Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
    7. thread.start();
    8. thread.start();
    9. }
    10. }
  1. Exception in thread "main" java.lang.IllegalThreadStateException
  2. at java.lang.Thread.start(Thread.java:708)
  3. at com.imyiren.concurrency.threadcore.startthread.DoubleStartThread.main(DoubleStartThread.java:10)
  4. Thread-0
  5. Process finished with exit code 1
  • IllegalThreadStateException 非法线程状态
  • 如果start()开始,正常线程线程就会按照 new->runnable->running->dead,如果一个线程执行完毕就会变成终止,就无法返回回去。所以才会抛出非法线程状态异常

    1.2.3 start()方法源码分析

    1. /* Java thread status for tools,
    2. * initialized to indicate thread 'not yet started'
    3. */
    4. private volatile int threadStatus = 0;
    5. // Thread的start方法
    6. public synchronized void start() {
    7. /**
    8. * This method is not invoked for the main method thread or "system"
    9. * group threads created/set up by the VM. Any new functionality added
    10. * to this method in the future may have to also be added to the VM.
    11. *
    12. * A zero status value corresponds to state "NEW".
    13. */
    14. if (threadStatus != 0)
    15. throw new IllegalThreadStateException();
    16. /* Notify the group that this thread is about to be started
    17. * so that it can be added to the group's list of threads
    18. * and the group's unstarted count can be decremented. */
    19. group.add(this);
    20. boolean started = false;
    21. try {
    22. start0();
    23. started = true;
    24. } finally {
    25. try {
    26. if (!started) {
    27. group.threadStartFailed(this);
    28. }
    29. } catch (Throwable ignore) {
    30. /* do nothing. If start0 threw a Throwable then
    31. it will be passed up the call stack */
    32. }
    33. }
    34. }
  1. 启动新线程检查线程状态
    • 线程的状态threadState,还没有启动的时候,默认值就是0,也就是还没有启动
    • 代码中的状态判断,如果线程状态不为0,那就抛出IllegalThreadStateException异常。
    • 注释上A zero status value corresponds to state "NEW".可知,0就为NEW状态。
  2. 加入线程组
    • 通过状态检查后就把当前线程放入到属性ThreadGroup groupthreads数组中,
  3. 调用start()
    • 然后就去执行private native void start0()方法,注意,此方法是native方法(C++实现)

      1.2.4 run()方法源码分析

      1. /**
      2. * If this thread was constructed using a separate
      3. * <code>Runnable</code> run object, then that
      4. * <code>Runnable</code> object's <code>run</code> method is called;
      5. * otherwise, this method does nothing and returns.
      6. * <p>
      7. * Subclasses of <code>Thread</code> should override this method.
      8. *
      9. * @see #start()
      10. * @see #stop()
      11. * @see #Thread(ThreadGroup, Runnable, String)
      12. */
      13. @Override
      14. public void run() {
      15. if (target != null) {
      16. target.run();
      17. }
      18. }
  • 在多线程中Threadrun()方法会有两种情况,一种是如果重写了就调用重写Thread类的run()方法,一种是调用Runnable实现的run()方法
  • 如果直接调用run()方法的话,就只是调用一个普通的方法而已。要启动一个线程还是只能调用start方法去间接调用我们的run()方法。

    1.3 创建线程到底有几种方法

    1.3.1 Thread源码分析

    1. /* What will be run. */
    2. private Runnable target;
    3. public Thread(Runnable target) {
    4. init(null, target, "Thread-" + nextThreadNum(), 0);
    5. }
    6. private void init(ThreadGroup g, Runnable target, String name,
    7. long stackSize) {
    8. init(g, target, name, stackSize, null, true);
    9. }
    10. private void init(ThreadGroup g, Runnable target, String name,
    11. long stackSize, AccessControlContext acc,
    12. boolean inheritThreadLocals) {
    13. ......
    14. this.target = target;
    15. ......
    16. }
    17. @Override
    18. public void run() {
    19. if (target != null) {
    20. target.run();
    21. }
    22. }
    • 我们可以看到平时我们通过实现Runnable接口和继承Thread来重写run方法,最终归结到了run方法的调用上。一个是重写,一个是调用接口的方法。

1.3.2 Oracle官方文档对创建线程的说明

  1. 将类继承Thread类重写run方法

官方原话:There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread.

  1. /**
  2. * 实现线程的第一个方式 继承Thread
  3. * @author yiren
  4. */
  5. public class MyThread extends Thread {
  6. @Override
  7. public void run() {
  8. System.out.println(Thread.currentThread().getName() + " Thread running...");
  9. }
  10. public static void main(String[] args) throws IOException {
  11. new MyThread().start();
  12. System.in.read();
  13. }
  14. }

  1. 实现Runnable接口

官方原话:The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method.

  1. /**
  2. * 实现线程的第二个方式 实现Runnable接口
  3. * @author yiren
  4. */
  5. public class MyRunnable implements Runnable {
  6. @Override
  7. public void run() {
  8. System.out.println(Thread.currentThread().getName() + " Runnable running...");
  9. }
  10. public static void main(String[] args) throws IOException {
  11. new Thread(new MyRunnable()).start();
  12. System.in.read();
  13. }
  14. }
  • Runnable的优点
    1. 业务代码与线程类创建启动等逻辑解耦。
      • 依赖倒置原则:抽象不应该依赖具体,具体应该依赖抽象
    2. Runnable可复用,Thread则需要每次创建。
    3. 类可以实现多个接口,而不可以继承多个对象。所以接口更好
  • 如果两种方式都用会有什么效果呢?

    1. /**
    2. * @author yiren
    3. */
    4. public class MyThreadAndRunnable {
    5. public static void main(String[] args) {
    6. Thread thread = new Thread(new Runnable() {
    7. @Override
    8. public void run() {
    9. System.out.println(Thread.currentThread().getName() + " runnable running...");
    10. }
    11. }
    12. ) {
    13. @Override
    14. public void run() {
    15. System.out.println(Thread.currentThread().getName() + " thread running...");
    16. }
    17. };
    18. // 这个地方应该是执行重写Thread类的run方法中的逻辑!
    19. thread.start();
    20. }
    21. }
    • 很明显,上面说了不重写Threadrun()方法就是调用target.run(),如果重写那也就没有调用target.run()了。

1.3.3 归根结底

  • 创建线程只有一种方式,就是创建Thread类的对象,而构建一个线程的方式则有多种:比如创建线程类、实现Runnable接口、创建线程池、FutureTask等等。
  • 线程池创建线程:实际是由默认的工厂代为创建Thread类来实现。

    1. // Executors中的DefaultThreadFactory
    2. static class DefaultThreadFactory implements ThreadFactory {
    3. private static final AtomicInteger poolNumber = new AtomicInteger(1);
    4. private final ThreadGroup group;
    5. private final AtomicInteger threadNumber = new AtomicInteger(1);
    6. private final String namePrefix;
    7. DefaultThreadFactory() {
    8. SecurityManager s = System.getSecurityManager();
    9. group = (s != null) ? s.getThreadGroup() :
    10. Thread.currentThread().getThreadGroup();
    11. namePrefix = "pool-" +
    12. poolNumber.getAndIncrement() +
    13. "-thread-";
    14. }
    15. public Thread newThread(Runnable r) {
    16. Thread t = new Thread(group, r,
    17. namePrefix + threadNumber.getAndIncrement(),
    18. 0);
    19. if (t.isDaemon())
    20. t.setDaemon(false);
    21. if (t.getPriority() != Thread.NORM_PRIORITY)
    22. t.setPriority(Thread.NORM_PRIORITY);
    23. return t;
    24. }
    25. }
    • 由上newThread()方法可知,即使是线程池,本质上还是使用Thread的创建线程。
  • Callable和FutureTask创建线程,本质其实也是Thread
  1. public interface RunnableFuture<V> extends Runnable, Future<V> {
  2. /**
  3. * Sets this Future to the result of its computation
  4. * unless it has been cancelled.
  5. */
  6. void run();
  7. }
  1. public class FutureTask<V> implements RunnableFuture<V> {
  2. ......
  3. private volatile Thread runner;
  4. ......
  • 定时器Timer:它的TimerTask其实也是实现了Runnable接口,可以看下TimerTask这个抽象类 ```java

/**

  • @author yiren */ public class TimerExample { public static void main(String[] args) {
    1. Timer timer = new Timer();
    2. // 每隔1s打印下自己的名字
    3. timer.scheduleAtFixedRate(new TimerTask() {
    4. @Override
    5. public void run() {
    6. System.out.println(Thread.currentThread().getName() + " timer running...");
    7. }
    8. }, 1000, 1000);
    } } ```

1.4. 停止线程

1.4.1 使用Interrupt来停止线程

  • 使用interrupt来通知线程停止,而不是强制停止。
    1. 注意只是通知,并不是让线程立即停止。
    2. 只需要通知线程,你需要停止,线程通过响应interrupt来在合适的地方停止或者退出线程的执行。
  • 为什么要这样做呢?
    线程在停止时,所使用的资源没有释放造成资源浪费甚至BUG,数据处理没有完成造成数据不一致,这样的问题往往会令我们头疼。而如果使用interrupt来通知它,线程可以进行停止前的释放资源,完成必须要处理的数据任务,诸如此类的事情,就会令我们的程序的健壮性提升,也减少了系统出现问题的几率
  • 停止普通线程

    1. /**
    2. * run 方法内没有sleep或者wait方法时,停止线程。
    3. *
    4. * @author yiren
    5. */
    6. public class RightStopThreadWithoutSleep {
    7. public static void main(String[] args) throws InterruptedException {
    8. Thread thread = new Thread(() -> {
    9. int num = 0;
    10. long start = System.currentTimeMillis();
    11. while (num <= Integer.MAX_VALUE / 2) {
    12. if (num % 1000 == 0) {
    13. System.out.println(num + " 是10000的倍数!");
    14. }
    15. // 注意 如果不interrupted的响应处理,线程不会处理interrupt
    16. if (Thread.currentThread().isInterrupted()) {
    17. System.out.println(Thread.currentThread().getName() + " was interrupted");
    18. break;
    19. }
    20. num++;
    21. }
    22. long end = System.currentTimeMillis();
    23. System.out.println("Task was finished! " + (end - start) / 1000.0 + "s");
    24. });
    25. thread.start();
    26. Thread.sleep(2000);
    27. thread.interrupt();
    28. }
    29. }

    ```sql …… 401797000 是10000的倍数! Thread-0 was interrupted Task was finished! 2.004s

Process finished with exit code 0

  1. - 停止阻塞线程
  2. - 如果线程在阻塞状态,比如调用`sleep()`方法时,响应`interrupt`的方式是抛出异常。
  3. - 所以停止阻塞线程使用`try-catch`来实现
  4. ```java
  5. /**
  6. * run 方法内有sleep时,停止线程。
  7. * @author yiren
  8. */
  9. public class RightStopThreadWithSleep {
  10. public static void main(String[] args) throws InterruptedException {
  11. Thread thread = new Thread(() -> {
  12. int num = 0;
  13. long start = System.currentTimeMillis();
  14. while (num <= 300) {
  15. if (num % 100 == 0) {
  16. System.out.println(num + " 是100的倍数!");
  17. }
  18. num++;
  19. // 注意 如果不interrupted的响应处理,线程不会处理interrupt
  20. if (Thread.currentThread().isInterrupted()) {
  21. System.out.println(Thread.currentThread().getName() + " was interrupted");
  22. break;
  23. }
  24. }
  25. try {
  26. Thread.sleep(1000);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. System.out.println(Thread.currentThread().getName() + " thread was interrupted by sleep!");
  30. }
  31. long end = System.currentTimeMillis();
  32. System.out.println("Task was finished! " + (end - start) / 1000.0 + "s");
  33. });
  34. thread.start();
  35. Thread.sleep(500);
  36. thread.interrupt();
  37. }
  38. }
  1. 0 100的倍数!
  2. 100 100的倍数!
  3. 200 100的倍数!
  4. 300 100的倍数!
  5. java.lang.InterruptedException: sleep interrupted
  6. at java.lang.Thread.sleep(Native Method)
  7. at com.imyiren.concurrency.threadcore.stopthread.RightStopThreadWithSleep.lambda$main$0(RightStopThreadWithSleep.java:26)
  8. at java.lang.Thread.run(Thread.java:748)
  9. Thread-0 thread was interrupted by sleep!
  10. Task was finished! 0.505s
  11. Process finished with exit code 0
  • 每个循环中都有sleep

    • 如果每个循环都有阻塞, 我们就可以不用每个循环都判断一次interrupted了,只需要处理catch的异常即可。

      1. /**
      2. * 在执行过程中每次循环都会调用sleep获wait等方法
      3. *
      4. * @author yiren
      5. */
      6. public class RightStopThreadWithSleepInLoop {
      7. public static void main(String[] args) throws InterruptedException {
      8. Thread thread = new Thread(() -> {
      9. int num = 0;
      10. long start = System.currentTimeMillis();
      11. try {
      12. while (num <= 10000) {
      13. if (num % 100 == 0) {
      14. System.out.println(num + " 是100的倍数!");
      15. }
      16. Thread.sleep(10);
      17. num++;
      18. }
      19. } catch (InterruptedException e) {
      20. e.printStackTrace();
      21. System.out.println(Thread.currentThread().getName() + " thread was interrupted by sleep!");
      22. }
      23. long end = System.currentTimeMillis();
      24. System.out.println("Task was finished! " + (end - start) / 1000.0 + "s");
      25. });
      26. thread.start();
      27. Thread.sleep(5000);
      28. thread.interrupt();
      29. }
      30. }

      ```sql 0 是100的倍数! 100 是100的倍数! 200 是100的倍数! 300 是100的倍数! 400 是100的倍数! java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.imyiren.concurrency.threadcore.stopthread.RightStopThreadWithSleepInLoop.lambda$main$0(RightStopThreadWithSleepInLoop.java:19) at java.lang.Thread.run(Thread.java:748) Thread-0 thread was interrupted by sleep! Task was finished! 5.005s

Process finished with exit code 0

  1. - 这个地方需要注意一个地方,`try-catch`的位置,这个不难看出,如果是下列代码,则不能`interrupt`,会死循环。。。
  2. ```java
  3. /**
  4. * @author yiren
  5. */
  6. public class CantInterrupt {
  7. public static void main(String[] args) throws InterruptedException {
  8. Thread thread = new Thread(() -> {
  9. int num = 0;
  10. long start = System.currentTimeMillis();
  11. while (num <= 10000) {
  12. if (num % 100 == 0) {
  13. System.out.println(num + " 是100的倍数!");
  14. }
  15. try {
  16. Thread.sleep(10);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. System.out.println(Thread.currentThread().getName() + " thread was interrupted by sleep!");
  20. }
  21. num++;
  22. }
  23. long end = System.currentTimeMillis();
  24. System.out.println("Task was finished! " + (end - start) / 1000.0 + "s");
  25. });
  26. thread.start();
  27. Thread.sleep(5000);
  28. thread.interrupt();
  29. }
  30. }
  1. 0 100的倍数!
  2. 100 100的倍数!
  3. 200 100的倍数!
  4. 300 100的倍数!
  5. 400 100的倍数!
  6. java.lang.InterruptedException: sleep interrupted
  7. at java.lang.Thread.sleep(Native Method)
  8. at com.imyiren.concurrency.threadcore.stopthread.CantInterrupt.lambda$main$0(CantInterrupt.java:17)
  9. at java.lang.Thread.run(Thread.java:748)
  10. Thread-0 thread was interrupted by sleep!
  11. 500 100的倍数!
  12. 600 100的倍数!
  13. 700 100的倍数!
  14. 800 100的倍数!
  15. ......
  • InterruptedException处理最佳实践(业务中如何使用?)
    • 绝对不应屏蔽中断请求
  1. run()方法直接抛出**interruptedException**,不做处理
    • 首先我们不能在业务方法中直接处理掉异常,不能try-catch,需要直接抛出。
    • 那么我们在业务方法中处理了这个异常会怎么样呢?那么如果run()方法中有循环,则无法退出循环。。
    • 最佳实践:在业务代码中有InterruptedException 优先选择 在方法签名中抛出异常,不处理。那么就会使InterruptedExceptionrun()方法中强制try-catch。如下代码
  1. /**
  2. * 生产中如何处理interrupted
  3. *
  4. * @author yiren
  5. */
  6. public class RightStopThreadInProd implements Runnable {
  7. @Override
  8. public void run() {
  9. try {
  10. while (true) {
  11. System.out.println("business code...");
  12. // 假设调用其他方法
  13. throwInMethod();
  14. System.out.println("business code...");
  15. }
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. System.out.println("catch interruptedException handle interrupted! ...");
  19. }
  20. }
  21. private void throwInMethod() throws InterruptedException {
  22. Thread.sleep(1000);
  23. }
  24. public static void main(String[] args) throws InterruptedException {
  25. Thread thread = new Thread(new RightStopThreadInProd());
  26. thread.start();
  27. Thread.sleep(500);
  28. thread.interrupt();
  29. }
  30. }
  1. business code...
  2. java.lang.InterruptedException: sleep interrupted
  3. at java.lang.Thread.sleep(Native Method)
  4. at com.imyiren.concurrency.threadcore.stopthread.RightStopThreadInProd.throwInMethod(RightStopThreadInProd.java:28)
  5. at com.imyiren.concurrency.threadcore.stopthread.RightStopThreadInProd.run(RightStopThreadInProd.java:18)
  6. at java.lang.Thread.run(Thread.java:748)
  7. catch interruptedException handle interrupted! ...
  8. Process finished with exit code 0
  1. 直接在业务方法中恢复中断(当业务方法无法抛出或不想抛出时)

    • 就是利用中断机制,调用Thread.currentThread().interrupt() 来恢复中断 ```java /**
    • 生产中如何处理interrupted 2
    • 最佳实践:在业务代码中有InterruptedException 在catch语句中调用Thread.currentThread().interrupt()
    • 以便于在后续的执行中,能够检测到发生了中断。
    • @author yiren */ public class RightStopThreadInProd2 implements Runnable {

      @Override public void run() { while (!Thread.currentThread().isInterrupted()) {

      1. System.out.println("business code...");
      2. // 假设调用其他方法
      3. reInterrupted();
      4. System.out.println("business code...");

      } }

      private void reInterrupted() { try {

      1. System.out.println("reInterrupted method business! ");
      2. Thread.sleep(1000);

      } catch (InterruptedException e) {

      1. System.out.println(Thread.currentThread().getName() + " reInterrupted interrupt");
      2. Thread.currentThread().interrupt();

      } }

      public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new RightStopThreadInProd2()); thread.start(); Thread.sleep(1500); thread.interrupt(); } } ```

  1. business code...
  2. reInterrupted method business!
  3. business code...
  4. business code...
  5. reInterrupted method business!
  6. Thread-0 reInterrupted interrupt
  7. business code...
  8. Process finished with exit code 0
  • 响应中断的一些方法

    1. Object.wait(...)
    2. Thraed.sleep(...)
    3. Thread.join(...)
    4. java.util.concurrent.BlockingQueue.take()/put(E)
    5. java.util.concurrent.locks.Lock.lockInterruptibly()
    6. java.util.concurrent.CountDownLatch.await()
    7. java.util.CyclicBarrier.await()
    8. java.util.concurrent.Exchanger.exchange(V)
    9. java.nio.channels.InterruptibleChannel的相关方法
    10. java.nio.channels.Selector的相关方法

      1.4.2 错误停止线程的方式

  • 被弃用的方法:stop()suspend()resume()

    1. stop方法停止

      • 由下代码可看到,很有可能,代码在计算过程中,最后一部分数据没被计算进去。
      • 代码具有偶然性,可能出错,可能不会出错。
      • 可想如果发生在银行转账过程中,那么最终的金额对不上。。。这就是个大故障了。。 ```java /**
      • 错误的停止方法,用stop来停止线程,会导致线程运行一半突然停止
      • 没办法完成一个基本单位的操作。会造成脏数据等问题 *
      • @author yiren */ public class ThreadStop { public static void main(String[] args) throws InterruptedException { final Data data = new Data(); Thread thread = new Thread(() -> {
        1. while (true) {
        2. int randomInt = (int) (Math.random() * 11);
        3. int sum = 0, temp;
        4. for (int i = 1; i < data.nums.length + 1; i++) {
        5. temp = randomInt * i;
        6. sum += temp;
        7. data.nums[i-1] += temp;
        8. System.out.println("i=" + i + ", num=" + temp);
        9. try {
        10. Thread.sleep(10);
        11. } catch (InterruptedException e) {
        12. //...
        13. }
        14. }
        15. data.total -= sum;
        16. }
        }); thread.start(); Thread.sleep(931); thread.stop(); System.out.println(data);

      } } class Data{ int total = Integer.MAX_VALUE; int[] nums = new int[5];

      @Override public String toString() { int sum = 0; for (int i = 0; i < nums.length; i++) {

      1. sum += nums[i];

      } return “Data{“ +

      1. "total=" + total +
      2. ", nums=" + Arrays.toString(nums) +
      3. ", sumNums=" + sum +
      4. ", sum=" + (sum + total) +
      5. ", Integer.MAX_VALUE=" + Integer.MAX_VALUE +
      6. '}';

      } } i=5, num=40 i=1, num=7 i=2, num=14 i=3, num=21 i=4, num=28 Data{total=2147482402, nums=[90, 180, 270, 360, 415], sumNums=1315, sum=-2147483579, Integer.MAX_VALUE=2147483647}

Process finished with exit code 0

  1. 1. suspendresume
  2. - `suspend()`方法会使得目标线程停下来,但却仍然持有在这之前获得的锁定。这样一来很容造成死锁。
  3. - `resume()`方法则是用于 恢复通过调用`suspend()`方法而停止运行的线程
  4. - 这两个方法都已被废弃,所以不推荐使用。
  5. - volatile设置boolean标志位
  6. 2. 案例一:可以停止
  7. ```sql
  8. /**
  9. * 看似可行的一个用volatile关键字案例
  10. * @author yiren
  11. */
  12. public class VolatileWrong implements Runnable{
  13. private volatile boolean canceled = false;
  14. @Override
  15. public void run() {
  16. int num = 0;
  17. while (!canceled) {
  18. num++;
  19. if (num % 100 == 0) {
  20. System.out.println("num = " + num);
  21. }
  22. try {
  23. Thread.sleep(10);
  24. } catch (InterruptedException e) {
  25. //...
  26. }
  27. }
  28. }
  29. public static void main(String[] args) throws InterruptedException {
  30. VolatileWrong volatileWrong = new VolatileWrong();
  31. Thread thread = new Thread(volatileWrong);
  32. thread.start();
  33. Thread.sleep(2345);
  34. System.out.println("开始停止线程...");
  35. volatileWrong.canceled = true;
  36. }
  37. }
  1. num = 100
  2. num = 200
  3. 开始停止线程...
  4. Process finished with exit code 0
  1. 不可以停止

    1. /**
    2. * 看似可行的一个用volatile关键字案例 二
    3. * 阻塞时,volatile时无法停止线程的
    4. * 实现一个生产者很快消费者很慢的案例
    5. *
    6. * @author yiren
    7. */
    8. public class VolatileWrongCantStop {
    9. public static void main(String[] args) throws InterruptedException {
    10. BlockingQueue storage = new ArrayBlockingQueue(10);
    11. Producer producer = new Producer(storage);
    12. Thread producerThread = new Thread(producer);
    13. producerThread.start();
    14. Thread.sleep(1000);
    15. Consumer consumer = new Consumer(storage);
    16. while (consumer.needMore()) {
    17. System.out.println(consumer.storage.take() + " 被消费了");
    18. Thread.sleep(200);
    19. }
    20. System.out.println("consumer 不需要数据了");
    21. producer.canceled = true;
    22. }
    23. static class Producer implements Runnable {
    24. BlockingQueue<Integer> storage;
    25. public volatile boolean canceled = false;
    26. public Producer(BlockingQueue storage) {
    27. this.storage = storage;
    28. }
    29. @Override
    30. public void run() {
    31. int num = 0;
    32. try {
    33. while (!canceled) {
    34. num++;
    35. if (num % 100 == 0) {
    36. System.out.println("num = " + num);
    37. storage.put(num);
    38. }
    39. Thread.sleep(1);
    40. }
    41. } catch (InterruptedException e) {
    42. System.out.println("??");
    43. //...
    44. } finally {
    45. System.out.println("Provider end!");
    46. }
    47. }
    48. }
    49. static class Consumer {
    50. BlockingQueue<Integer> storage;
    51. public Consumer(BlockingQueue<Integer> storage) {
    52. this.storage = storage;
    53. }
    54. public boolean needMore() {
    55. return Math.random() < 0.9;
    56. }
    57. }
    58. }
  • volatile用于停止线程,如果遇到线程阻塞时,是无法停止线程的。如上案例二,运行过后Consumer已经发出信号停止线程,但是由于我们的BlockingQueue满了,停在了storage.put(num);方法上中,所以finally中的输出语句始终没有出现,程序也没有停止。
  • 我们可以看到上面的put方法是抛出了InterruptedException的,所以我们可以利用异常处理来实现。如下代码:

    1. /**
    2. * 看似可行的一个用volatile关键字案例 二 使用interrupted修复问题
    3. *
    4. * @author yiren
    5. */
    6. public class VolatileWrongCantStopFix {
    7. public static void main(String[] args) throws InterruptedException {
    8. BlockingQueue storage = new ArrayBlockingQueue(10);
    9. Producer producer = new Producer(storage);
    10. Thread producerThread = new Thread(producer);
    11. producerThread.start();
    12. Thread.sleep(1000);
    13. Consumer consumer = new Consumer(storage);
    14. while (consumer.needMore()) {
    15. System.out.println(consumer.storage.take() + " 被消费了");
    16. Thread.sleep(200);
    17. }
    18. System.out.println("consumer 不需要数据了");
    19. producerThread.interrupt();
    20. }
    21. static class Producer implements Runnable {
    22. BlockingQueue<Integer> storage;
    23. public Producer(BlockingQueue storage) {
    24. this.storage = storage;
    25. }
    26. @Override
    27. public void run() {
    28. int num = 0;
    29. try {
    30. while (true) {
    31. num++;
    32. if (num % 100 == 0) {
    33. System.out.println("num = " + num);
    34. storage.put(num);
    35. }
    36. Thread.sleep(1);
    37. }
    38. } catch (InterruptedException e) {
    39. System.out.println("interrupt !!!");
    40. //...
    41. } finally {
    42. System.out.println("Provider end!");
    43. }
    44. }
    45. }
    46. static class Consumer {
    47. BlockingQueue<Integer> storage;
    48. public Consumer(BlockingQueue<Integer> storage) {
    49. this.storage = storage;
    50. }
    51. public boolean needMore() {
    52. return Math.random() < 0.9;
    53. }
    54. }
    55. }

1.4.3 关键方法源码

  • interrupt()方法

    • 该方法很简单,里面并没有直接处理中断的代码,而是调用了native方法interrupt0()
    • interrupt0()它在JVM中实际是调用系统的方法

      1. public void interrupt() {
      2. if (this != Thread.currentThread())
      3. checkAccess();
      4. synchronized (blockerLock) {
      5. Interruptible b = blocker;
      6. if (b != null) {
      7. interrupt0(); // Just to set the interrupt flag
      8. b.interrupt(this);
      9. return;
      10. }
      11. }
      12. interrupt0();
      13. }
      14. private native void interrupt0();
  • isInterruped()方法

    • 该方法返回中断状态,并清除中断,设置为false

      1. public boolean isInterrupted() {
      2. return isInterrupted(false);
      3. }
      4. private native boolean isInterrupted(boolean ClearInterrupted);
  • Thread#interrupted()

    • 它是唯一一个清除中断的方法。
      1. public static boolean interrupted() {
      2. return currentThread().isInterrupted(true);
      3. }

      1.5 子线程异常处理 UncaughtException

  • 未捕获异常处理UncaughtException使用UncaughtExceptionHandler处理

  1. 为什么要使用UncaughtExceptionHandler来处理?
    • 主线程可以轻松发现异常,而子线程却不行 ```java /**
    • 单线程抛出处理有异常堆栈
    • 而多线程,子线程发生异常有什么不同? *
    • @author yiren */ public class ExceptionInChild { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> {
      1. throw new RuntimeException();
      }); thread.start(); for (int i = 0; i < 5; i++) {
      1. System.out.println(Thread.currentThread().getName() + ": " +i);
      } } } Exception in thread “Thread-0” java.lang.RuntimeException at com.imyiren.concurrency.thread.uncaughtexception.ExceptionInChild.lambda$main$0(ExceptionInChild.java:14) at java.lang.Thread.run(Thread.java:748) main: 0 main: 1 main: 2 main: 3 main: 4

Process finished with exit code 0

  1. - 由上可看出,子线程报错,丝毫不印象主线程的执行。
  2. - 子线程的异常无法用传统的方法捕获

/**

    1. 不加try-catch 抛出四个异常
    1. 加了try-catch 期望捕获第一个线程的异常,线程234应该不运行,希望看到CaughtException
    1. 执行时发现,根本没有CaughtException,线程234依旧运行并抛出异常 *
  • @author yiren */ public class CantCatchDirectly { public static void main(String[] args) throws InterruptedException {

    1. Runnable runnable = () -> {
    2. throw new RuntimeException();};
    3. Thread thread1 = new Thread(runnable);
    4. Thread thread2 = new Thread(runnable);
    5. Thread thread3 = new Thread(runnable);
    6. Thread thread4 = new Thread(runnable);
    7. try {
    8. thread1.start();
    9. Thread.sleep(200);
    10. thread2.start();
    11. Thread.sleep(200);
    12. thread3.start();
    13. Thread.sleep(200);
    14. thread4.start();
    15. } catch (RuntimeException e) {
    16. System.out.println("caught exception");
    17. }

    } } Exception in thread “Thread-0” java.lang.RuntimeException at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15) at java.lang.Thread.run(Thread.java:748) Exception in thread “Thread-1” java.lang.RuntimeException at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15) at java.lang.Thread.run(Thread.java:748) Exception in thread “Thread-2” java.lang.RuntimeException at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15) at java.lang.Thread.run(Thread.java:748) Exception in thread “Thread-3” java.lang.RuntimeException at com.imyiren.concurrency.thread.uncaughtexception.CantCatchDirectly.lambda$main$0(CantCatchDirectly.java:15) at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

  1. - 如上,无法用传统的方法来捕获异常信息。
  2. - `try-catch`是针对主线程的,而不是针对子线程的。`throw new RuntimeException()`是运行在子线程的。
  3. 2. 解决上面的主线程无法捕获的问题:
  4. - 方案一(不推荐):在`run()`方法中进行`try-catch`
  5. ```sql
  6. /**
  7. * 方案一:在run方法中try-catch
  8. * @author yiren
  9. */
  10. public class CatchExceptionInRun {
  11. public static void main(String[] args) {
  12. new Thread(() -> {
  13. try {
  14. throw new RuntimeException();
  15. } catch (RuntimeException e) {
  16. System.out.println("Caught Exception ...");
  17. }
  18. }).start();
  19. }
  20. }
  1. Caught Exception ...
  2. Process finished with exit code 0
  • 方案二:利用UncaughtExceptionHandler接口处理

    • 先看下线程异常处理器的调用策略 在ThreadGroup类中
    • 它会检查是否有父线程,如果父线程不为空就一直向上找到最顶层。
    • 如果没有,那就尝试获取默认的异常处理器。如果取到的实现不为空,那就调用实现的处理方式,如果为空那就打印异常堆栈信息。
    • 从上面的案例可知 没有实现的时候是直接打印异常堆栈。
      1. public void uncaughtException(Thread t, Throwable e) {
      2. if (parent != null) {
      3. parent.uncaughtException(t, e);
      4. } else {
      5. Thread.UncaughtExceptionHandler ueh =
      6. Thread.getDefaultUncaughtExceptionHandler();
      7. if (ueh != null) {
      8. ueh.uncaughtException(t, e);
      9. } else if (!(e instanceof ThreadDeath)) {
      10. System.err.print("Exception in thread \""
      11. + t.getName() + "\" ");
      12. e.printStackTrace(System.err);
      13. }
      14. }
      15. }
    1. 给程序统一设置

      • 首先自定义一个Handler ```java /**
      • 自定义异常Handler
      • @author yiren */ public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

      private String name;

      public MyUncaughtExceptionHandler(String name) { this.name = name; }

      @Override public void uncaughtException(Thread t, Throwable e) { Logger logger = Logger.getAnonymousLogger(); logger.log(Level.WARNING, name + “caught thread exception : “ + t.getName()); } }

      1. - 然后设置默认处理器
      2. ```java
      3. /**
      4. * 使用自定义的handler
      5. * @author yiren
      6. */
      7. public class CatchByOwnUncaughtExceptionHandler {
      8. public static void main(String[] args) throws InterruptedException {
      9. Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("catch-handler"));
      10. Runnable runnable = () -> {
      11. throw new RuntimeException();
      12. };
      13. Thread thread1 = new Thread(runnable);
      14. Thread thread2 = new Thread(runnable);
      15. thread1.start();
      16. Thread.sleep(200);
      17. thread2.start();
      18. }
      19. }

      ``` 二月 12, 2020 2:09:20 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException 警告: catch-handlercaught thread exception : Thread-0 二月 12, 2020 2:09:20 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException 警告: catch-handlercaught thread exception : Thread-1 二月 12, 2020 2:09:21 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException 警告: catch-handlercaught thread exception : Thread-2 二月 12, 2020 2:09:21 下午 com.imyiren.concurrency.thread.uncaughtexception.MyUncaughtExceptionHandler uncaughtException 警告: catch-handlercaught thread exception : Thread-3

Process finished with exit code 0

  1. - 如上可以看到 线程异常处理是使用我们自定义的处理器。
  2. 1. 可以给每个线程单独设置
  3. - 可以通过`thread.setUncaughtExceptionHandler(handler)`设置
  4. 2. 给线程池设置
  5. - 可以通过`ThreadPoolExecutor`来处理
  6. <a name="B3Mop"></a>
  7. ### 1.4 yield方法详解
  8. - 释放当前CPU占用,状态依旧是RUNNABLE
  9. - JVM不保证遵循yield,如CPU资源不紧张,极端点没有线程使用,即使调用yield也有可能不释放CPU资源
  10. - sleep的区别:是否可以随时再次被调度
  11. <a name="WkR4m"></a>
  12. ### 1.5 `Thread.currentThread()方法`
  13. - 主要是返回当前线程的引用。
  14. ```sql
  15. /**
  16. * 打印main thread-0 thread-1
  17. * @author yiren
  18. */
  19. public class CurrentThread {
  20. public static void main(String[] args) {
  21. Runnable runnable = () -> System.out.println(Thread.currentThread().getName());
  22. // 主线程直接调用函数
  23. runnable.run();
  24. new Thread(runnable).start();
  25. new Thread(runnable).start();
  26. }
  27. }

2. Object

2.1. wait,notify,notifyAll方法详解

2.1.1 wait-notify用法

  • 我们创建两个线程类,用一个object对象加锁,然后一个线程调用object.wati(),另一个调用object.notify(),且wait先执行。
  • 先看一段代码和结果
  1. /**
  2. * wait和notify的基本用法
  3. * 1. 代码的执行顺序
  4. * 2. wait释放锁
  5. *
  6. * @author yiren
  7. */
  8. public class Wait {
  9. private final static Object object = new Object();
  10. static class ThreadOne extends Thread {
  11. @Override
  12. public void run() {
  13. synchronized (object) {
  14. try {
  15. System.out.println(Thread.currentThread().getName() + " in run before wait");
  16. object.wait();
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. System.out.println(Thread.currentThread().getName() + " in run after wait");
  21. }
  22. }
  23. }
  24. static class ThreadTwo extends Thread {
  25. @Override
  26. public void run() {
  27. synchronized (object) {
  28. System.out.println(Thread.currentThread().getName() + " in run before notify");
  29. object.notify();
  30. System.out.println(Thread.currentThread().getName() + " in run after notify");
  31. }
  32. }
  33. }
  34. public static void main(String[] args) throws InterruptedException {
  35. ThreadOne threadOne = new ThreadOne();
  36. ThreadTwo threadTwo = new ThreadTwo();
  37. threadOne.start();
  38. Thread.sleep(100);
  39. threadTwo.start();
  40. }
  41. }
  1. Thread-0 in run before wait
  2. Thread-1 in run before notify
  3. Thread-1 in run after notify
  4. Thread-0 in run after wait
  5. Process finished with exit code 0
  • 执行顺序如上结果,执行解释如下
  • Thread-0先进入执行,然后wait()进入等待唤醒的WAITING状态,并释放锁
  • Thread-1后进入执行,发现加锁了,然后等待Thread-0释放锁过后调用notify()通知Thread-0不用等了,不过此时由于Thread-1持有了object的锁,所以Thread-1先执行完毕后释放锁,然后Thread-0再拿到锁,把wait()后面的代码执行完毕。

2.1.2 wait-notifyAll 用法

  • 创建三个线程,两个线程wait,然后用第三个线程调用notifyAll唤醒
  • 代码和即如果如下
  1. /**
  2. * 三个线程 2个被wait阻塞,另一个来唤醒他们
  3. *
  4. * @author yiren
  5. */
  6. public class WaitNotifyAll implements Runnable {
  7. private static final Object objectOne = new Object();
  8. @Override
  9. public void run() {
  10. synchronized (objectOne) {
  11. Thread currentThread = Thread.currentThread();
  12. System.out.println(currentThread.getName() + " in run before wait, state is " + currentThread.getState());
  13. try {
  14. objectOne.wait();
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. System.out.println(currentThread.getName() + " in run after wait, state is " + currentThread.getState());
  19. }
  20. }
  21. public static void main(String[] args) throws InterruptedException {
  22. WaitNotifyAll waitNotifyAll = new WaitNotifyAll();
  23. Thread threadOne = new Thread(waitNotifyAll,"thread-one");
  24. Thread threadTwo = new Thread(waitNotifyAll,"thread-two");
  25. Thread threadThree = new Thread(() -> {
  26. synchronized (objectOne) {
  27. Thread currentThread = Thread.currentThread();
  28. System.out.println(currentThread.getName() + " in run before notifyAll, state is " + currentThread.getState());
  29. objectOne.notifyAll();
  30. System.out.println(currentThread.getName() + " in run after notifyAll, state is " + currentThread.getState());
  31. }
  32. }, "thread-three");
  33. threadOne.start();
  34. threadTwo.start();
  35. Thread.sleep(200);
  36. threadThree.start();
  37. }
  38. }
  1. thread-one in run before wait, state is RUNNABLE
  2. thread-two in run before wait, state is RUNNABLE
  3. thread-three in run before notifyAll, state is RUNNABLE
  4. thread-three in run after notifyAll, state is RUNNABLE
  5. thread-two in run after wait, state is RUNNABLE
  6. thread-one in run after wait, state is RUNNABLE
  7. Process finished with exit code 0
  • 线程1和2分别先后进入到WAITING状态后释放锁,
  • 线程3进入run方法后调用notifyAll唤醒,执行完毕run方法 释放锁,线程1和2抢占锁然后执行wait方法后面的代码。

    2.1.3 wait释放锁

  • 我们在使用wait的时候,它只会释放它的那把锁,代码入下:

  1. /**
  2. * 证明wait 只释放当前的那把锁
  3. *
  4. * @author yiren
  5. */
  6. public class WaitNotifyReleaseOwnMonitor {
  7. private static final Object objectOne = new Object();
  8. private static final Object objectTwo = new Object();
  9. public static void main(String[] args) throws InterruptedException {
  10. Thread threadOne = new Thread(() -> {
  11. synchronized (objectOne) {
  12. System.out.println(Thread.currentThread().getName() + " got objectOne lock ");
  13. synchronized (objectTwo) {
  14. System.out.println(Thread.currentThread().getName() + " got objectTwo lock ");
  15. try {
  16. System.out.println(Thread.currentThread().getName() + " release objectOne lock ");
  17. objectOne.wait();
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }
  23. });
  24. Thread threadTwo = new Thread(() -> {
  25. try {
  26. Thread.sleep(1000);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. synchronized (objectOne) {
  31. System.out.println(Thread.currentThread().getName() + " got lock objectOne");
  32. synchronized (objectTwo) {
  33. System.out.println(Thread.currentThread().getName() + " got lock objectTwo");
  34. }
  35. }
  36. });
  37. threadOne.start();
  38. threadTwo.start();
  39. }
  40. }
  1. Thread-0 got objectOne lock
  2. Thread-0 got objectTwo lock
  3. Thread-0 release objectOne lock
  4. Thread-1 got lock objectOne
  • 注意上面的运行并没有结束。因为两个线程都还没有执行完毕。

2.1.4 wait、notify、notifyAll特点和性质

  • 使用时必须先拥有monitor,也就是获取到这个对象的锁
  • notify只唤醒一个,取决于JVM。notifyAll则是唤醒全部。
  • 都是数据Object的对象的方法,且都是final修饰的native方法。
  • 类似功能的有一个Condition对象
  • 如果线程同时持有多把锁一定要注意释放顺序,不然容易产生死锁。

2.1.5 生产者消费者模式实现

  1. /**
  2. * 用wait notify实现生产者消费者模式
  3. * @author yiren
  4. */
  5. public class ProducerConsumer {
  6. public static void main(String[] args) {
  7. EventStorage storage = new EventStorage();
  8. Thread producerThread = new Thread(new Producer(storage));
  9. Thread consumerThread = new Thread(new Consumer(storage));
  10. producerThread.start();
  11. consumerThread.start();
  12. }
  13. private static class Producer implements Runnable{
  14. EventStorage storage;
  15. public Producer(EventStorage storage) {
  16. this.storage = storage;
  17. }
  18. @Override
  19. public void run() {
  20. for (int i = 0; i < 100; i++) {
  21. storage.put();
  22. }
  23. }
  24. }
  25. private static class Consumer implements Runnable{
  26. EventStorage storage;
  27. public Consumer(EventStorage storage) {
  28. this.storage = storage;
  29. }
  30. @Override
  31. public void run() {
  32. for (int i = 0; i < 100; i++) {
  33. storage.take();
  34. }
  35. }
  36. }
  37. private static class EventStorage {
  38. private int maxSize;
  39. private LinkedList<LocalDateTime> storage;
  40. public EventStorage() {
  41. maxSize = 10;
  42. storage = new LinkedList<>();
  43. }
  44. public synchronized void put() {
  45. while (storage.size() == maxSize) {
  46. try {
  47. wait();
  48. } catch (InterruptedException e) {
  49. e.printStackTrace();
  50. }
  51. }
  52. storage.add(LocalDateTime.now());
  53. System.out.println("storage has " + storage.size() + " product(s).");
  54. notify();
  55. }
  56. public synchronized void take() {
  57. while (storage.size() == 0) {
  58. try {
  59. wait();
  60. } catch (InterruptedException e) {
  61. e.printStackTrace();
  62. }
  63. }
  64. System.out.println("get date " + storage.poll() + ", storage has " + storage.size() + " product(s).");
  65. notify();
  66. }
  67. }
  68. }
  1. storage has 1 product(s).
  2. storage has 2 product(s).
  3. storage has 3 product(s).
  4. storage has 4 product(s).
  5. storage has 5 product(s).
  6. storage has 6 product(s).
  7. storage has 7 product(s).
  8. storage has 8 product(s).
  9. storage has 9 product(s).
  10. storage has 10 product(s).
  11. get date 2020-02-11T15:46:43.554, storage has 9 product(s).
  12. get date 2020-02-11T15:46:43.554, storage has 8 product(s).
  13. get date 2020-02-11T15:46:43.554, storage has 7 product(s).
  14. get date 2020-02-11T15:46:43.554, storage has 6 product(s).
  15. get date 2020-02-11T15:46:43.554, storage has 5 product(s).
  16. get date 2020-02-11T15:46:43.554, storage has 4 product(s).
  17. get date 2020-02-11T15:46:43.554, storage has 3 product(s).
  18. get date 2020-02-11T15:46:43.554, storage has 2 product(s).
  19. get date 2020-02-11T15:46:43.554, storage has 1 product(s).
  20. get date 2020-02-11T15:46:43.555, storage has 0 product(s).
  21. storage has 1 product(s).
  22. storage has 2 product(s).
  23. storage has 3 product(s).
  24. storage has 4 product(s).
  25. get date 2020-02-11T15:46:43.555, storage has 3 product(s).
  26. get date 2020-02-11T15:46:43.555, storage has 2 product(s).
  27. get date 2020-02-11T15:46:43.555, storage has 1 product(s).
  28. get date 2020-02-11T15:46:43.555, storage has 0 product(s).

2.2. Sleep方法详解

  • 让线程在预期的时间执行,其他事件不要占用CPU资源
  • wait()会释放锁,但是sleep()方法不释放锁,包括synchronizedlock

2.2.1 sleep不释放锁

  1. synchronized

    1. /**
    2. * sleep不释放锁
    3. * @author yiren
    4. */
    5. public class SleepDontReleaseMonitor {
    6. public static void main(String[] args) {
    7. final Object object = new Object();
    8. Runnable runnable = new Runnable() {
    9. @Override
    10. public void run() {
    11. synchronized (object) {
    12. System.out.println(Thread.currentThread().getName() + " into synchronized !");
    13. try {
    14. Thread.sleep(3000);
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. System.out.println(Thread.currentThread().getName() + " out to synchronized !");
    19. }
    20. }
    21. };
    22. Thread thread1 = new Thread(runnable);
    23. Thread thread2 = new Thread(runnable);
    24. thread1.start();
    25. thread2.start();
    26. }
    27. }
  1. Thread-0 into synchronized !
  2. Thread-0 out to synchronized !
  3. Thread-1 into synchronized !
  4. Thread-1 out to synchronized !
  5. Process finished with exit code 0
  1. Lock
  1. public class SleepDontReleaseLock {
  2. private static final Lock LOCK = new ReentrantLock();
  3. public static void main(String[] args) {
  4. Runnable runnable = new Runnable() {
  5. @Override
  6. public void run() {
  7. LOCK.lock();
  8. try {
  9. System.out.println(Thread.currentThread().getName() + " into LOCK !");
  10. Thread.sleep(3000);
  11. System.out.println(Thread.currentThread().getName() + " out to LOCK !");
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. } finally {
  15. LOCK.unlock();
  16. }
  17. }
  18. };
  19. Thread thread1 = new Thread(runnable);
  20. Thread thread2 = new Thread(runnable);
  21. thread1.start();
  22. thread2.start();
  23. }
  24. }
  1. Thread-0 into LOCK !
  2. Thread-0 out to LOCK !
  3. Thread-1 into LOCK !
  4. Thread-1 out to LOCK !
  5. Process finished with exit code 0

2.2.2 响应中断

  • 在调用时,会抛出InterruptedException,并且清除中断状态
  1. /**
  2. * sleep响应中断案例
  3. * Thread.sleep()
  4. * TimeUnit.SECONDS.sleep()
  5. *
  6. * @author yiren
  7. */
  8. public class SleepInterrupted {
  9. public static void main(String[] args) throws InterruptedException {
  10. Thread thread = new Thread(() -> {
  11. for (int i = 0; i < 10; i++) {
  12. System.out.println(Thread.currentThread().getName() + ": " + LocalDateTime.now());
  13. try {
  14. TimeUnit.SECONDS.sleep(1);
  15. } catch (InterruptedException e) {
  16. System.out.println(Thread.currentThread().getName() + ": was interrupted!");
  17. }
  18. }
  19. });
  20. thread.start();
  21. Thread.sleep(3500);
  22. thread.interrupt();
  23. }
  24. }
  • sleep(time)可以通过TimeUnit.时间单位.sleep(time)调用;此方法优于Thread.sleep(time)我们可以看下它的源码,它做了一个大于零的判断,以免传入负数,而Thread.sleep(time)中如果传入负数则会报IllegalArgumentException错误。
    1. public void sleep(long timeout) throws InterruptedException {
    2. if (timeout > 0) {
    3. long ms = toMillis(timeout);
    4. int ns = excessNanos(timeout, ms);
    5. Thread.sleep(ms, ns);
    6. }
    7. }

2.2.3 一句话总结

  • sleep(time)方法可以让线程进入到WAITING状态,并停止占用CPU资源,但是不释放锁,直到规定事件后再执行,休眠期间如果被中断,会抛出异常并清除中断状态。

    2.3. join方法详解

    2.3.1 作用及用法

  • 作用:新线程加入,所以要等待它执行完再出发

  • 用法:main等待thread1、thread2等线程执行完毕
  1. 普通用法
    1. /**
    2. * 普通用法
    3. * @author yiren
    4. */
    5. public class JoinSimple {
    6. public static void main(String[] args) throws InterruptedException {
    7. Runnable runnable = () -> {
    8. try {
    9. TimeUnit.SECONDS.sleep(1);
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. }
    13. System.out.println(Thread.currentThread().getName() + " was finished!");
    14. };
    15. Thread thread1 = new Thread(runnable);
    16. Thread thread2 = new Thread(runnable);
    17. thread1.start();
    18. thread2.start();
    19. System.out.println("start to wait child threads.");
    20. thread1.join();
    21. thread2.join();
    22. System.out.println("all threads run completed!");
    23. }
    24. }
    ``` start to wait child threads. Thread-0 was finished! Thread-1 was finished! all threads run completed!

Process finished with exit code 0

  1. - 如果两个线程不join的话就会先打印最后一句话。
  2. 2. 中断
  3. - `thread.join()`响应的中断是执行join方法的这个线程的中断 而不是thread,就如下方代码,中断的是主线程。
  4. ```java
  5. /**
  6. * 响应中断
  7. * @author yiren
  8. */
  9. public class JoinInterrupt {
  10. public static void main(String[] args) {
  11. final Thread mainThread = Thread.currentThread();
  12. Runnable runnable = () -> {
  13. try {
  14. mainThread.interrupt();
  15. TimeUnit.SECONDS.sleep(5);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. System.out.println(Thread.currentThread().getName() + " was finished!");
  20. };
  21. Thread thread1 = new Thread(runnable);
  22. thread1.start();
  23. System.out.println("start to wait child thread.");
  24. try {
  25. thread1.join();
  26. } catch (InterruptedException e) {
  27. // 实际是主线程中断
  28. System.out.println(Thread.currentThread().getName() + " was interrupted!");
  29. e.printStackTrace();
  30. }
  31. }
  32. }
  1. start to wait child thread.
  2. main was interrupted!
  3. java.lang.InterruptedException
  4. at java.lang.Object.wait(Native Method)
  5. at java.lang.Thread.join(Thread.java:1252)
  6. at java.lang.Thread.join(Thread.java:1326)
  7. at com.imyiren.concurrency.thread.method.JoinInterrupt.main(JoinInterrupt.java:25)
  8. Thread-0 was finished!
  9. Process finished with exit code 0
  1. join期间线程状态
    1. /**
    2. * join发生后 主线程的状态
    3. * @author yiren
    4. */
    5. public class JoinState {
    6. public static void main(String[] args) {
    7. Thread mainThread = Thread.currentThread();
    8. Thread thread = new Thread(() -> {
    9. try {
    10. TimeUnit.SECONDS.sleep(3);
    11. System.out.println("main thread state: " + mainThread.getState());
    12. System.out.println(Thread.currentThread().getName() + " finished");
    13. } catch (InterruptedException e) {
    14. e.printStackTrace();
    15. }
    16. });
    17. thread.start();
    18. try {
    19. System.out.println("waiting child thread");
    20. thread.join();
    21. System.out.println("completed child thread");
    22. } catch (InterruptedException e) {
    23. e.printStackTrace();
    24. }
    25. }
    26. }
    ```sql waiting child thread main thread state: WAITING Thread-0 finished completed child thread

Process finished with exit code 0

  1. <a name="kFZgV"></a>
  2. #### 2.3.2 join源码分析
  3. ```java
  4. public final void join() throws InterruptedException {
  5. join(0);
  6. }
  7. public final synchronized void join(long millis) throws InterruptedException {
  8. long base = System.currentTimeMillis();
  9. long now = 0;
  10. if (millis < 0) {
  11. throw new IllegalArgumentException("timeout value is negative");
  12. }
  13. if (millis == 0) {
  14. while (isAlive()) {
  15. wait(0);
  16. }
  17. } else {
  18. while (isAlive()) {
  19. long delay = millis - now;
  20. if (delay <= 0) {
  21. break;
  22. }
  23. wait(delay);
  24. now = System.currentTimeMillis() - base;
  25. }
  26. }
  27. }
  • join(0)是代表无限期等待
  • 它的根本方法就是调用的wait(time)
  • 但是没有notify?其实是JVM的Thread执行完毕会自动执行一次notifyAll。
  • 既然知道它是通过wait-notify实现的,那么我们可以写一下等价的代码:
  1. /**
  2. * 自己实现 等价代码
  3. * @author yiren
  4. */
  5. public class JoinImplements {
  6. public static void main(String[] args) throws InterruptedException {
  7. Runnable runnable = () -> {
  8. try {
  9. TimeUnit.SECONDS.sleep(1);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println(Thread.currentThread().getName() + " was finished!");
  14. };
  15. Thread thread1 = new Thread(runnable);
  16. thread1.start();
  17. System.out.println("start to wait child threads.");
  18. // thread1.join(); // 等价于下方代码
  19. synchronized (thread1) {
  20. thread1.wait();
  21. }
  22. System.out.println("all threads run completed!");
  23. }
  24. }

2.3.3 常见面试题

  • 在join期间线程会处于那种状态?

    • WAITING

      3. 面试常见问题

      3.1 Thread相关面试题

  • 什么时候我们需要设置守护线程?

  • 我们应该如何应用线程优先级来帮助程序运行?有哪些禁忌?
  • 不同的操作系统如何处理优先级问题?

  • 一个线程两次调用start方法会出现什么情况?为什么?

  • start方法会间接调用run方法,为什么我们不直接调用run方法?
  • 为什么线程通信的方法wait(),notify()和notifyAl()被定义在Object类里面?而sleep却定义在Thread类中
  • 用三种方法实现生产者模式
  • join和sleep和wait期间线程的状态分别是什么?为什么?

  • Java异常体系

  • 实际工作中,如何处理全局异常?为什么要处理全局异常?不处理行不行?
  • run方法是否可以抛出异常?如果抛出异常,线程状态会怎么样?
  • 线程中如何处理某个未处理异常?

    3.2 Object相关面试题

  • 两个线程交替打印0-100的奇偶数

  • 手写生产者消费者设计模式 (前面有代码)
  • 为什么wait()需要在同步代码块中实现,而sleep不需要
    1. wait设计是针对多个线程的,如果多个线程运行的时候,在执行wait前就切换到了另外一个线程,恰好把notify执行掉了,那么就会形成死锁。
    2. 而sleep则是针对单个线程本身,不涉及到其他线程。
  • 为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里面?而Sleep定义在Thread类里面?
    1. wait(),notify(),notifyAll()属于锁级别的操作,而锁一般是针对某个对象的,所以就定义在了Object中。每一个对象,在对象头中,都是有几位来表示当前锁的状态的,所以这个锁是绑定到某个对象上面,而并不是线程中。
    2. 如果把这些方法放在了Thread中,那么如果一个线程中持有了多把锁,就没有办法灵活的实现这样的多锁逻辑,也会增加编程难度。
  • wait()方法属于Object对象,那如果调用Thread.wait方法会怎样?
  • 对于Thread类特别特殊,因为在JVM中,线程在退出的现实中,它会自己去执行notify,这样会使我们设计的程序受到干扰。
  • 如何选择用notify和notifyAll
    • 主要考虑是我们需要唤醒的是单个线程还是多个线程
  • notifyAll之后所有的线程都会再次抢夺锁,如果某线程抢夺锁失败怎么办?
    • notifyAll线程执行完同步块中代码后,其他线程会同时竞争这把锁,只有一个线程会竞争成功,其他线程会进入到WAITING状态,等待竞争成功的这个线程执行结束再次竞争锁
  • 能不能用suspend()和resume()来阻塞线程?为什么?
    • Java官方是不推荐使用suspend来阻塞线程的,并且两个方法以及注明了过时,推荐使用wait-notify来实现
  • wait/notify、sleep异同
    • 思路:方法属于哪个对象?线程状态怎么切换。
    • 相同:都进入阻塞,都响应中断
    • 不同:wait/notify需要同步块,sleep不需要;wait释放锁,sleep不释放;wait可不指定时间,sleep必须指定;所属类不同
  • 线程有几种状态、生命周期是什么?

    3.x 较长的答案

  • 两个线程交替打印0-100的奇偶数

    1. 用synchronized来实现

      1. /**
      2. * 两个线程交替打印0-100
      3. * @author yiren
      4. */
      5. public class OddEvenBySync {
      6. /*
      7. 两个线程
      8. 1. 一个处理偶数(Even),另一个处理奇数(Odd) 用位运算来实现判断
      9. 2. 用synchronized 来实现
      10. */
      11. private static volatile int count = 0;
      12. private static final Object lock = new Object();
      13. public static void main(String[] args) {
      14. Thread threadEven = new Thread(() -> {
      15. while (count < 100) {
      16. synchronized (lock) {
      17. // if (count % 2 == 0) {
      18. if (0 == (count & 1)) {
      19. System.out.println(Thread.currentThread().getName() + ": " + count++);
      20. }
      21. }
      22. }
      23. }, "thread-even");
      24. Thread threadOdd = new Thread(() -> {
      25. while (count < 100) {
      26. synchronized (lock) {
      27. // if (count % 2 == 0) {
      28. if (1 == (count & 1)) {
      29. System.out.println(Thread.currentThread().getName() + ": " + count++);
      30. }
      31. }
      32. }
      33. }, "thread-odd");
      34. threadEven.start();
      35. threadOdd.start();
      36. }
      37. }
    2. 用wait-notify实现

      1. /**
      2. * 使用wait-notify 实现奇偶打印
      3. * @author yiren
      4. */
      5. public class OddEvenByWaitNotify {
      6. private static final Object lock = new Object();
      7. private static int count = 0;
      8. private static final int MAX_COUNT = 100;
      9. public static void main(String[] args) {
      10. Runnable runnable = new Runnable() {
      11. @Override
      12. public void run() {
      13. while (count <= MAX_COUNT ) {
      14. synchronized (lock) {
      15. try {
      16. System.out.println(Thread.currentThread().getName() + ": " + count++);
      17. lock.notify();
      18. // 如果任务还没结束 就让出锁 自己休眠
      19. lock.wait();
      20. } catch (InterruptedException e) {
      21. e.printStackTrace();
      22. }
      23. }
      24. }
      25. }
      26. };
      27. Thread thread1 = new Thread(runnable);
      28. Thread thread2 = new Thread(runnable);
      29. thread1.start();
      30. thread2.start();
      31. }
      32. }