玩转并发-Condition

概述

任何一个java对象都天然继承于Object类,在线程间实现通信的往往会应用到Object的几个方法,比如wait(),wait(long timeout),wait(long timeout, int nanos)与notify(),notifyAll()几个方法实现等待/通知机制,同样的, 在java Lock体系下依然会有同样的方法实现等待/通知机制。从整体上来看Object的wait和notify/notify是与对象监视器配合完成线程间的等待/通知机制,而Condition与Lock配合完成等待通知机制,前者是java底层级别的,后者是语言级别的,具有更高的可控制性和扩展性。

同步等待队列

指AQS中维护的用于存放等待锁的线程队列

条件队列

条件队列是指某个条件不满足时,挂起自己并释放锁,一旦等待条件为真,则立即醒来

创建一个condition对象是通过lock.newCondition(),而这个方法实际上是会new出一个ConditionObject对象,该类是AQS(AQS的实现原理的文章)的一个内部类,有兴趣可以去看看.condition是要和lock配合使用的也就是condition和Lock是绑定在一起的,而lock的实现原理又依赖于AQS,自然而然ConditionObject作为AQS的一个内部类无可厚非。我们知道在锁机制的实现上,AQS内部维护了一个同步等待队列,如果是独占式锁的话,所有获取锁失败的线程的尾插入到同步队列,同样的,condition内部也是使用同样的方式,内部维护了一个 条件队列,所有调用condition.await方法的线程会加入到等待队列中,并且线程状态转换为条件等待状态

	    //队列头部
        private transient Node firstWaiter;
        //队列尾部
        private transient Node lastWaiter;

具体使用示例

#入门Demo-简单的生产者消费者模式
condition一般会结合lock使用

public class ConditionExample {
	public static final Lock lock = new ReentrantLock();
	public static final Condition condition = lock.newCondition();//创建Condition实例

	
	public static int useDate=1;
	public static boolean useIf=true;//true代表已经消费数据
	
	public static void main(String[] args) {
		new Thread() {
			@Override
			public void run() {
				while(true) {
					Produce();
				}
			}
		}.start();
		
		
		new Thread() {
			@Override
			public void run() {
				while(true) {
					Consumer();
				}
			}
		}.start();
	}
	
	
	//生产者
	public static void Produce() {
			try {
				lock.lock();
				//未消费则await
				while(!useIf) {
					condition.await();
				}
				Thread.sleep(100);
				useDate++;
				useIf=false;
				//唤醒等待此锁的线程
				condition.signalAll();
				System.out.println("生产者生产数据--"+useDate);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally {
				lock.unlock();
			}
	}
	
	//消费者
	public static void Consumer() {
		try {
			lock.lock();
			//未生产则await
			while(useIf) {
				condition.await();
			}
			Thread.sleep(100);
			System.out.println("消费者消费数据--"+useDate);
			useIf=true;
			//唤醒等待此锁的线程
			condition.signalAll();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	} 
}


Condition接口
public interface Condition {

    void await() throws InterruptedException;
    
    long awaitNanos(long nanosTimeout) throws InterruptedException;

    boolean await(long time, TimeUnit unit) throws InterruptedException;

    boolean awaitUntil(Date deadline) throws InterruptedException;

    void signal();

    void signalAll();
}

接口方法两大类,一类是 await 方法,目的是使当前线程进入等待状态直到被通知(signal)或者中断。此类方法又可以分为以下几类:awaitUninterruptibly(中断不敏感)、awaitNanos(支持超时等待)、awaitUntil(等待到某个截止时间)。另一类是 signal 方法,分为两类:signal(通知等待队列上的一个线程)、signalAll(通知等待队列上的所有线程)。

Lock 接口里面有个 newCondition 方法,用于返回一个 Condition 实例,在 ReentrantLock 中是直接调用的 AQS 的 newCondition 方法。可见 Condition 实例是与 Lock 相绑定的。

#ReentrantLock下

public Condition newCondition() {
		//直接调用AQS的newCondition方法
        return sync.newCondition();
    }

//Reentrant的静态内部类Sync下
final ConditionObject newCondition() {
            return new ConditionObject();
  }

Condition实现

AQS下Condition实现

ConditionObject每次new都会维护一个条件队列,通过node的nextWaiter串起来

public class ConditionObject implements Condition, java.io.Serializable {
		//序列化ID
        private static final long serialVersionUID = 1173984872572414699L;
        //可以将Node理解为车厢,负责节点间的断开和连接;
        //而我们传入的数据就是乘客,无需理会链表间的操作
		//队列头部
        private transient Node firstWaiter;
        //队列尾部
        private transient Node lastWaiter;

        //无参构造
        public ConditionObject() { }
        ...
        }

Await方法实现

当调用condition.await()方法后会使得当前获取lock的线程进入到条件队列,如果该线程能够从await()方法返回的话一定是该线程获取了与condition相关联的lock

        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //创建新的节点,增加到条件队列尾部
            Node node = addConditionWaiter();
            //释放AQS同步等待队列中的节点
            int savedState = fullyRelease(node);
            int interruptMode = 0;



         	//如果在条件队列中,则进入循环
            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);
        }      

当前线程调用condition.await()方法后,会使得当前线程释放lock然后加入到条件队列中,直至被signal/signalAll后会使得当前线程从条件队列中移至到同步等待队列中去,才能从循环中跳出,接着自旋等待获取锁,直到获得了lock后才会从await方法返回,或者在等待时被中断会做中断处理。


        //创建当前线程的Node,并添加到最后一个节点中
        private Node addConditionWaiter() {
            Node t = lastWaiter;
            //t为最后一个等待节点,如果不为空且条件不为CONDITION,则清除该节点
            if (t != null && t.waitStatus != Node.CONDITION) {
            	//如果当前节点的状态不是condition,清除该节点
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            
            //创建当前线程的Node节点
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            //如果尾节点尾null,说明此队列为空队列,则新建节点为头部
            if (t == null)
                firstWaiter = node;
            else
            	//添加到尾部的下一个节点
                t.nextWaiter = node;
            lastWaiter = node;
            //返回当前线程的node
            return node;
        }

       //清除链表中所有失效的节点
        private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            Node trail = null;
            while (t != null) {
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        }

判断当前线程是否在同步等待队列中

 final boolean isOnSyncQueue(Node node) {
 		//如果当前线程状态是条件等待且上一个节点为null,则不在同步等待队列中了,而是在条件队列中
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
         //如果next不是null,说明其在同步等待队列上
        if (node.next != null) // If has successor, it must be on queue
            return true;
        // 如果从 tail 开始找上一个节点,找到了给定的节点,说明也在队列上.返回 true.
        return findNodeFromTail(node);
    }

很显然,当线程第一次调用condition.await()方法时,该线程的状态一定是CONDITION,所以会进入到这个while()循环中,然后通过LockSupport.park(this)方法使得当前线程进入等待状态。那么要想退出这个await方法第一个前提条件自然而然的是要先退出这个while循环,,出口就只剩下两个地方:

  1. 逻辑走到break退出while循环–中断
  2. while循环中的逻辑判断为false–当前节点被移动到了同步等待队列中(即另外线程调用的condition的signal或者signalAll方法)

在这里插入图片描述
如图,调用condition.await方法的线程必须是已经获得了lock,也就是当前线程是同步等待队列中的头结点。调用该方法后会使得当前线程所封装的Node尾插入到条件队列的尾部


那么又是如何释放锁,并唤醒AQS队列中的一个节点上的一个线程

	//释放AQS中同步等待队列中的节点
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
        	//获取state变量
            int savedState = getState();
            //如果释放成功,则返回state大小,也就是之前持有锁的线程的数量
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
            	//释放失败,抛出异常
                throw new IllegalMonitorStateException();
            }
        } finally {
        	//释放失败
            if (failed)
            	//将这个节点指成取消状态,然后从队列中移除
                node.waitStatus = Node.CANCELLED;
        }
    }

release方法实现

//主要功能就是释放锁,并唤醒阻塞在锁上的线程
public final boolean release(int arg) {
		//如果释放锁成功,返回true,可能会抛出监视器异常,即当前线程不是持有锁的线程
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        //释放失败
        return false;
    }

release中调用了tryRelease方法,该方法就是释放锁

//ReentrantLock下的实现
protected final boolean tryRelease(int releases) {
			//计算state
            int c = getState() - releases;
            //判断当前线程是否为持有锁的线程
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //c为0,则成功是释放锁
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //设置state
            setState(c);
            return free;
        }

 
/** 
2个状态,表示await被唤醒后,如果检查线程是中断的,就需要判断是在什么时候被中断,然后判断怎么返回这个中断,是直接异常还是设置中断状态
 */
private static final int REINTERRUPT =  1;
private static final int THROW_IE    = -1;



/**
	检查中断状态,0:未中断,
	THROW_IE:
	REINTERRUPT:
 */
private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}


/**
检查中断是在什么时候发生的,是在signal前还是signal后
*/
final boolean transferAfterCancelledWait(Node node) {
	//如果调用signal的话,先把节点的状态设置成0,再把节点从条件队列转移(enq)到AQS的等待队列
	//所以下面这个cas成功,那么这个中断肯定是发生在signal前
  if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
  		//把节点放入AQS队列,保证后面acquireQueued执行
      enq(node);
      return true;
  }
  /*
   * If we lost out to a signal(), then we can't proceed
   * until it finishes its enq().  Cancelling during an
   * incomplete transfer is both rare and transient, so just
   * spin.
   */
	/**
	到这里的话,肯定是已经发生了signal,但是signal的enq没有完成,所以自旋,让signal的enq完成,返回false
	*/   
  while (!isOnSyncQueue(node))
      Thread.yield();
  return false;

该await执行流程

  1. 判断线程中断,中断直接抛出异常
  2. 在Condition中,维护着一个条件队列,执行await方法,都会根据当前线程创建一个节点,并添加到尾部
  3. 然后释放锁,并唤醒阻塞在锁的AQS等待队列中的一个线程
  4. 然后将自己阻塞
  5. .while判断是否在AQS等待队列
  6. 在被别的线程唤醒后,将刚刚这个节点放到AQS等待队列中,接下来就是那个节点的事情,比如抢锁
  7. .中断唤醒要判断signal前还是signal后,设置怎么处理中断
  8. 紧接着就会尝试抢锁,接下来的逻辑就和普通锁一样,抢不到就阻塞,抢到了就继续执行。

Signal方法实现

调用condition的signal或者signalAll方法可以将条件队列中等待时间最长的节点移动到同步等待队列中,使得该节点能够有机会获得lock。按照等待队列是先进先出(FIFO)的,所以条件队列的头节点必然会是等待时间最长的节点,也就是每次调用condition的signal方法是将头节点移动到同步等待队列中

public final void signal() {
		//子类实现判断是否是自己拥有该锁
		//即唤醒其他线程时先确保自己拥有该锁的持有权,否则抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //获取条件队列中的第一个节点
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}


/**子类实现判断是否是自己拥有*/
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}


/**
这里从first开始释放一个condition状态的节点
*/
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
         //将头结点从等待队列中移除
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}


final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    //设置节点状态为0
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
 
    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    //将节点加入AQS的同步等待队列,返回的是加入节点的pre
    Node p = enq(node);
    int ws = p.waitStatus;
    //设置节点状态为SIGNAL,如果失败直接unpark新加入的节点
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

调用condition的signal的前提条件是当前线程已经获取了lock,该方法会使得等待队列中的头节点即等待时间最长的那个节点移入到同步队列,而移入到同步队列后才有机会使得等待线程被唤醒,即从await方法中的LockSupport.park(this)方法中返回,从而才有机会使得调用await方法的线程成功退出
在这里插入图片描述

signal只释放first的开始第一个状态为condition的节点,然后将节点加入到AQS的同步等待队列,设置新加入节点的pre的状态为SIGNAL。看下signalAll的释放:

private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

小结

JUC下的Lock时通过底层的AQS实现的,等待Lock的线程会进入AQS等待队列,其对于Node的waitStaus为SIGNAL,当执行Condition的await方法后,会将其从SIGNAL状态的等待队列中设置为CONDITION,并放入条件队列,一旦等待条件为真,则立即醒来,即其他线程调用signal/signalall方法时,设置其从条件队列到等待队列,重新去争抢锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值