除了 synchronized
和 volatile
这两个关键字和对象头文件中的锁标志位之外,Object
类中还有三个方法 wait(),notify()和notifyAll()
.
为什么要有这三个方法
看一下下面这段代码
public void synchronized printThreadName(Thread thread){
System.out.println("thread's name = " + thread.getName()+ "\n假装这是一个长时间的操作");
}
当三个线程同时执行这段代码,只会有一个线程能够获得执行条件,其他两个线程被阻塞,但是这两个线程一直盯着占得先机的线程释放锁,然后再争夺锁的所有权.这是一种很激烈的行为,长时间地阻塞非常耗费 cpu
的资源.所以为了防止 cpu
爆肝式的竞争锁,wait()
,notify
和 notifyAll()
这三个方法应运而生.如果锁已经被夺走了,那我们就去休息,等释放了锁再叫醒我们去争夺锁,大概就是这么一个意思.
使用
例如,我们写一个 Lock
类
public class Lock{
private boolean islocked = false;
public synchronized void lock() throws InterruptedException{
while(locked){
wait();
}
islocked = true;
}
public synchronized void unlock(){
if(!islocked)
throw new IllegalMonitorStateException();
islocked = false;
notifyAll();
}
}
这段代码只是为了演示,如果非当前线程在当前线程调用了 lock
方法之后调用了 unlock
方法肯定会出问题(请忽略).
想象一下这段代码为线程之间的行为做的限制:线程 A 和线程 B 抢占 lock()
方法,线程 A 占得先机,此时 locked
为 false
,然后将 islocked
改成 true
,退出同步代码块执行自己的逻辑,接着线程 B 抢占到锁,进行 lock()
方法,发现已经被别人锁了,开始 wait()
,一定要注意这个 wait()
,虽然是 this 对象的 wait()
方法,但是线程 B 进入了 waiting
状态.线程 A unlock
最后一行 notifyAll()
已经唤醒了线程 B,但是此时线程 A 还没有释放同步块的锁,线程 B 开始竞争锁对象,当线程 A 退出代码块之后,线程 B 竞争到锁执行后续操作.
有几个注意点,调用锁对象的 wait()
方法,挂起的是当前线程.用 while
而不用 if
的是防止虚假唤醒,当被虚假唤醒之后就继续往前走了而不会因为情况不对再回来 wait()
.
例子
其实 wait()
,notify()
很直观,但是需要考虑很多情况,比如哪个线程先进来,用哪些标志来确定是否要 wait()
,是需要细心考虑的方法.
前两天看到一道题目,感觉很适合结合抢占和 wait()
来讲.
三个线程,名称分别为 t1
,t2
和 t3
,要求三个线程启动之后,先打印 t1
,再打印 t2
或 t3
其中一个,然后再打印 t1
如此循环.如:t1 t2 t1 t3 t1 t2
.
首先来个抢占式的
public class TestWait {
public static void main(String[] args) throws InterruptedException {
final singlePrint singlePrint = new singlePrint();
new Thread(singlePrint,"t3").start();
new Thread(singlePrint,"t1").start();
new Thread(singlePrint,"t2").start();
Thread.sleep(10000);
}
}
class singlePrint implements Runnable{
boolean flag = true;
public synchronized void print(Thread t) throws InterruptedException {
if(t.getName().equals("t1")&&flag){
System.out.println("t1");
flag = false;
}else if((t.getName().equals("t2")||t.getName().equals("t3"))&&!flag){
System.out.println(t.getName());
flag = true;
}
}
@Override
public void run() {
while (true){
try {
print(Thread.currentThread());
// Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
通过 jvisualvm
来查看一下线程之间的状况
不是监视就是正在运行,三个线程之间竞争十分激烈.
来使用一下 wait()
和 notify()
,原谅我太懒了,没有把线程中的匿名内部类抽出来,因为我直接复制粘贴更快:p.
public class Test{
public static void main(String[] args) throws InterruptedException {
final SingleFlagLock s = new SingleFlagLock();
new Thread(() -> {
while (true) {
try {
s.tPrint(Thread.currentThread());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2").start();
new Thread(() -> {
while (true) {
try {
s.tPrint(Thread.currentThread());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t3").start();
new Thread(() -> {
while (true) {
try {
s.t1Print(Thread.currentThread());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
Thread.sleep(10000);
}
}
class SingleFlagLock {
private boolean t1Printed = false;
public synchronized void t1Print(Thread t) throws InterruptedException {
while (t1Printed) {
wait();
}
System.out.println(t.getName());
t1Printed = true;
Thread.sleep(1000);
notifyAll();
}
public synchronized void tPrint(Thread t) throws InterruptedException {
while (!t1Printed) {
wait();
}
System.out.println(t.getName());
t1Printed = false;
Thread.sleep(1000);
notifyAll();
}
}
再来看一下 jvisualvm
中线程之间的状态变化.
多了一个等待状态,长时间的监视红条已经不见了,线程们该吃吃该睡睡了.
到这应该对 wait()
和 notify()
有抽象和直观的理解了.文章就到此为止了,如果三个线程按照 t1 t2 t3 t1 t2 t3
这种方式循环打印你能做出来吗:p.
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于