在java中,可以通过4种方式让线程进入休眠状态,分别是 Thread.sleep、Object.wait、condition.await、LockSupport.park,今天就来研究这几个方法的区别。
Thread.sleep
- Thread.sleep有两个重载的方法,虽然sleep(long millis, int nanos) 看起来实现了微秒级别,但进入源码查看,只是 (nanos >= 500000 || (nanos != 0 && millis == 0)) 条件成立进行了 millis++。
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
- Thread.sleep 不会释放线程的锁
public class ThreadSleepTest {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread t1 = new Thread(() -> {
synchronized (o) {
try {
System.out.println("我进入了睡眠10秒阶段");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我也是可以执行的代码");
}
}, "t1");
t1.start();
Thread.sleep(100);
System.out.println("模拟sleep的异常 InterruptedException");
t1.interrupt();
synchronized (o) {
System.out.println("虽然sleep中断了,但是我还能执行");
}
}
}
- 可以看到中断响应会释放锁对象,同时抛出 InterruptedException 异常
Object.wait
- java的每个对象都隐式的继承了Object类,Class.class instanceof Object 输出为 true
- wait() 属于Object 定义的方法,所以每个对象都可以执行object.wait()方法也可以让线程进入休眠,wait()有3个重载方法:
// wait(0) <==> wait()
public final void wait() throws InterruptedException;
public final native void wait(long timeout) throws InterruptedException;
// 内部同样没有实现 微秒级别
public final void wait(long timeout, int nanos) throws InterruptedException;
- wait() 会释放锁对象
public class ObjectWaitTest {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
new Thread(() -> {
synchronized (obj) {
try {
System.out.println("我要睡眠10秒,别来打扰我");
// 这里如果没有设置时间,会一直等待
obj.wait(10000);
System.out.println("我是被唤醒的线程,我需要等待锁线程的释放");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(100);
synchronized (obj) {
System.out.println("我不用等到10秒就可以获取对象");
obj.notify();
Thread.sleep(10000);
System.out.println("我10秒之后释放锁,其他人先等着");
}
}
}
- 模拟 InterruptedException 异常
public class ObjectWaitTest1 {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Thread t1 = new Thread(() -> {
synchronized (obj) {
try {
System.out.println("我要睡眠10秒,别来打扰我");
// 这里如果没有设置时间,会一直等待
obj.wait(10000);
System.out.println("我是被唤醒的线程,我需要等待锁线程的释放");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
t1.start();
Thread.sleep(100);
t1.interrupt();
System.out.println("我抛出了 InterruptedException,同时也会释放锁");
synchronized (obj) {
System.out.println("我不用等到10秒就可以获取对象");
obj.notify();
Thread.sleep(10000);
System.out.println("我需要睡眠10秒后执行 --> 代码块");
}
}
}
- 响应中断会在适当的时机抛出,运行结果随机
- 要注意 wait、notify 的执行时机,若 notify 在 wait 之前先执行,会导致 wait 一直阻塞,无法唤醒
public class ObjectWaitTest2 {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
synchronized (obj) {
System.out.println("我先执行 notify");
obj.notify();
}
synchronized (obj) {
System.out.println("我再执行 wait");
obj.wait();
System.out.println("我不会执行,因为没有人来唤醒我");
}
}
}
- 可以看到,线程一直在阻塞
- notify、notifyAll 的唤醒顺序
- notify 说是随机唤醒一个线程,但实践发现一个规律,每次会唤醒第一个线程,接下来计算唤醒次数,唤醒接下来的n个等待线程,并倒序执行
- notifyAll 则直接倒序唤醒全部等待线程,从最后一个开始,一个一个来
public class ObjectWaitTest2 {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (obj) {
try {
System.out.println(Thread.currentThread().getName() + "\t ----------- 睡眠中" + " ");
obj.wait();
// Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "\t ----------- 被唤醒了" + " ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t" + i).start();
}
// 为了前面线程全执行完毕
Thread.sleep(10000);
System.out.println("========================");
synchronized (obj) {
for (int i = 0; i < 3; i++) {
obj.notify();
}
}
// 让 notify 执行完成
Thread.sleep(10000);
System.out.println("========================");
synchronized (obj) {
obj.notifyAll();
}
}
}
condition.await
-
从整体上来看 Object 的 wait 和 notify/notifyAll 是与对象监视器 Synchronized 配合完成线程间的等待/通知机制,而Condition 的 await和 signal/signalAll 是与 Lock 配合完成等待通知机制。前者是java底层级别的,后者是语言级别的,两者用法基本类似,后者具有更高的可控制性和扩展性。
-
底层也是调用 LockSupport.park、LockSupport.unpark
-
signal/signalAll 唤醒顺序:先睡眠先唤醒
- condition.await() 维护了一个条件队列, signal 方法会将条件队列的第一个节点firstWaiter 加入同步队列,signalAll() 则从第一个节点开始,循环将每一个 Node.CONDITION 放入同步队列
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionAwaitSingal {
static Object objectLock = new Object();
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t ---------进入睡眠");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ---------被唤醒");
}finally {
lock.unlock();
}
},"t" + i).start();
}
Thread.sleep(10000);
System.out.println("=======线程全部睡眠完毕=======");
// signal 先唤醒部分
lock.lock();
try {
for (int i = 0; i < 3; i++) {
condition.signal();
}
}finally {
lock.unlock();
}
Thread.sleep(10000);
System.out.println("=======signal唤醒完毕=======");
// signalAll 唤醒全部
lock.lock();
try {
condition.signalAll();
}finally {
lock.unlock();
}
}
}
- await 会响应中断,awaitUninterruptibly 忽略中断报错
public class ConditionAwaitSingal2 {
static Object objectLock = new Object();
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static Condition condition2 = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t ---------come in");
condition.await(10, TimeUnit.SECONDS);
System.out.println(Thread.currentThread().getName() + "\t ---------被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + "\t ---------中断报错");
} finally {
lock.unlock();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t ---------come in");
condition2.awaitUninterruptibly();
System.out.println(Thread.currentThread().getName() + "\t ---------被唤醒");
} finally {
lock.unlock();
}
}, "t2");
t2.start();
Thread.sleep(100);
System.out.println("===================");
t1.interrupt();
lock.lock();
try {
try {
condition.signal();
System.out.println("t1线程中断,释放了锁");
} catch (Exception e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
t2.interrupt();
lock.lock();
try {
try {
condition2.signal();
System.out.println("awaitUninterruptibly忽略中断");
} catch (Exception e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
LockSupport.park
-
LockSupport.park() 的实现原理是通过二元信号量做的阻塞。0 是阻塞,1是通行。unpark()方法会将信号量变为 1,不论执行多少次unpark(这里指凭证没有被消费),也只能变成1。
-
t1线程没有启动时,其他线程的unpark(),t1 接收不到
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 等待被唤醒");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t 我拿到了凭证,已经被唤醒");
}, "t1");
Thread t2 = new Thread(() -> {
LockSupport.unpark(t1);
}, "t2");
t2.start();
// 确保t2先执行
Thread.sleep(1000);
t1.start();
}
}
- t1 线程启动后,在睡眠等 t2 线程执行,此时 LockSupport.park(); 拿到凭证,相当于被注释了
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t 等待被唤醒" + System.currentTimeMillis());
LockSupport.park();// 此时这句代码相当于被注释
System.out.println(Thread.currentThread().getName() + "\t 我拿到了凭证,已经被唤醒" + System.currentTimeMillis());
}, "t1");
Thread t2 = new Thread(() -> {
LockSupport.unpark(t1);
}, "t2");
t2.start();
t1.start();
}
}
- 只要凭证确保被消耗后,同一线程执行多次unpark,凭证都会 +1
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
LockSupport.park();// 此时这句代码相当于被注释
System.out.println(Thread.currentThread().getName() + "\t 等待被唤醒" + System.currentTimeMillis());
LockSupport.park();// 此时这句代码相当于被注释
System.out.println(Thread.currentThread().getName() + "\t 我拿到了凭证,已经被唤醒" + System.currentTimeMillis());
}, "t1");
Thread t2 = new Thread(() -> {
LockSupport.unpark(t1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(t1);
}, "t2");
t1.start();
Thread.sleep(100);
t2.start();
}
}
- 中断响应,但没有异常
public class LockSupportTest1 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
LockSupport.park();
}, "t1");
t1.start();
t1.interrupt();
System.out.println("因为中断,我没有被阻塞");
}
}
总结
- 盗用这位老哥博客的图