同步工具-ReentrantLock

ReentrantLock
  我们主要看ReentrantLock的Lock,unlock这两个公平和非公平的实现 还有Condition的实现。
其实我一直都很疑惑,为啥ReentrantLock,CountDownLatch等类都是基于AQS实现的,那么为啥不直接继承AQS而是内部引用一个AQS的实现类呢,这里ReentrantLock给出了答案,它内部提供公平和非公平的实现,而这两个模式则是根据聚合不同的AQS来实现的,假如选择直接继承AQS的话,那么要提供者2种模式的实现就必须由两个类,ReentrantFairLock,ReentrantUnfairLock。不选择继承也可以给实现类更多的选择空间去继承自己需要的。
  首先来看非公平模式的实现
  lock

        final void lock() {
        	//判断当前锁的状态 0未被获取 1已被获取
            if (compareAndSetState(0, 1))
            	//没被占据那么就设置占有的线程为自己。
            	//(AQS的父类声明了一个局部变量,表示独占模式下拥有资源的线程)
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
        final boolean nonfairTryAcquire(int acquires) {
        	//当前线程
            final Thread current = Thread.currentThread();
            //当前状态
            int c = getState();
            //这里再次判断锁是否被获取
            if (c == 0) {
            	//自旋是否能够获取锁
                if (compareAndSetState(0, acquires)) {
                	//获取成功,设置当前线程为占有线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果锁被占有,并且占有线程时自己的话,
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                //int溢出直接抛异常
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //更新锁获取的次数,因为只有当前线程会更新锁的状态,所以不用担心多线程影响state
                setState(nextc);
                return true;
            }
            return false;
        }

这里我们可以看到当锁已经占有时,nonfairTryAcquire中会判断占有线程是否是自己,如果是的话并不会阻塞当前线程,而是更新的state,也就是调用一次lock则state+=1。这里就是占有锁的线程多次调用Lock而不会被阻塞的原因。可以预测后面的unlock方法也是每调用一次state-=1,说明了如果需要解除锁的话,当前线程调用了几次Lock就得调用几次unlock。
  unlock

    public void unlock() {
        sync.release(1);
    }

release的代码上次独占模式的AQS中讲过,这里直接看tryRelease。

        protected final boolean tryRelease(int releases) {
        	//和预测的一样获取锁-1之后的状态
            int c = getState() - releases;
            //判断是否是获取锁的线程调用,如果不是那就直接丢异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //状态为0那就是锁被当前线程释放了,清除表示占有锁的线程的引用即可。
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //设置锁的状态 也就是减少一次锁的获取。
            setState(c);
            //如果是0那就返回true,表示需要唤醒同步队列中下一个等待的线程,不是0那就是说当前线程还占有锁。
            return free;
        }

可以看到非公平锁的代码非常简单。
公平锁实现
lock

        final void lock() {
            acquire(1);
        }
        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

可以看到这个方法和非公平的实现区别与一个方法hasQueuedPredecessors,我看他的实现

public final boolean hasQueuedPredecessors() {
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    //头等于尾的话 就说明同步队列为空,或者当前节点的线程已经获取锁在执行了,
    //又或者是当前线程就是头结点的线程,无论哪种情况,都说明当前线程之前并没有线程在休眠等待获取锁

	//如果头不等于尾,先判断老二是否已经成功入队,如果没有入队说明那就返回true,不进行当前线程和老二之
	//间的获取顺序控制,再判断老二是不是本线程的节点(这个判断很奇怪,已经入队了的老二怎么可能本方法的
	//调用线程呢?老二只有在唤醒之后踢掉头结点之后才能返回acquire方法,这时候老二就不是老二了,我想到的
	//解释就是其他调用者需要这个条件)
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

如果当前线程之前有排着队的线程,返回true。如果同步队列为空,或者当前线程是第一个节点,返回false。简单的来说是判断当前线程的优先级的。那就明确了,也就是说公平锁模式下,只是根据完全进入同步队列的时间(入队先后顺序)。非公平锁,入队之后的线程获取锁是按照先后顺序执行的,但是放锁之后,新的准备抢占锁的线程(未入队的线程) 可能比队列中的排队的第一个(释放锁之前的老二节点)早获取锁。
  非公平锁的就是比公平锁更加快速把,在高并发的情况下,减少休眠,唤醒的操作,也就是减少了线程切换(后来的线程可能直接获取到锁,而不需要进行休眠,让出cpu时间),上下文切换。减少了很多cas操作,降低cpu消耗。
  Condition类了
  假如现在有一个情形,我有个钱包,里面有5000块钱,但是我这个钱包最多只能装10000块。设计一个并发的容器,当有多个线程给钱包拿钱加钱的情况。
  下面是内部锁实现

public class Wallet_Sync {
	private int money = 5000;
	private final int max;

	public Wallet_Sync(int max) {
		super();
		this.max = max;
	}

	public void addMoney(int param) throws InterruptedException {
		if (money < 0) {
			throw new IllegalArgumentException("input cannot be negative");
		}
		synchronized (this) {
			System.out.println("begin addMoney " + param);
			while (param + money > max) {
				this.wait();
				System.out.println(Thread.currentThread()+"wake");
			}
			money -= param;
			this.notifyAll();
			System.out.println("end addMoney " + param);
		}
	}

	public void getMoney(int param) throws InterruptedException {
		if (money < 0) {
			throw new IllegalArgumentException("input cannot be negative");
		}
		synchronized (this){
			while (money - param < 0) {
				this.wait();
			}
			money -= param;
			this.notifyAll();
		}
	}
}
	public static void testWaitNotify() throws InterruptedException {
		Wallet_Sync wallet_Sync = new Wallet_Sync(10000);
		new Thread(() -> {
			try {
				wallet_Sync.addMoney(6000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();

		Thread.sleep(2 * 1000);

		new Thread(() -> {
			try {
				wallet_Sync.addMoney(4000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();
	}

Thread[Thread-0,5,main]begin addMoney 6000
Thread[Thread-1,5,main]begin addMoney 4000
Thread[Thread-1,5,main]end addMoney 4000
Thread[Thread-0,5,main]wake
  结果可见,尽管不满足条件,第一个线程还是被唤醒了。内部锁是使用单一的同步队列实现阻塞的。当状态发生改变时,会唤醒所有等待状态变更的线程,被唤醒的线程等待的并不是该条件,或者是线程被唤醒之后,获取锁之前,该状态又被其他线程改变,从而又进行反复的休眠,唤醒操作,消耗性能。

public class Wallet_Condition{
	private ReentrantLock lock = new ReentrantLock();
	private int money = 5000;
	private final int max;
	private Condition notEnough = lock.newCondition();
	private Condition alReadyMax = lock.newCondition();

	public Wallet_Condition(int max) {
		super();
		this.max = max;
	}

	public void addMoney(int param) throws InterruptedException {
		if (money < 0) {
			throw new IllegalArgumentException("input cannot be negative");
		}
		lock.lock();
		try {
			System.out.println(Thread.currentThread() + "begin addMoney " + param);
			while (param + money > max) {
				alReadyMax.await();
				System.out.println(Thread.currentThread() + "wake");
			}
			money += param;
			notEnough.signal();
			System.out.println(Thread.currentThread() + "end addMoney " + param);
		} finally {
			lock.unlock();
		}
	}

	public void getMoney(int param) throws InterruptedException {
		if (money < 0) {
			throw new IllegalArgumentException("input cannot be negative");
		}
		lock.lock();
		try {
			while (money - param < 0) {
				notEnough.await();
			}
			money -= param;
			alReadyMax.signal();
		} finally {
			lock.unlock();
		}
	}
}
	@Test
	public static void testCondition() throws InterruptedException {
		Wallet_Condition wallet_Condition = new Wallet_Condition(10000);
		new Thread(() -> {
			try {
				wallet_Condition.addMoney(6000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();

		Thread.sleep(2 * 1000);

		new Thread(() -> {
			try {
				wallet_Condition.addMoney(4000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();
	}

Thread[Thread-0,5,main]begin addMoney 6000
Thread[Thread-1,5,main]begin addMoney 4000
Thread[Thread-1,5,main]end addMoney 4000

可见,第一个线程将不会被第二线程所唤醒。这里的显示锁实现可以见到模式和内部锁几乎一样,不一样的是显式锁可以设置多个同步队列,每个同步队列中的线程休眠,唤醒的条件可以不同。所以这里可以使用signal,因为是独占锁,只有一个线程能获取资源,而且时指定唤醒某个队列的线程,所以不用担心信号丢失的问题。所以不需要signalAll。signal与notify相比可以说是高效了非常多,notifyAll最坏的情况是O(N^2)时间复杂度,而signal则是O(1)。
  下面可以看看源码实现。

    public Condition newCondition() {
        return sync.newCondition();
    }
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
public class ConditionObject implements Condition, java.io.Serializable{
	//省略代码
	public ConditionObject() { }
}

ConditionObject 其实是AQS中的一个非静态内部类,所以外部的AQS对象会持有一个ConditionObject 对象的引用。

先看await方法


        /**
         * Implements interruptible condition wait.
         * <ol>
         * <li> If current thread is interrupted, throw InterruptedException.
         * <li> Save lock state returned by {@link #getState}.
         * <li> Invoke {@link #release} with saved state as argument,
         *      throwing IllegalMonitorStateException if it fails.
         * <li> Block until signalled or interrupted.
         * <li> Reacquire by invoking specialized version of
         *      {@link #acquire} with saved state as argument.
         * <li> If interrupted while blocked in step 4, throw InterruptedException.
         * </ol>
         */
        public final void await() throws InterruptedException {
        	//是否中断,中断则抛出异常
            if (Thread.interrupted())
                throw new InterruptedException();
            //在Condition队列中加入当前节点
            Node node = addConditionWaiter();
            //释放所有当前线程获取的锁,因为await的触发肯定是对象的状态不满足执行条件,所以释放锁,从而使得
            //其他线程能够获取锁 从而改变对象的状态,然后再唤醒对应的等待线程。
            int savedState = fullyRelease(node);
            //interruptMode 0-没有发生中断 THROW_IE(1)-中断的发生在signal之前,REINTERRUPT(-1)-中断的发生
            //在signal之后  看方法注释  If interrupted while blocked in step 4, throw InterruptedException.
            int interruptMode = 0;
            //判断是否在同步队列,
            while (!isOnSyncQueue(node)) {
            	//不在同步队列那么就发生阻塞
                LockSupport.park(this);
                //阻塞结束之后 判断是由中断还是signal引起的
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //这里的话node已经是进入了同步队列了 acquireQueued方法阻塞当前线程 直到node节点成为同步队列
            //的头结点也就是获取了锁的状态savedState则是state增加的次数。返回值表示在这个过程中是否发生中断
            //如果没发生中断,那么interruptMode 就不变。如果发生了中断并且在condition队列的自旋中没有发生中断
            //或者中断在signal之后,那么就interruptMode =REINTERRUPT
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            //去掉在condiiton中cancel掉的节点.
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            //如果发生中断,根据interruptMode 做出具体的反应
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
        /**
         * Adds a new waiter to wait queue.
         *  
         * @return its new wait node
         */
        private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            //清除cancel的节点
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //加入节点,队列为空就初始化队列,不空就往后面加,这里的时候线程还没有释放锁,所以不用考虑并发
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
        	//获取当前sync的状态如果是reentrantlock的话就是当前线程获取了多少次锁
            int savedState = getState();
            //全部释放,这是释放一定要成功的,失败就抛异常
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }
    final boolean isOnSyncQueue(Node node) {
    	//如果节点状态是CONDITION的话,直接返回false,因为当节点被signal时会修改CONDITION为0,然后enq
    	//加入同步队列。这里返回false使得线程park
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        //如果有next那一定是在同步队列了
        if (node.next != null) // If has successor, it must be on queue
            return true;
        /*
         * node.prev can be non-null, but not yet on queue because
         * the CAS to place it on queue can fail. So we have to
         * traverse from tail to make sure it actually made it.  It
         * will always be near the tail in calls to this method, and
         * unless the CAS failed (which is unlikely), it will be
         * there, so we hardly ever traverse much.
         */
         //前两个判断都没返回的话,就在同步队列中一个一个找,如果找到本身的话就返回true
        return findNodeFromTail(node);
    }
	//一个一个在同步队列中找,在到就返回true
    private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }
        private int checkInterruptWhileWaiting(Node node) {
        	//返回中断情况,没发生中断返回0,发生了中断就transferAfterCancelledWait方法判断
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }
	//此方法用于判断中断发生在signal之前还是之后,signal之前 则checkInterruptWhileWaiting返回throw_ie通知上层
	//抛出异常,REINTERRUPT那么就通知上层重新设置标志位   因为Thread.interrupted()会重置中断状态
    final boolean transferAfterCancelledWait(Node node) {
    	//设置node状态是否成功,在signal之后,调用signal的线程会设置node的状态为0,所以如果这里判断成功的话
    	//那么中断发生于signal之前,因为状态没有发生改变嘛!
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        	//进入同步队列,然后返回true,通知上层抛ie。
            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,把node置入同步队列。这里方法退出的时候,马上会
        //调用acquireQueued,所以此方法退出前要确保node已经在同步队列了。所以这里如果不在同步队列的话就让出
        //cup时间,等enq完成。
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }
        //可以看到,根据mode来判断具体是抛中断异常还是重置中断状态
        private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }
    
	    static void selfInterrupt() {
	        Thread.currentThread().interrupt();
	    }

signal

        public final void signal() {
        	//先判断是不是独占模式,不是直接丢异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
		//按顺序的找到不是cancel的节点,调用transferForSignal
        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.
         */
        //上面也说了,线程调用signal时会修改node的状态为0,失败的话说明node已经cancel
        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).
         */
        //调用enq 进入同步队列
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

condition的代码就到这里了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值