同步工具-Synchronizer(2) 独占模式

Synchronizer

  铺垫了这么多,终于到了我们的Synchronizer了。其实jdk的很多阻塞工具都是基于一个通用的类构建的例如ReentrantLock,FutureTask,Semaphore,CountDownLatch等等,这个就是AbstractQueuedSynchronizer。AQS也是面试当中很容易问到的一环。
  ReentrantLock,FutureTask,Semaphore,CountDownLatch这些类都需要的是通过自身的状态来判断是否阻塞当前线程,或者是唤醒被阻塞的线程。
  1.ReentrantLock#lock(),如果当前的锁未被其他线程占有,或者是点用着本身已经占有了锁那么lock方法直接返回,否则阻塞。ReentrantLock#Unlock()则是根据当前线程是否已经不占有锁来释放那些阻塞在Lock方法里的线程。
  2.CountDownLatch#await(),则是根据自身的一个次数是否是0来决定是否阻塞当前线程,而他的CountDownLatch#countDown(),则是根据自身次数是否是0来决定是否唤醒阻塞的线程。
  所以AQS提供了一个int值state来表示状态的抽象,并提供getState,setState,compareAndSetState等方法供子类自己去控制或是判断自身状态,提供tryAcquire(独占),tryAcquireShare(共享) 利用返回值决定是否阻塞调用线程,提供与之对应的tryRelease,tryReleaseShare 利用返回值决定是否唤醒被阻塞的线程。
  下面我们先介绍一个独占模式AQS用法。

public class SimpleLatch {
	private Sync sync = new Sync();

	public void pass() {
		sync.acquire(1);
	};

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

	private static class Sync extends AbstractQueuedSynchronizer {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		@Override
		protected boolean tryAcquire(int arg) {
			// state 0表示关闭 1表示打开 默认关闭
			return getState() == 1;
		}

		@Override
		protected boolean tryRelease(int arg) {
			setState(1);
			return true;
		}
	}
}

测试代码

	public void testSimple() {
		SimpleLatch simpleLatch = new SimpleLatch();
		for (int i = 0; i < 1; i++) {
			new Thread(() -> {
				simpleLatch.pass();
				System.out.println(Thread.currentThread().getName() + " Im passed!");
			}).start();
		}

		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("begin to open");
		simpleLatch.open();
	}

结果
begin to open
Thread-0 Im passed!

SimpleLatch 是一个简单的二元闭锁。只有当状态为1时才允许线程通过,使用的是AQS提供的独占模式锁。
  下面看看AQS的源码他是怎么实现的。
AQS独占模式
1.获取

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

首先判断是否能够获取状态,可以的话直接返回。

    private Node addWaiter(Node mode) {
    	//实例化node对象,设置当前线程和独占模式。
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //尾节点不为空时 自旋添加到同步链表,并设置尾节点,然后返回
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //尾节点为空
        enq(node);
        return node;
    }
    //尾节点为空 自旋把头,尾节点指向一个新实例化的节点,自旋向后面添加入参节点
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

获取失败时加入同步队列,如果同步队列没有实例化时实例化同步队列。之后加入同步队列,node中包含当前线程,waitState=0。(其中多次使用CAS自旋保证操作一定成功)。

    final boolean acquireQueued(final Node node, int arg) {
        //是否抛出异常
        boolean failed = true;
        try {
            //是否被打断
            boolean interrupted = false;
            for (;;) {
            	//获取前驱节点
                final Node p = node.predecessor();
                //如果前驱节点是头结点 并且可以获取状态时
                if (p == head && tryAcquire(arg)) {
                	//把头结点踢出队列
                    setHead(node);
                    p.next = null; // help GC
                    设置本节点为头结点,设置操作成功,返回是否中断
                    failed = false;
                    return interrupted;
                }
                //判断当前线程是否需要休息
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    	//获取前驱节点状态
        int ws = pred.waitStatus;
        //如果前驱节点等待被唤醒 则返回true,外层方法阻塞当前线程
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        //前驱节点为cancel 那么循环删除前面所有的cancel节点。
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
        	// 0 或者 PROPAGATE,0则是当前节点还没来的及设置前驱节点状态,
        	// PROPAGATE则是共享模式中可能出现的情况
        	// 本线程设置前驱节点状态为SIGNAL。
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        //返回false 要求再次自旋。
        return false;
    }
    private final boolean parkAndCheckInterrupt() {
    	//阻塞当前线程
        LockSupport.park(this);
        //线程已被唤醒,检查是否被中断,此方法会清除中断标志位,所以在acquire方法中进行selfInterrupt()来补偿。
        return Thread.interrupted();
    }

2.释放

    public final boolean release(int arg) {
    	//如果释放成功
        if (tryRelease(arg)) {
        	//获取头结点
            Node h = head;
            //头结点部位空,并且状态不等0时(也就是状态==-1)
            if (h != null && h.waitStatus != 0)
				//注意此时头结点的后继节点已经设置前驱节点为SINGAL,
				//也就是说后继节点的线程可能已经阻塞,所以需要唤醒后继节点
                unparkSuccessor(h);
            //waitStatus ==0 也就是说后继节点还活着 还有一次tryAcquire的机会,所以不需要唤醒。
            return true;
        }
        return false;
    }
    private void unparkSuccessor(Node node) {
    	//设置入参节点的状态为0,可能会与后继节点的修改前驱节点操作产生并发,不过没关系。
    	//这里设置失败也没所谓
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        //按顺序找到后继节点
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //假如有复合规则的后继节点,唤醒他。
        if (s != null)
            LockSupport.unpark(s.thread);
    }


  所以说独占模式的话,一个线程调用release只会释放同步队列中第一个等待的线程,所以在SimpleLock的实现中,加入有多个线程调用pass,但是只有一个线程调用了open()的话,那么只有第一个线程会被释放,别的线程就尴尬了。
  好了,下一篇我们就再去描述下一非独占模式的AQS实现,也就是一个release可以释放多个阻塞的线程。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值