目录
1. 线程通讯
1.1 方法介绍
1.1.1 wait() 使用
1.1.2 notify 使用
1.1.3 notiyAll 使用
1.1.4 注意事项
2. wait() VS wait(long timeout)
3. wait VS sleep
3.1 wait(0) 与 sleep(0) 的区别
3.2 wait 和 sleep 释放锁
3.3 wait和sleep区别(相同点&不同点)
4. 线程休眠和指定唤醒:LockSupport
4.1 park() 和 unpark()
4.2 parkUntil(long)
4.3 LockSupport VS wait
由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知,但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。举个例子:
球场上的每个运动员都是独立的 "执行流" , 可以认为是⼀个 "线程",而完成⼀个具体的进攻得分动作, 则需要多个运动员相互配合, 按照⼀定的顺序执行一定的动作,线程1 先 "传球" , 线程2 才能 "扣篮",从而得分。
完成这个协调工作(线程通讯),主要涉及到三个方法(这三个方法都需要配合 synchronized ⼀起使用):
wait 执行流程:
wait 结束等待的条件:
示例代码:
public class WaitDemo {
public static void main(String[] args) {
Object lock = new Object();
Thread t1 = new Thread(() -> {
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1调用wait方法");
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
},"线程1");
t1.start();
}
}
执行结果:
线程1调用wait方法后,线程1进行等待,并未执行后面代码。
notify 方法是唤醒等待的线程:
示例代码:
public class WaitDemo2 {
public static void main(String[] args) {
Object lock = new Object();
Thread t1 = new Thread(() -> {
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1调用wait方法");
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
},"线程1");
Thread t2 = new Thread(() -> {
System.out.println("线程2开始执行");
try {
synchronized (lock) {
System.out.println("线程2调用wait方法");
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完成");
},"线程2");
Thread t3 = new Thread(() -> {
System.out.println("线程3开始执行");
try {
synchronized (lock) {
System.out.println("线程3调用wait方法");
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3执行完成");
},"线程3");
t1.start();
t2.start();
t3.start();
// 唤醒lock对象上休眠的线程(随机一个)
Thread t4 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程4开始执行");
synchronized (lock) {
lock.notify();
System.out.println("线程4执行了唤醒操作");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程4 synchronized 执行完成");
}
},"线程4");
t4.start();
}
}
运行结果:
观察两次运行结果可以发现:在 notify() 方法后,当前线程不会马上释放该对象锁,要等到执行 notify() 方法的线程将程序执行完才会释放对象锁,有三个等待的线程,每次唤醒的也是随机一个线程。
notify() 方法只是唤醒某⼀个等待线程. 使用 notifyAll() 方法可以⼀次唤醒所有的等待线程。
示例代码:
public class WaitDemo3 {
public static void main(String[] args) {
Object lock = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(() -> {
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1调用wait方法");
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
},"线程1");
Thread t2 = new Thread(() -> {
System.out.println("线程2开始执行");
try {
synchronized (lock) {
System.out.println("线程2调用wait方法");
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完成");
},"线程2");
Thread t3 = new Thread(() -> {
System.out.println("线程3开始执行");
try {
synchronized (lock2) {
System.out.println("线程3调用wait方法");
lock2.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3执行完成");
},"线程3");
t1.start();
t2.start();
t3.start();
// 唤醒lock对象上休眠的线程(随机一个)
Thread t4 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程4开始执行");
synchronized (lock) {
lock.notifyAll();
System.out.println("线程4执行了唤醒操作");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程4 synchronized 执行完成");
}
},"线程4");
t4.start();
}
}
运行结果:
可以观察到,notifyAll() 并不是唤醒所有 wait 等待的线程,而是唤醒当前对象(lock)处于 wait 等待的所有线程(线程1、线程2)。
示例代码:
public class WaitDemo4 {
public static void main(String[] args) {
Object lock = new Object();
Object lock2 = new Object();
new Thread(() -> {
System.out.println("线程1开始执行");
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
}
}, "无参wait线程").start();
new Thread(() -> {
System.out.println("线程2开始执行 " + LocalDateTime.now());
synchronized (lock2) {
try {
lock2.wait(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完成 " + LocalDateTime.now());
}
}, "有参wait线程").start();
}
}
运行结果:
结论:
public class WaitSleepDemo7 {
public static void main(String[] args) {
Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1开始执行");
try {
lock.wait(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1结束执行");
}
}, "wait(0)");
t1.start();
Thread t2 = new Thread(() -> {
System.out.println("线程2开始执行");
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2结束执行");
}, "sleep(0)");
t2.start();
}
}
运行结果:
Thread.sleep(0) 表示重新触发一次 CPU 竞争;而 wait(0) 表示无期限的等待,直到有线程唤醒它为止。
public class WaitSleepDemo8 {
public static void main(String[] args) {
Object lock = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1开始执行");
try {
lock.wait(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1结束执行");
}
}, "wait");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("线程2开始执行");
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2结束执行");
}
}, "sleep");
t2.start();
// 创建 2 个线程,先让线程休眠 1s 之后,尝试获取,看能不能获取到锁
// 如果可以获取到锁,说明休眠时线程是释放锁的,而如果获取不到锁,说明是不释放锁
Thread t3 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("尝试获取wait方法的锁");
synchronized (lock) {
System.out.println("成功获取wait方法的锁");
}
});
t3.start();
Thread t4 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("尝试获取sleep方法的锁");
synchronized (lock2) {
System.out.println("成功获取sleep方法的锁");
}
});
t4.start();
}
}
运行结果:
wait方法在执行的时候都会释放锁,而sleep不会释放锁。
相同点:
不同点:
使用 LockSupport 也可以使线程休眠和唤醒,它包含两个主要的方法:
注意:LockSupport ⽆需配和 synchronized 使用。
示例代码:
public class LockSupportDemo1 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("线程1开始执行");
LockSupport.park();
System.out.println("线程1结束执行");
});
t1.start();
Thread t2 = new Thread(() -> {
System.out.println("线程2开始执行");
LockSupport.park();
System.out.println("线程2结束执行");
});
t2.start();
Thread t3 = new Thread(() -> {
System.out.println("线程3开始执行");
LockSupport.park();
System.out.println("线程3结束执行");
});
t3.start();
Thread.sleep(1000);
System.out.println();
LockSupport.unpark(t1);
Thread.sleep(1000);
LockSupport.unpark(t2);
Thread.sleep(1000);
LockSupport.unpark(t3);
}
}
运行结果:
LockSupport.parkUntil(long) 等待最⼤时间是⼀个固定时间.
示例代码:
public class LockSupportDemo2 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("线程1开始执行:" + LocalDateTime.now());
LockSupport.parkUntil(System.currentTimeMillis() + 3000);
System.out.println("线程1结束执行:" + LocalDateTime.now());
});
t1.start();
}
}
运行结果:
休眠 3s 后,自动恢复执行。