简介
LockSupport是juc里的类,提供了线程等待唤醒机制(wait/notify)功能
LockSupport中的park和unpark的作用分别是阻塞线程和解除阻塞线程。
有三种线程等待唤醒的方法:
- 使用Object中的wait方法让线程等待,notify方法唤醒线程
- 使用JUC中的Condition的await方法让线程等待,使用signal方法唤醒线程
- 使用LockSupport阻塞当前线程以及唤醒指定被阻塞的线程
LockSupport的Api如下:
wait && notify
案例:
static final Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (obj){
System.out.println(Thread.currentThread().getName() +" start");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"被唤醒");
}
}, "A").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
synchronized (obj){
System.out.println(Thread.currentThread().getName()+" start");
obj.notify();
}
}, "B").start();
}
wait
和notify
方法都是在synchronized
代码体中执行的,如果没有经过synchronized
修饰,直接使用则会抛出java.lang.IllegalMonitorStateException
异常。
对此,jdk官方文档给出的解释:
第二段话翻译过来就是:当前线程必须拥有此对象监视器。该线程释放对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。
Condition await && signal
案例:
static final Object obj = new Object();
static final ReentrantLock lock = new ReentrantLock();
static final Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName() +" start");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"被唤醒");
} finally {
lock.unlock();
}
}, "A").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName() +" start");
condition.signal();
} finally {
lock.unlock();
}
}, "B").start();
}
运行结果:
传统的Object和Condition 实现等待唤醒通知的约束:
- 线程先要获得并持有锁,必须在锁块(synchronized或Lock)中
- 必须先等待再唤醒,线程才能够唤醒
LockSupport
LockSupport是用来创建锁和几他同步类的基本线程阻塞原语.
LockSupport类使用了一种名为Permit (许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit).
permit只有两个值1和零,默认是零。
可以把许可看成是一种(0,1)信号量(Semaphore).但与Semaphore不同的是,许可的累加上限是1。
阻塞: park()/park(Object blocker):阻塞当前线程/阻塞传入的具体线程
唤醒:unpark(Thread thread):唤醒处于阻塞状态的线程
案例1:
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(() -> {
System.out.println(Thread.currentThread().getName() +" start");
LockSupport.park();
System.out.println(Thread.currentThread().getName() +"被唤醒");
}, "A");
a.start();
TimeUnit.SECONDS.sleep(1);
Thread b = new Thread(() -> {
System.out.println(Thread.currentThread().getName() +" start");
LockSupport.unpark(a);
}, "B");
b.start();
}
案例2:
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" start");
LockSupport.park();
System.out.println(Thread.currentThread().getName() +"被唤醒");
}, "A");
a.start();
Thread b = new Thread(() -> {
System.out.println(Thread.currentThread().getName() +" start");
LockSupport.unpark(a);
}, "B");
b.start();
}
案例3:
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" start");
LockSupport.park();
LockSupport.park();
System.out.println(Thread.currentThread().getName() +"被唤醒");
}, "A");
a.start();
Thread b = new Thread(() -> {
System.out.println(Thread.currentThread().getName() +" start");
LockSupport.unpark(a);
LockSupport.unpark(a);
}, "B");
b.start();
}
使用LockSupport:
线程不需要先在锁块中获取锁
可以先执行unpark,再执行park
"通行证"最多一个
总结
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
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也不会积累凭证。
形象的理解
线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。
当调用park方法时
- 如果有凭证,则会直接消耗掉这个凭证然后正常退出;