多线程之wait/notify/await/signal/park/upark
简单说明
主要聊下wait、notify(notifyAll)、await、signal(signalAll)、park、unpark
这几个方法
wait/notify(notifyAll)
1.来源
首先我们知道这2个方法都是用于线程的阻塞以及唤醒,那么是不是认为这2种方法来源于Thread类呢。其实并不然。它们来自Object类。
2.用法及注意事项
2.1需配合synchronized关键字使用,铁三角。
先看下面这段代码,可以正常运行吗?
public class WaitNotifyDemo {
static Object object = new Object();
public static void main(String[] args) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "/t 开始等待");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t 被唤醒");
}, "t1").start();
}
}
答案是否定的,报错了o(╥﹏╥)o
加上syncnized关键字之后
不报错了,所以wait,notify(notifyAll)要配合synchronized关键字使用。它们是个铁三角。
配合下notify使用:
public static void main(String[] args) {
new Thread(() -> {
synchronized (object) {
try {
System.out.println(Thread.currentThread().getName() + "/t 开始等待");
object.wait();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "\t 被唤醒");
}
}, "t1").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
synchronized (object) {
object.notify();
}
}, "t2").start();
}
运行结果:先阻塞,在唤醒。
2.2 注意使用顺序
如果先notify在wait会如何,看下下面的代码
public static void main(String[] args) {
new Thread(() -> {
synchronized (object) {
object.notify();
}
}, “t2”).start();
new Thread(() -> {
synchronized (object) {
try {
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t 开始等待");
object.wait();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "\t 被唤醒");
}
}, "t1").start();
}
运行结果:
程序无法结束,阻塞在这里。
2.3.总结
由上面的例子我们可以看出:
- wait、notify(notifyAll)要配合synchronized关键字使用否则会报IllegalMonitorStateException
- 注意使用顺序先wait后notify
await/signal(signalAll)
1.来源
java.util.concurrent.locks包下的Condition接口:
2.用法及注意事项
2.1 需要配合lock/unlock使用否则 IllegalMonitorStateException
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "\t 开始等待");
condition.await();
} catch (InterruptedException e) {
} finally {
}
System.out.println(Thread.currentThread().getName() + "\t 被唤醒");
}, "t1").start();
new Thread(() -> {
condition.signal();
}, "t2").start();
}
正确代码:
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 开始等待");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println(Thread.currentThread().getName() + "\t 被唤醒");
}, "t1").start();
new Thread(() -> {
lock.lock();
try {
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t2").start();
}
注意使用顺序,
先signal在await,程序无法结束,被阻塞了
总结
- await/signal(signalAll)配合lock/unlock使用,铁三角。
- 使用顺序:先await在signal(signalAll)
park/unpark
1.来源
java.util.concurrent.locks下的 LockSupport类
阻塞原语
2.用法
2.1直接使用。
public static void main(String[] args){
new Thread(() -> {
LockSupport.unpark();
}, "t2").start();
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 开始等待");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t 被唤醒");
}, "t1");
t1.start();
new Thread(() -> {
LockSupport.unpark(t1);
}, "t2").start();
}
运行结果:
无需配合synchronized或lock,直接使用。
2.2使用顺序没有要求
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t 开始等待");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t 被唤醒");
}, "t1");
t1.start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName() + "\t 执行了unpark!!");
}
运行结果:
先执行upark,在执行park程序可以正常结束不会阻塞;
为什么先upark在park也是可以的呢,我们看下jdk1.8文档的解释:
主要原因是upark时为给定的线程提供许可证(如果尚未提供)。park方法调用时会校验当前线程有无许可证,有放行,否则禁止当前线程进行线程调度
需要注意的是许可证的数量只有一个,重复调用不会增加
调用2次unpark只会有一个许可证,第一次park消耗之后,第二次park导致线程无法调度,阻塞了。所以程序无法停止。
3.总结
- park/unpark可直接使用。
- 无先后顺序要求。重复调用是注意不会叠加许可次数。
暂时到这儿把…