目前java语言层面能够实现线程的阻塞与唤醒,主要包含两个组合对wait/notify以及park/unpark。wait/notify这组组合中wait必须发生在notify前面,否则很可能造成线程会一直阻塞,而park与unpark则不必,但是如果unpark先执行,后面执行park操作将不会阻塞。JDK并发中的AQS框架使用的就是LockSupport中的park/unpark操作,实际上调用的是Unsafe类的底层操作.
public static void park() {//组设
U.park(false, 0L);
}
public static void unpark(Thread thread) {//唤醒
if (thread != null)
U.unpark(thread);//调用UnSafe类的底层操作.
}
public static void parkNanos(long nanos)//可以指定等待的最长时间,参数是相对于当前时间的纳秒数,超时解锁
public static void parkUntil(long deadline) //指定等待的最长时间毫秒数,它是相对于积怨开始的时间,调用时需要System.currentTimemillis()+durationMillis
//下面这几个方法与上面的差不多,只是多了一个blocker参数,表示由于该对象造成的等待,一般传this
public static void park(Object blocker)
public static void parkNanos(Object blocker, long nanos)
public static void parkUntil(Object blocker, long deadline)
eg.
@Test
public void testLockSupport(){
Thread thread=new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"11111");
LockSupport.park();//阻塞放弃cpu执行,进入等待队列,必须等待别的线程调用unpark解锁才可以继续执行(不同于yield)
System.out.println(Thread.currentThread().getName()+"22222");
}
};
try {
thread.start();
Thread.sleep(1000);
}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getId());
LockSupport.unpark(thread);//唤醒,主线程调用unpark(thread)解锁
}
输出:
Thread-011111
1
Thread-022222
ReentrantLock是java中的显示锁,它有公平锁(按照申请锁的顺序,FIFO的顺序获取锁)和非公平锁之分(线程间竞争锁,synchronized是非公平锁),在其构造函数中传参的不同决定使用公平锁还是非公平锁,实现了Lock接口。
public class ReentrantLock implements Lock, Serializable {
private static final long serialVersionUID = 7373984872572414699L;
private final ReentrantLock.Sync sync;
public ReentrantLock() {//默认情况下是非公平锁
this.sync = new ReentrantLock.NonfairSync();//实例化Sync,它是AQS中的核心类,后续会讲到
}
public ReentrantLock(boolean var1) {//var1若为true采用公平锁,否则为非公平锁.
this.sync = (ReentrantLock.Sync)(var1?new ReentrantLock.FairSync():new ReentrantLock.NonfairSync());
}
public void lock() {
this.sync.lock();//其实是调用sync来实现加锁
}
.....
}
public interface Lock {
void lock();//获取锁操作.lock()会获取锁直到成功
void lockInterruptibly() throws InterruptedException;//与lock不同的是它可以获取中断,然后抛出InterruptedException
boolean tryLock();//尝试获取锁,不会阻塞
boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;// 在指定的时间内尝试获取锁
void unlock();//解锁
Condition newCondition();//这个很重要,是"锁上的条件"
public interface Condition {
void await() throws InterruptedException;//相当于Object中的wait
void awaitUninterruptibly();
long awaitNanos(long var1) throws InterruptedException;
boolean await(long var1, TimeUnit var3) throws InterruptedException;
boolean awaitUntil(Date var1) throws InterruptedException;
void signal();//相当于Object中的notify
void signalAll();//相当于Object中的notifyAll
}
}
eg.
@Test
public void testReentrantLock() {
final Lock lock = new ReentrantLock(false);
lock.lock();
try {
//doSomething();
} catch (Exception e) {
} finally {
lock.unlock();//必须显示的调用unlock
}
}
需要注意的是synchronized修饰的方法或者同步代码块,在方法执行完毕的时候会自动释放锁,而ReentrantLock需要手动的调用lock.unlock()释放锁,为了避免出现异常情况,一般在finally中做释放锁的操作.
当我们调用lock的时候实际上执行的是sync.lock这里以非公平锁为例,sync=new ReentrantLock.NonfairSync();
//AQS简化了并发数据的处理
abstract static class Sync extends AbstractQueuedSynchronizer
{
AQS中封装了一个状态的设置和获得,方便子类的调用
protected AbstractQueuedSynchronizer() {}
protected final int getState() {
return this.state;
}
protected final void setState(int var1) {
this.state = var1;
}
protected final boolean compareAndSetState(int var1, int var2) {
return unsafe.compareAndSwapInt(this, stateOffset, var1, var2);
}
}
//下面的方法是设置和获取当前持有锁的线程
protected final void setExclusiveOwnerThread(Thread var1) {
this.exclusiveOwnerThread = var1;
}
protected final Thread getExclusiveOwnerThread() {
return this.exclusiveOwnerThread;
}
static final class NonfairSync extends ReentrantLock.Sync {
private static final long serialVersionUID = 7316153563782823691L;
NonfairSync() {
}
final void lock() {
if(this.compareAndSetState(0, 1)) {//如果当前锁的数量为0,表示未被锁定则将当前线程持有锁并将state+1持有锁定
this.setExclusiveOwnerThread(Thread.currentThread());
} else {
this.acquire(1);//如果当前锁被别的线程持有锁定则调用AQS中的acquire方法
}
}
protected final boolean tryAcquire(int var1) {
return this.nonfairTryAcquire(var1);// 调用nonfairTryAcquire
}
}
final boolean nonfairTryAcquire(int var1) {
Thread var2 = Thread.currentThread();
int var3 = this.getState();
if(var3 == 0) {
if(this.compareAndSetState(0, var1)) {//如果当前没有持有锁,设置持有锁的线程并返回
this.setExclusiveOwnerThread(var2);
return true;
}
} else if(var2 == this.getExclusiveOwnerThread()) {
int var4 = var3 + var1;//如果当前线程持有锁就将锁的数量加1并返回true
if(var4 < 0) {
throw new Error("Maximum lock count exceeded");
}
this.setState(var4);
return true;
}
return false;//否则返回false
}
AQS:
public final void acquire(int var1) {
if(!this.tryAcquire(var1) && //调用子类NonfairSync的tryAcquire获取锁
this.acquireQueued(this.addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), var1)) {//如何tryAcquire返回false执行acquireQueued,创建当前线程的node加入队列中,在队列中尝试获取锁
selfInterrupt();
}
}
final boolean acquireQueued(AbstractQueuedSynchronizer.Node var1, int var2) {
boolean var3 = true;
try {
boolean var4 = false;
while(true) {
AbstractQueuedSynchronizer.Node var5 = var1.predecessor();
if(var5 == this.head && this.tryAcquire(var2)) {
this.setHead(var1);
var5.next = null;
var3 = false;
boolean var6 = var4;
return var6;
}
if(shouldParkAfterFailedAcquire(var5, var1) && this.parkAndCheckInterrupt()) {
var4 = true;
}
}
} finally {
if(var3) {
this.cancelAcquire(var1);
}
}
}
主体是一个死循环,在每次循环中,首先检查当前节点的前置节点是不是第一个等待的节点,
如果是且能获得到锁,则将当前节点从等待队列中移除并返回,
否则最终调用LockSupport.park放弃CPU,进入等待,
被唤醒后,检查是否发生了中断,记录中断标志,
在最终方法返回时返回中断标志。如果发生过中断,acquire方法最终会调用selfInterrupt方法设置中断标志位。
ReentrantLock的unlock方法的代码为:
public void unlock() {
this.sync.release(1);
}
public final boolean release(int var1) {
if(this.tryRelease(var1)) {
AbstractQueuedSynchronizer.Node var2 = this.head;
if(var2 != null && var2.waitStatus != 0) {
this.unparkSuccessor(var2);//调用unparkSuccessor方法释放锁.
}
return true;
} else {
return false;
}
}
上面的逻辑比较乱总结起来就是:
阻塞
if(尝试获取锁失败) {
创建node
使用CAS方式把node插入到队列尾部
while(true){
if(尝试获取锁成功 并且 node的前驱节点为头节点){
把当前节点设置为头节点
跳出循环
}else{
使用CAS方式修改node前驱节点的waitStatus标识为signal
if(修改成功)
LockSupport.park();
}
}
}唤醒
if(尝试释放锁成功){
LockSupport.unpark(下一节点包含的线程);
}
假如一条线程参与锁竞争,首先先尝试获取锁,失败的话创建节点并插入队列尾部,然后再次尝试获取锁,如若成功则不做其他任务处理直接返回,否则设置节点状态为待运行状态,最后使用LockSupport的park阻塞当前线程。前驱节点运行完后将尝试唤醒后继节点,使用的即是LockSupport的unpark唤醒。