官方解释:
-
LockSupport
类是用来创建锁和其他同步类的基本线程阻塞原语; -
LockSupport 类使用了一种名为
Permit
(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit); -
permit 只有两个值1和零,默认是零;
-
可以把许可看成是一种(0,1)信号量(
Semaphore
),但与Semaphore不同的是,许可的累加上限是1。
其实它就是线程的等待唤醒机制的改良加强版;
LockSupport
类中的park()
和unpark()
的作用分别是阻塞线程和解除阻塞线程;
我们知道Object
类中有wait()
和notify()
等待唤醒线程的方法,juc 包中Conditon
接口类也有await()
和signal()
等待唤醒线程的方法, 为什么还要来个LockSupport
类的park
()和unpark()
的等待唤醒线程的方法呢?
我们来看下Object
类和 juc 包中Conditon
接口类使用线程等待唤醒方法分别有什么限制;
1. 测试案例
1.1 wait 和 notify方法
public class LockSupportDemo1 {
// 加锁对象
static Object objectLock = new Object();
public static void main(String[] args) {
// 第一个线程调用 wait()方法
new Thread(()->{
/*try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
synchronized (objectLock){
System.out.println(Thread.currentThread().getName() + ":进入并等待");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":被唤醒");
}
},"A").start();
// 第二个线程调用 notify()方法,唤醒线程A
new Thread(()->{
synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName() + ":通知");
}
},"B").start();
}
}
正常返回:
A:进入并等待
B:通知
A:被唤醒
- 异常一:如果把两个线程的同步代码块方法去掉【10,18,23,26行】,抛出异常:
A:进入
Exception in thread "A" Exception in thread "B" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.beef.LockSupportDemo.lambda$main$1(LockSupportDemo.java:30)
at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.beef.LockSupportDemo.lambda$main$0(LockSupportDemo.java:19)
at java.lang.Thread.run(Thread.java:748)
所以 Object 类中的 wait、notify、notifyAll 方法用于线程等待和唤醒的方法,都必须在synchronized内部执行 (必须用到关键字synchronized)。
- 异常二:当
notify()
运行在wait()
前面时【放开10~14行代码】,线程无法被唤醒,程序无法正常执行;
这个不用测试我们都知道,放这里是为了突出后面LockSupport
中的park()
和unpark()
可以这么干;
1.2 await 和 signal 方法
public static void main(String[] args) {
// 第一个线程调用 await()方法
new Thread(()->{
/*try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + ":进入并等待");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":被唤醒");
}finally {
lock.unlock();
}
},"A").start();
// 第二个线程调用 signal()方法,唤醒线程A
new Thread(()->{
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + ":通知");
}finally {
lock.unlock();
}
},"B").start();
}
正常返回:
A:进入并等待
B:通知
A:被唤醒
- 异常一:如果把两个线程的加锁释放锁的方法去掉【10,20,26,31行】,抛出异常:
A:进入
Exception in thread "A" Exception in thread "B" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at com.beef.lockSupport.LockSupportDemo2.lambda$main$1(LockSupportDemo2.java:38)
at java.lang.Thread.run(Thread.java:748)
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.beef.lockSupport.LockSupportDemo2.lambda$main$0(LockSupportDemo2.java:24)
at java.lang.Thread.run(Thread.java:748)
所以await()
和signal()
方法用于线程等待和唤醒的方法,必须在lock()
和unlock()
;
- 异常二:当
await()
运行在signal()
前面时【开放第5~9行代码】,线程无法被唤醒,程序也无法正常执行;
小总结
传统的synchronized
和Lock
实现等待唤醒通知的约束:
- 线程先要获得并持有锁,必须在锁块(
synchronized
或lock
)中; - 必须要先等待后唤醒,线程才能够被唤醒;
1.3 park 和 unpark 的方法
public class LockSupportDemo3 {
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
// 第一个线程调用 park()方法
Thread a = new Thread(() -> {
/*try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println(Thread.currentThread().getName() + ":进入并加锁");
// 阻塞,等待唤醒
LockSupport.park();
System.out.println(Thread.currentThread().getName() + ":唤醒");
}, "A");
a.start();
// 第二个线程调用 unpark()方法,唤醒线程A
new Thread(()->{
LockSupport.unpark(a);
System.out.println(Thread.currentThread().getName()+":解锁");
},"B").start();
}
}
正常返回:
A:进入
B:通知
A:被唤醒
当unpark()
运行在park()
前面时【开放第10~14行代码】,程序可以正常执行;
B:解锁
A:进入并加锁
A:唤醒
这种情况相当于第12行LockSupport.park();
被注释掉了;
小总结
LockSupport
中的park()
和unpark()
无锁块要求,且可以先唤醒后等待;
2. LockSupport 源码
LockSupport
是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。LockSupport 调用的 Unsafe
中的 native
代码,是通过C语言原语级别来控制的;
比如我们看下park()
方法:
public static void park() {
UNSAFE.park(false, 0L);
}
// 点进UNSAFE.park方法
public native void park(boolean var1, long var2);