1、wait和notify/notifyAll 限制
在没有LockSupport之前,线程的挂起和唤醒咱们都是通过Object的wait和notify/notifyAll方法实现。
- 正确使用
wait和notify/notifyAll的限制
- 必须包含在
synchronized代码块里面。 - 必须先wait,再notify才可以唤醒阻塞的线程。
正确示例:
public static void main(String[] args) throws InterruptedException {
// 定义一个对象,充当锁。
final Object obj = new Object();
Thread a = new Thread(()->{
int sum = 0;
for (int i = 0 ;i < 10 ;i ++) {
sum = sum + i;
}
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum);
}
});
a.start();
// 睡眠一秒钟,保证A线程已经计算完成。阻塞在await方法上
Thread.sleep(1000);
// wati,和notify 必须包含在 synchronized 代码块里面
synchronized (obj) {
obj.notify();
}
}
输出如下:
45
错误示例1: 不放在synchronized中进行唤醒。
public static void main(String[] args) throws InterruptedException {
// 定义一个对象,充当锁。
final Object obj = new Object();
Thread a = new Thread(()->{
int sum = 0;
for (int i = 0 ;i < 10 ;i ++) {
sum = sum + i;
}
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum);
}
});
a.start();
// 睡眠一秒钟,保证A线程已经计算完成。阻塞在await方法上
Thread.sleep(1000);
// 不放在synchronized中进行唤醒。
obj.notify();
}
输出如下:
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at demo3.main(demo3.java:27)
错误示例2: 先notify再进行wait
public static void main(String[] args) throws InterruptedException {
// 定义一个对象,充当锁。
final Object obj = new Object();
Thread a = new Thread(()->{
//保证先进行notify 再进行wait操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("notify to exit");
}
});
a.start();
// 先唤醒
synchronized (obj) {
obj.notify();
}
}
导致程序无法关闭。
2、什么是LockSupport?
- ①. 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
- ②. LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码
- ③. 官网解释:
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit只有两个值1和零,默认是零
可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1
3、阻塞方法
- ①. permit默认是0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时, park方法会被唤醒,然后会将permit再次设置为0并返回
- ②. static void park( ):底层是unsafe类native方法
- ③. static void park(Object blocker)
/**
* Disables the current thread for thread scheduling purposes unless the
* permit is available.
*
* <p>If the permit is available then it is consumed and the call
* returns immediately; otherwise the current thread becomes disabled
* for thread scheduling purposes and lies dormant until one of three
* things happens:
*
* <ul>
*
* <li>Some other thread invokes {@link #unpark unpark} with the
* current thread as the target; or
*
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
*
* <li>The call spuriously (that is, for no reason) returns.
* </ul>
*
* <p>This method does <em>not</em> report which of these caused the
* method to return. Callers should re-check the conditions which caused
* the thread to park in the first place. Callers may also determine,
* for example, the interrupt status of the thread upon return.
*/
public static void park() {
UNSAFE.park(false, 0L);
}
4、唤醒方法(注意这个permit最多只能为1)
- ①. 调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回
②. static void unpark( )
/**
* Makes available the permit for the given thread, if it
* was not already available. If the thread was blocked on
* {@code park} then it will unblock. Otherwise, its next call
* to {@code park} is guaranteed not to block. This operation
* is not guaranteed to have any effect at all if the given
* thread has not been started.
*
* @param thread the thread to unpark, or {@code null}, in which case
* this operation has no effect
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
5、LockSupport它的解决的痛点
- ①. LockSupport不用持有锁块,不用加锁,程序性能好
- ②. 先后顺序,不容易导致卡死(因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞)
- ③. 代码演示:
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t"+"coming....");
LockSupport.park();
/*
如果这里有两个LockSupport.park(),因为permit的值为1,上一行已经使用了permit
所以下一行被注释的打开会导致程序处于一直等待的状态
* */
//LockSupport.park();
System.out.println(Thread.currentThread().getName()+"\t"+"被B唤醒了");
},"A");
t1.start();
//下面代码注释是为了A线程先执行
//try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
Thread t2=new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t"+"唤醒A线程");
//有两个LockSupport.unpark(t1),由于permit的值最大为1,所以只能给park一个通行证
LockSupport.unpark(t1);
//LockSupport.unpark(t1);
},"B");
t2.start();
}
6、LockSupport 面试题目
-
①. 为什么可以先唤醒线程后阻塞线程?(因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞)
-
②. 为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?(因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行)