概念

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport中的park()
和unpark()
的作用分别是阻塞线程和解除阻塞线程。
使用
三种让线程等待和唤醒的方法
- synchronized+wait+notify
- lock+await+signal
- LockSupport+park+unpark
JUC之synchronized和Lock | Kylin (codekylin.cn)

- 使用Object中的
wait()
方法让线程等待,使用Object中的notify()
方法唤醒线程。
- 使用JUC包中的Condition的
await()
方法让线程等待,使用signal()
方法唤醒线程。
- LockSupport类可以阻塞当前线程已经唤醒指定被阻塞的线程。
synchronized

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package com.kylin;
public class SynchronizedDemo {
static Object objectLock = new Object();
public static void main(String[] args) { 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()+"---------被唤醒"); } }, "A").start();
new Thread(() -> { synchronized (objectLock) { objectLock.notify(); System.out.println(Thread.currentThread().getName() + "---------通知"); } }, "B").start(); } }
|
A线程让线程暂停,B线程唤醒A线程执行。

此时一切正常。接着把同步代码快注释

运行抛出java.lang.IllegalMonitorStateException
异常。说明wait
和notify
是无法单独脱离synchronized
使用的。
接着我们让A线程运行先暂停3秒钟,确保B线程先运行。也就是notify和wait先后过程调换,先notify再wait。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package com.kylin;
import java.util.concurrent.TimeUnit;
public class SynchronizedDemo {
static Object objectLock = new Object();
public static void main(String[] args) { new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (objectLock) { System.out.println(Thread.currentThread().getName() + "---------come in"); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"---------被唤醒"); } }, "A").start();
new Thread(() -> { synchronized (objectLock) { objectLock.notify(); System.out.println(Thread.currentThread().getName() + "---------通知"); } }, "B").start(); } }
|
A线程暂停3秒钟,确保B线程先执行notify方法。B线程先notify(此时没有线程被暂停,没有唤醒任何线程),3秒钟过后,A线程继续执行,阻塞等待。一直没有线程将其唤醒。程序一直运行….
总结
- wait和notify方法必选要再同步代码快或者同步方法里面而且成对出现使用
- 遵循先wait后notify
Lock

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.kylin;
import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
static Lock lock = new ReentrantLock(); static Condition condition = lock.newCondition();
public static void main(String[] args) { new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "---------come in"); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } System.out.println(Thread.currentThread().getName() + "---------被唤醒");
}, "A").start();
new Thread(() -> { lock.lock(); try { condition.signal(); System.out.println(Thread.currentThread().getName() + "---------通知"); }finally { lock.unlock(); } }, "B").start(); } }
|
将lock的lock
和unlock
操作注释。运行代码


同样运行抛出java.lang.IllegalMonitorStateException
异常
接着我们同样是A线程先暂停3秒钟,确保B线程先运行,通知唤醒。

同样程序处于一直运行状态,A线程没有被唤醒。
这和使用synchronized
是一样的问题,只不过对应Api有所区分。
传统的synchronized和Lock实现等待唤醒通知的约束
- 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
- 必须要先等待后唤醒,线程才能够被唤醒
LockSupport
通过park()
和unpark(thread)
方法来实现阻塞和唤醒线程的操作
LockSupport类使用一种名为Permit
(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit只有两个值1和0,默认是零。
可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public class LockSupportDemo {
public static void main(String[] args) { Thread a = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "---------come in"); LockSupport.park(); System.out.println(Thread.currentThread().getName() + "---------被唤醒"); }, "A"); a.start();
Thread b = new Thread(() -> { LockSupport.unpark(a); System.out.println(Thread.currentThread().getName() + "---------通知"); }, "B"); b.start(); } }
|
接着同样是A线程暂停3秒钟,确保B线程先执行唤醒操作。

并没有出现抛出异常的情况,B线程运行3秒钟后,A线程正常运行,并被正常唤醒。
通过以上代码可以看出,LockSupport不需要同步代码块之类的前提,同时也支持先唤醒后等待这种操作。(先唤醒,后面的等待操作就没有用了,相当于抵消了)
详解
LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。总之,LockSupport调用的Unsafe中的native代码。

1 2 3
| public static void park() { UNSAFE.park(false, 0L); }
|

1 2 3 4
| public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); }
|
LockSupport提供park()和unpark()方法来实现阻塞线程和解除线程阻塞的过程。
LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0。
调用一次unpark就加1,变成1。
调用一次park会消费permit,也就是将1变成0,同时park立即返回。
如再次调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。
每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会积累凭证。

- A线程运行首先使用park(),将线程阻塞,permit为0。
- B线程调用unpark(a)两次,permit由0+1=1变成了1,不能积累。
- A线程不唤醒,再次调用park(),将线程阻塞,permit为0。没有唤醒操作,程序一直运行…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
public class LockSupportDemo {
public static void main(String[] args) { Thread a = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "---------come in"); LockSupport.park(); LockSupport.park(); System.out.println(Thread.currentThread().getName() + "---------被唤醒"); }, "A"); a.start();
Thread b = new Thread(() -> { LockSupport.unpark(a); LockSupport.unpark(a); System.out.println(Thread.currentThread().getName() + "---------通知"); }, "B"); b.start(); } }
|
线程阻塞需要消耗凭证(permit),这个凭证最多只有一个。
当调用park方法时
- 如果有凭证,则会直接消耗掉这个凭证然后正常退出
- 如果无凭证,就必选阻塞等待凭证可用
而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。
为什么可以先唤醒线程后阻塞线程?
因为unpark获得一个凭证,之后调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证。而调用两次park却需要消耗两个凭证,证不够,不能放行。