LockSupport
LockSupport
线程等待和唤醒方法
了解LockSupport前,先回顾2种让线程等待和唤醒的方法
方式一
使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
public void test1() {
Object objectLock = new Object(); //同一把锁,类似资源类
new Thread(() -> {
synchronized (objectLock) {
System.out.println(Thread.currentThread().getName()+" -------come in");
try {
objectLock.wait();//等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒了");
},"t1").start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
synchronized (objectLock) {
objectLock.notify();
System.out.println(Thread.currentThread().getName()+" ------通知");
}
},"t2").start();
}
执行结果
t1 -------come in
t2 ------通知
t1 被唤醒了
异常情况一
如果去掉synchronized,即不在synchronized关键字方法中使用wait() 和 notify() 方法将抛出 java.lang.IllegalMonitorStateException
异常
t1 -------come 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.company.jucmax.LockSupport.LockSupportDemo.lambda$test1notsyn$2(LockSupportDemo.java:44)
at java.lang.Thread.run(Thread.java:750)
Exception in thread "t2" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.company.jucmax.LockSupport.LockSupportDemo.lambda$test1notsyn$3(LockSupportDemo.java:57)
at java.lang.Thread.run(Thread.java:750)
异常情况二
先 notify() 后 wait() t1线程无法被唤醒
总结
wait和notify方法必须要在同步块或者方法里面且成对出现使用
先wait后notify才可以
方式二
使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
public void lock() {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "----come in");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "------被唤醒");
} finally {
lock.unlock();
}
}, "t1").start();
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "------通知");
} finally {
lock.unlock();
}
}, "t2").start();
}
运行结果
t1----come in
t2------通知
t1------被唤醒
异常情况一
不在 lock() 和 unlock() 方法内使用 await() 和 signal() 方法,方法将抛出 java.lang.IllegalMonitorStateException
异常
t1----come in
Exception in thread "t1" Exception in thread "t2" 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.company.jucmax.LockSupport.LockSupportDemo.lambda$locknotlock$8(LockSupportDemo.java:139)
at java.lang.Thread.run(Thread.java:750)
java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at com.company.jucmax.LockSupport.LockSupportDemo.lambda$locknotlock$9(LockSupportDemo.java:153)
at java.lang.Thread.run(Thread.java:750)
异常情况二
先 signal() 后 await() t1线程无法被唤醒
总结
Condtion中的线程等待和唤醒方法之前,需要先获取锁
先await后signal
总结
Object和Condition使用的限制条件:
- 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
- 必须要先等待后唤醒,线程才能够被唤醒
因此出现LockSupport 来实现线程的阻塞和唤醒
LockSupport简介
- 用于创建锁和其他同步类的基本线程阻塞原语。
- 简而言之,当调用LockSupport.park时,表示当前线程将会等待,直至获得许可,当调用LockSupport.unpark时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行。
- LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),permit只有两个值1和零,默认是零。
- 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
主要实现方法
阻塞
park() /park(Object blocker)
阻塞当前线程/阻塞传入的具体线程
public static void park() {
UNSAFE.park(false, 0L);
}
permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒, 然后会将permit再次设置为零并返回。
唤醒
unpark(Thread thread)
唤醒处于阻塞状态的指定线程
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
代码
正常+无锁块要求
public void test1notsyn() {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+" -------come in");
try {
LockSupport.park();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒了");
},"t1");
t1.start();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName()+" ------通知");
},"t2").start();
}
t1 -------come in
t2 ------通知
t1 被唤醒了
- 即使先唤醒,后等待也可以执行
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+" -------come in");
LockSupport.park();
System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒了");
},"t1");
t1.start();
new Thread(() -> {
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName()+" ------通知");
},"t2").start();
}
t2 ------通知
t1 -------come in
t1 被唤醒了
综上:LockSupport不用担心阻塞和唤醒操作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样的。