LcokSupport
定义
LockSupport是一个在JUC包里的线程阻塞工具类,所有的方法都是静态方法,主要用途是让线程在任意位置阻塞,广泛应用在AQS和各种JUC的锁中
常用的方法和基本原理了解
public static void park(Object blocker); // 暂停当前线程
public static void parkNanos(Object blocker, long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(Object blocker, long deadline); // 暂停当前线程,直到某个时间
public static void park(); // 无期限暂停当前线程
public static void parkNanos(long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(long deadline); // 暂停当前线程,直到某个时间
public static void unpark(Thread thread); // 恢复当前线程
可以看到这里的方法主要就是park和unpark也就是阻塞和唤醒的作用
park指的就是停车
unpark指的就是放行
这里的blocker指的一般就是当前的线程对象,方便dump时分析问题,
我们可以尝试先在代码使用下这些方法
static class ParkThread implements Runnable{
@Override
public void run() {
System.out.println( Thread.currentThread().getName() + "开始线程阻塞");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "结束线程阻塞");
}
}
public static void main(String[] args) {
ParkThread parkThread = new ParkThread();
Thread t1=new Thread(parkThread);
Thread t2=new Thread(parkThread);
t1.start();
LockSupport.unpark(t1);
t2.start();
LockSupport.unpark(t2);
}
这样代码运行是没有问题,那么如果先执行unpark方法,后执行park方法会怎么样呢
static class ParkThread implements Runnable{
@Override
public void run() {
System.out.println( Thread.currentThread().getName() + "开始线程阻塞");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "结束线程阻塞");
}
}
public static void main(String[] args) {
ParkThread parkThread = new ParkThread();
Thread t1=new Thread(parkThread);
Thread t2=new Thread(parkThread);
t1.start();
LockSupport.unpark(t1);
t2.start();
LockSupport.unpark(t2);
}
运行后会发现,线程并没有阻塞,而是和之前一样执行成功了,这个就是和notify和wait最大的区别了,因为notify的唤醒顺序是不能颠倒的.
那么LockSupport是怎么实现这个功能的呢?这里是基本介绍,我就不和大家分析源码了,给大家描述一下流程:
源码里其实是通过一个_counter变量来控制的
_counter可以作为一个凭证
unpark方法的时候把_counter置为1
park方法判断如果_counter=1的话就不堵塞,并且把_counter设置为0,
换句话说当_counter=1的时候,调用park方法是不会阻塞的, 所以调用多次unpark方法后,调用一次park方法,不会堵塞
但是调用多次unpark方法后,调用多次park方法就会堵塞
应用
在之前分析AQS源码ReentrantLock加锁解锁源码详细解析的时候,我们看到过LockSupport的实际应用场景,其实在Condition和CountDownLatch等常用的并发类中用的也都是LockSupport
notify&wait
定义
notify和wait都是object的方法,一般被用作线程间的协作,也可以实现线程的等待和唤醒,功能和LockSupport较为类似
应用示例
private static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(new WaitThread(), "wait-1").start();
Thread.sleep(100);
new Thread(new NotifyThread(), "notify-1").start();
}
static class WaitThread implements Runnable{
@Override
public void run() {
synchronized (obj){
System.out.println("start wait");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end wait");
}
}
}
static class NotifyThread implements Runnable{
@Override
public void run() {
synchronized (obj){
System.out.println("start notify");
obj.notify();
System.out.println("end notify");
}
}
}
需要注意的是notify和wait必须在同步代码块中使用,否则就会出现IllegalMonitorStateException
start wait
start notify
Exception in thread "notify-1" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.example.skill.lock.NotifyTest$NotifyThread.run(NotifyTest.java:40)
at java.lang.Thread.run(Thread.java:748)
基本原理
wait方法:
- 把当前线程封装成一个node
- 通过objectmonitor::addwaiter方法将node添加到_WaitSet列表中
- 通过ObjectMonitor:exit方法释放当前的ObjectMonitor对象,这样其他竞争线程就可以获取该ObjectMonitor对象
- 最终还是代调用了park方法挂起线程
notify方法则是随机唤醒等待池中的一个线程
两者之间的区别
相同点:
- wait¬ift和LockSupport都可以用作线程之间的协作
- 低层其实都是调用了park方法来挂起线程
不同点:
- wait¬ify必须在同步代码块中使用,LockSupport则可以在任意场合使用
- wait¬ify不能颠倒顺序使用,LockSupport可以颠倒park和unpark的顺序
- wait¬ify不能唤醒指定的线程,但是LockSupport可以
- notifyAll方法可以唤醒所有等待的线程,但是LockSupport只能唤醒单个线程
扩展1(如何使用wait¬ify实现一个阻塞队列)
public static void main(String[] args) throws InterruptedException {
NotifyBlockQueue notifyBlockQueue = new NotifyBlockQueue(5);
for (int i = 0; i < 10; i++) {
final int a = i;
new Thread(() -> {
try {
notifyBlockQueue.put(a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
System.out.println("从队列取出" + notifyBlockQueue.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
static class NotifyBlockQueue{
//模拟队列
private LinkedList linkedList= new LinkedList();
//最大容量
private int maxSize;
public NotifyBlockQueue(int maxSize){
this.maxSize = maxSize;
}
//队列是否满了
public boolean isFull(){
return linkedList.size() == maxSize;
}
//队列是否为空
public boolean isEmpty(){
return linkedList.isEmpty();
}
public synchronized void put(Object value) throws InterruptedException {
//如果队列满了就一直等待
while (isFull()){
this.wait();
}
linkedList.add(value);
System.out.println("开始往队列放入" + value);
this.notifyAll();
}
public synchronized Object get() throws InterruptedException {
while (isEmpty()){
this.wait();
}
Object o = linkedList.removeFirst();
this.notifyAll();
return o;
}
}