写在前面,阅读这篇文章,需要这些知识:
Java并发——Thread类解析、线程初探
Java并发——CAS原子操作
Java并发——AQS框架详解
LockSupport
LockSupport
类是一个工具类,用来在显示锁里面替换Object
类的wait
、notify
方法的。在内置锁里面,wait
方法必须在线程持有锁的时候才能对线程进行阻塞,但是LockSupport
类却不一样,即使线程没有持有锁,它也能将该线程阻塞,它有两个重要的方法,但是底层的实现是由Unsafe
类来实现的,也就是说,是由JVM来实现的:
public static void park() {
UNSAFE.park(false, 0L);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
park
方法是阻塞线程,而unpark
方法是唤醒被park
方法阻塞的线程,如下代码:
public class Test {
public static void main (String args[]) throws InterruptedException {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("蕾姆被障碍挡住啦");
LockSupport.park();
System.out.println("拉姆帮蕾姆清扫了障碍");
}
});
thread.start();
Thread.sleep(3000);
System.out.println("三秒后拉姆来帮蕾姆清扫障碍了");
LockSupport.unpark(thread);
}
}
//打印:
//蕾姆被障碍挡住啦
//三秒后拉姆来帮蕾姆清扫障碍了
//拉姆帮蕾姆清扫了障碍
从代码结果看出LockSupport
阻塞了线程,三秒后由unpark
方法唤醒接着运行。LockSupport
阻塞线程的实质是以唯一一个许可证来完成的,unpark
方法是给线程一个许可,而park
方法是回收线程的许可,如果没有许可则陷入阻塞,不管调用几个unpark
方法,线程中的许可始终只有一个。出于这个特性,unpark
方法甚至可以在park
方法之前调用。还有一点与wait
方法不同,它阻塞的时候不会释放锁:
public static void main (String args[]) throws InterruptedException {
Lock lock=new ReentrantLock();
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("蕾姆被障碍挡住啦");
LockSupport.park();
System.out.println("拉姆帮蕾姆清扫了障碍");
lock.unlock();
}
});
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("拉姆来帮蕾姆清扫障碍了");
LockSupport.unpark(thread);
lock.unlock();
}
});
thread.start();
thread1.start();
//只输出:蕾姆被障碍挡住啦
输出完蕾姆被障碍挡住啦
后,两个线程都陷入阻塞的状态,可见,确实没有释放掉锁。
Condition
Condition
在Java中是用于多线程协调通信的工具类,一般用于显示锁,即ReetrantLock
的通信,与LockSupport
类一样,提供了await
方法、signal
方法以及signalAll
方法对应,Object
类的wait
、notify
、notifyAll
方法。以下面的代码为例:
public class Test {
public static void main (String args[]) throws InterruptedException {
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("蕾姆被障碍挡住啦");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("拉姆帮蕾姆清扫了障碍");
lock.unlock();
}
});
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("拉姆来帮蕾姆清扫障碍了");
condition.signal();
lock.unlock();
}
});
thread.start();
thread1.start();
}
}
//输出:
//蕾姆被障碍挡住啦
//拉姆来帮蕾姆清扫障碍了
//拉姆帮蕾姆清扫了障碍
Condition
的创建时依赖于ReentrantLock
可重入锁的,因为Condition本质是一个接口,它的实现交由AQS框架
完成的,如果对AQS框架不熟的同学可以看看这篇文章:Java并发——AQS框架详解。从上述例子中可以看出,它与Object类的wait方法一样是在阻塞的过程中释放锁的。在AbstractQueuedSynchronizer
类中可以找到await
方法源码:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//d对当前线程包装,设置为CONDITION状态,加入等待队列
Node node = addConditionWaiter();
//释放当前线程占有的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//释放完毕后,遍历AQS的队列,看当前节点是否在队列中,
// 不在说明它还没有竞争锁的资格,所以继续将自己沉睡。
// 直到它被加入到队列中,signal时加入同步队列
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//检查是否正在阻塞
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//等待重新加入同步队列,请求资源
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
AQS框架
维护了一个同步队列,当持有锁的线程调用await
方法时,AQS
会把这个等待被唤醒的线程移除该队列,加入等待队列中,并调用LockSupport
类的park
方法使其陷入阻塞。当调用signal
方法时,会把该线程重新加入同步队列,与其它线程抢夺资源。signal
方法源码如下:
public final void signal() {
//判断是否是独占锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//唤醒第一个等待队列的线程
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
signal
方法与notify
方法不同,它总是唤醒第一个处于等待队列中的线程,而notify
是随机的唤醒处于等待队列中的线程。doSignal
方法底层调用了transferForSignal
方法,该方法让处于CONDITION状态
的线程,变为0状态
并使其从等待队列中加入同步队列,去重新和其它线程竞争,且调用unpark方
让线程恢复运行,signalAll
原理一样。Condition
另外一个比wait
方法的优势在于,它可以创建多个等待队列来管理线程,它们互不干扰。而wait
只有一个等待队列,也就是说Condition
比wait
方法要灵活。比如有如下场景,线程中有读取数据以及写入数据两部分,当存贮满的时候,这时候你肯定不希望写入,利用Condition
则可以选择唤醒读取那部分而不是写入。如下代码:
public class Test {
public static void main (String args[]) throws InterruptedException {
Lock lock=new ReentrantLock();
Condition read=lock.newCondition();
Condition write=lock.newCondition();
boolean readStatus=true;
boolean writeStatus=true;
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("拉姆准备写数据啦");
try {
write.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("拉姆写入数据了");
lock.unlock();
}
});
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("蕾姆准备读数据啦");
try {
if (writeStatus){
write.signal();
}
read.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("蕾姆读到数据了");
lock.unlock();
}
});
thread.start();
thread1.start();
}
}
通过自定义的状态可以随时的控制condition
的唤醒,而内置锁则没有这么的灵活。