文章目录
1、LockSupport有什么用
一般情况下,我们们有如下3种办法去唤醒一个线程
- 使用object方法的wait()方法,让线程等待;使用object的notify()方法进行唤醒
- 使用juc包中的condition的await()方法让线程等待,使用signal()方法唤醒线程
- LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
接下来将进行一一的讲解
2、使用wait和notify唤醒一个线程
2.1、正常情况
public class InterruputDemo1 {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "----coming in");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "好的,多谢通知 ,我不等待了,");
}
}, "t1").start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (o) {
o.notify();
System.out.println("解锁了,不用等待了");
}
},"t2").start();
}
}
2.2、异常情况2 ,这里去掉了 synchronized (o) {} 代码块
public class InterruputDemo1 {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "----coming in");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "好的,多谢通知 ,我不等待了,");
}, "t1").start();
Thread.sleep(1000);
new Thread(() -> {
o.notify();
System.out.println("解锁了,不用等待了");
},"t2").start();
}
}
会出现如下的错误
t1----coming in
Exception in thread "t1" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.test.InterruputDemo1.lambda$main$0(InterruputDemo1.java:10)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "t2" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.test.InterruputDemo1.lambda$main$1(InterruputDemo1.java:21)
at java.lang.Thread.run(Thread.java:748)
结论: wait和notify 必须要配合synchronized
2.3、异常情况3 先notify再wait
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "----coming in");
try {
synchronized (o) {
o.wait();
}
System.out.println(Thread.currentThread().getName() + "好的,多谢通知 ,我不等待了,");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (o) {
o.notify();
System.out.println("解锁了,不用等待了");
}
},"t2").start();
}
最后输出的结果是
解锁了,不用等待了
t1----coming in
xxxxx 程序没有停止 ,仍在执行!!!!
总结: wait和notify必须放到同步代码块和方法里面,且成对出现, 先wait后notify才行
3、使用await和signal唤醒一个线程
3.1、正常情况
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
lock.lock();
System.out.println("我等待被唤醒");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println("被唤醒");
},"t1").start();
Thread.sleep(1000);
new Thread(()->{
lock.lock();
condition.signal();
System.out.println("你被释放了");
lock.unlock();
},"t2").start();
}
3.2、异常情况: 如果去除锁块
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
// lock.lock();
System.out.println("我等待被唤醒");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// lock.unlock();
}
System.out.println("被唤醒");
},"t1").start();
Thread.sleep(1000);
new Thread(()->{
// lock.lock();
condition.signal();
System.out.println("你被释放了");
// lock.unlock();
},"t2").start();
}
输出结果为
我等待被唤醒
Exception in thread "t1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
at com.test.InterruputDemo1.lambda$main$0(InterruputDemo1.java:17)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "t2" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at com.test.InterruputDemo1.lambda$main$1(InterruputDemo1.java:30)
at java.lang.Thread.run(Thread.java:748)
3.3、异常情况: 先执行signal后await
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
System.out.println("我等待被唤醒");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println("被唤醒");
},"t1").start();
Thread.sleep(1000);
new Thread(()->{
lock.lock();
condition.signal();
System.out.println("你被释放了");
lock.unlock();
},"t2").start();
}
输出结果
你被释放了
我等待被唤醒
xxxxx 程序没有停止 ,仍在执行!!!!
总结: Condition中的线程等待和唤醒都需要先获取锁,一定要先await,然后signal,顺序不能反
4、LockSupport的park等待和unpark唤醒
LockSupport类使用了一种名叫Permit的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可,但是与Semaphore不同,许可的累加上限是1
4.1、正常代码
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "---->com in");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "---->被唤醒了");
}, "t1");
t1.start();
Thread.sleep(1000);
new Thread(()->{
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName()+"---->我来唤醒你");
},"t2").start();
}
输出结果
t1---->com in
t2---->我来唤醒你
t1---->被唤醒了
4.2、先执行unPark 后执行park
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---->com in");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "---->被唤醒了");
}, "t1");
t1.start();
new Thread(()->{
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName()+"---->我来唤醒你");
},"t2").start();
}
输出结果
t2---->我来唤醒你
t1---->com in
t1---->被唤醒了
重要说明:LockSupportL的许可累加是1
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---->com in");
LockSupport.park();
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "---->被唤醒了");
}, "t1");
t1.start();
new Thread(()->{
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName()+"---->我来唤醒你");
},"t2").start();
}
输出结果
t2---->我来唤醒你
t1---->com in
xxxx 程序没有停止运行!!!
虽然unpark是一个生成一个许可证,但是他的上限只有1,只能通过1park,后面就无法通过了;
5、面试题
5.1、问题1: 为什么LockSupport可以突破wait和notify的原有的调用顺序
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。先发放了凭证后续可以畅通无阻。
5.2、问题2: 为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行。