- 因为wait()方法需要释放锁,所以必须在synchronized中使用,否则会抛出异常
IllegalMonitorStateException - notify()方法也必须在synchronized中使用,并且应该指定对象
- synchronized()、wait()、notify()对象必须一致,一个synchronized()代码块中只能有一个线程调用wait()或notify()
以上诸多限制,体现出了很多的不足,所以LockSupport的好处就体现出来了。
在JDK1.6中的java.util.concurrent的子包locks中引了LockSupport这个API,LockSupport是一个比较底层的工具类,用来创建锁和其他同步工具类的基本线程阻塞原语。java锁和同步容器框架的核心 AQS:AbstractQueuedSynchronizer,就是通过调用 LockSupport .park()和 LockSupport .unpark()的方法,来实现线程的阻塞和唤醒的。
示例程序
public class T08_LockSupport {
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i == 5) {
// park()方法阻塞当前线程t
LockSupport.park();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
以上程序中当 i 等于5时,会调用LockSupport.park()方法使当前线程阻塞。可以看到方法并没有加锁,就默认使当前线程阻塞了,由此可以看出LockSupprt.park()方法没有加锁的限制。
程序稍加改动,用unpark方法唤醒线程:
public class T08_LockSupport1 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i == 5) {
// park()方法阻塞当前线程t
LockSupport.park();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 启动线程t
t.start();
// 唤醒线程t
LockSupport.unpark(t);
System.out.println("unpark方法被调用!");
}
}
运行后发现unpark(t)方法先执行了之后,park()方法不会再阻塞。即LockSupport的unpark()方法可以先于LockSupport的park()方法执行。
下面尝试一下调用两次park(),i等于8时再park一次:
public class T08_LockSupport2 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i == 5) {
// park()方法阻塞当前线程t
LockSupport.park();
}
if (i == 8) {
// park()方法阻塞当前线程t
LockSupport.park();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 启动线程t
t.start();
// 唤醒线程t
LockSupport.unpark(t);
// LockSupport.unpark(t); 两次unpark仍不能唤醒i==8时的park
System.out.println("unpark方法被调用!");
}
}
运行后发现只有当i==5时的park被唤醒,i==8时依然会阻塞。
原因是LockSupport的unpark()方法就像是获得了一个“令牌”,而park()方法就像是在识别“令牌”,当主线程调用了LockSupport.unpark(t)方法也就说明线程 " t " 已经获得了”令牌”,当线程 " t " 再调用LockSupport的park()方法时,线程 " t " 已经有令牌了,这样他就会马上再继续运行,也就不会被阻塞了。
但是当i==8时线程 " t " 再次调用了LockSupport的park()方法使线程再次进入阻塞状态,这个时候“令牌”已经被使用作废掉了,也就无法阻塞线程 " t " 了。而且如果主线程处于等待“令牌”状态时,线程 " t " 再次调用了LockSupport的park()方法,那么线程 " t "就会永远阻塞下去,即使调用unpark()方法也无法唤醒了。
park()和unpark()方法的实现原理
park()和unpark()方法的实现是由Unsafe类提供的,而Unsafe类是由C和C++语言完成的。它主要通过一个变量作为一个标识,变量值在0,1之间来回切换,当这个变量大于0的时候线程就获得了“令牌”,其实park()和unpark()方法就是在改变这个变量的值,来达到线程的阻塞和唤醒的。
总结
- LockSupport不需要synchornized加锁就可以实现线程的阻塞和唤醒
- LockSupport.unpark()可以先于LockSupport.park()执行,并且线程不会阻塞
- 如果一个线程处于等待状态,连续调用了两次park()方法,就会使该线程永远无法被唤醒
参考:马士兵《多线程与高并发》
网友评论