5、线程-几种等待 2022-02-15 19:30 > 以下基于JDK1.8 前面我们使用的`wait()`,`await()`都是让线程进入WAITING状态,通俗来说可以理解为让线程等待。我们也用了`Thread.sleep(50);`这种让线程直接睡眠50ms,也可以理解为让线程等待,那么他们之间有什么区别呢?还有没有其他“等待”形式呢?这些形式之间的关联和区分又是什么呢? ### 几种等待 1、Thread.sleep(50); 当前线程睡眠50ms 2、wait()、notify() 当前线程进入等待状态(进入WaitSet队列),并释放锁;接收唤醒的时候会从重新公平的和其他线程竞争锁,竞争不到就进入EntrySet队列。 3、Condition.await()、single() 同上 4、LockSupport.park()、unpark() 若当前没有许可可用,则进入等待状态,直至设定的时间过去,或有许可可用。 5、Thread.yield() 让出CPU执行权(从操作系统线程状态来说是由运行状态转为就绪状态),重新与其他线程争夺CPU执行。 #### 横向大对比: 1、方法类型:Thread.sleep()、Thread.yield()、LockSupport.park()都是静态方法,任何人都能用;而wait()是Object的成员方法,await()是Condition的成员方法,必须用指定的实例对象调用。 2、是否需要锁:Thread.sleep()、LockSupport.park()、Thread.yield() 不需要锁;wait()、await()需要锁(不在锁内执行会抛出IllegalMonitorStateException非法监视器状态异常)。前者由于不需要锁,所以等待时也不会释放锁;后者需要锁,等待时会释放锁,当被唤醒重新执行时,也需要重新获得锁。 3、唤醒时机:Thread.sleep()必须传入时间,到达指定时间后自动唤醒;而wait()、await()、park()都是可传可不传,不传入的话,必须有其他线程主动执行方法去唤醒。而Thread.yield()只是让出CPU执行权,CPU重新选择时还有可能选择到他来执行。 4、状态转换:Thread.sleep()由于必须传入时间,所以在执行后的等待期间,线程状态只能是TIMED_WAITING;而wait()、await()、park()这些依据是否传入时间,其状态可以是WAITING或者TIMED_WAITING。Thread.yield()由于只是让出CPU时间片,所以在java层面,其状态一直都是RUNNABLE。 5、实现方式:Condition.await()是通过java代码实现,底层使用`LockSupport.park(this);`来实现等待,所以和LockSupport.park()是一致的;而Thread.sleep()、wait()、Thread.yield() 则是直接由native方法实现。(其实LockSupport.park()底层也是native方法,是调用的Unsafe的native方法) 6、等待唤醒顺序:LockSupport.park()可以实现先unpark(),再调用park(),线程就不会等待;而wait()、await()等待唤醒顺序颠倒后,线程仍会进入等待。 7、对异常的响应:Thread.sleep()、wait()、await()本身会抛出`InterruptedException`(中断)异常;而LockSupport.park()、Thread.yield()方法本身没有异常抛出。 #### 中断处理 ##### 1、Thread.sleep()接受中断处理 thread1开启执行,输出内容后立即打算睡眠3秒,而主线程睡眠50毫秒后,立即中断thread1线程。 ```java Thread thread1 = new Thread(() -> { System.out.println("我执行了" + System.currentTimeMillis()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我结束了" + System.currentTimeMillis()); }); thread1.start(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } thread1.interrupt(); ``` 执行结果:输出下面内容后程序立即结束。可以看到,thread1并没有睡眠3秒,被唤醒后就继续执行并结束了。 ``` 我执行了1644463212874 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.example.demo.core.Contrast.wait.Demo1.lambda$main$0(Demo1.java:14) at java.lang.Thread.run(Thread.java:748) 我结束了1644463212924 ``` ##### 2、wait()接受中断处理 thread1开启执行,输出内容后抢占锁,然后wait()进入等待状态,而主线程睡眠50毫秒后,立即中断thread1线程。 ```java Object monitor = new Object(); Thread thread1 = new Thread(() -> { System.out.println("我执行了" + System.currentTimeMillis()); synchronized (monitor) { try { monitor.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我结束了" + System.currentTimeMillis()); } }); thread1.start(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } thread1.interrupt(); ``` 执行结果:输出下面内容后程序立即结束。可以看到,thread1并没有一直等待下去,而是被唤醒后立即向下执行并结束了。 ``` 我执行了1644463329584 java.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at com.example.demo.core.Contrast.wait.Demo1.lambda$main$0(Demo1.java:36) at java.lang.Thread.run(Thread.java:748) 我结束了1644463329635 ``` ##### 3、await()接受中断处理 thread1开启执行,输出内容后抢占锁,然后await()进入等待状态,而主线程睡眠50毫秒后,立即中断thread1线程。 ```java Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); Thread thread1 = new Thread(() -> { System.out.println("我执行了" + System.currentTimeMillis()); lock.lock(); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } System.out.println("我结束了" + System.currentTimeMillis()); }); thread1.start(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } thread1.interrupt(); ``` 执行结果:输出下面内容后程序立即结束。可以看到,thread1并没有一直等待下去,而是被唤醒后立即向下执行并结束了。 ``` 我执行了1644463698709 java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048) at com.example.demo.core.Contrast.wait.Demo1.lambda$main$0(Demo1.java:64) at java.lang.Thread.run(Thread.java:748) 我结束了1644463698760 ``` ##### 4、park()不接受中断但处理中断 park()时,即使外部调用了interrupt()让线程中断,线程也不会收到中断异常。但是有中断状态存在时,会使park()失效。关于“中断状态”和park()失效,可以查看之前的文章。 下述代码中,thread1开启执行,输出内容后park()住,进入等待状态;而主线程睡眠50毫秒后,立即中断thread1线程。 ```java Thread thread1 = new Thread(() -> { try { System.out.println("我执行了" + System.currentTimeMillis()); LockSupport.park(); System.out.println("我结束了" + System.currentTimeMillis()); } catch (Exception e) { e.printStackTrace(); } }); thread1.start(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } thread1.interrupt(); ``` 执行结果:输出下面内容后程序立即结束。可以看到,thread1并没有一直等待下去,而且没有抛出任何异常。 ``` 我执行了1644471780245 我结束了1644471780292 ``` #### 源码 源码大都是native的,但是这里还是贴一下吧 ##### `Thread` ```java /** * 导致当前正在执行的线程休眠指定毫秒数,线程不会失去任何监视器的所有权。 * @param millis * @throws InterruptedException */ public static native void sleep(long millis) throws InterruptedException; /** * 给调度程序的一个提示,表明当前线程愿意放弃当前对处理器的使用。调度程序可以随意忽略此提示。 */ public static native void yield(); ``` ##### `Object` ```java public final void wait() throws InterruptedException { wait(0); } /** * 使当前线程等待,直到另一个线程调用notify()/notifyAll()或指定时间已过 * @param timeout * @throws InterruptedException */ public final native void wait(long timeout) throws InterruptedException; ``` ##### `AbstractQueuedSynchronizer` ```java /** * 实现可中断条件等待。 * @throws InterruptedException */ public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) { LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); } ``` ##### `LockSupport` ```java /** * 除非许可证可用,否则出于线程调度目的禁用当前线程。 */ public static void park() { UNSAFE.park(false, 0L); } `Unsafe` public native void park(boolean var1, long var2); ``` --END--
发表评论