队列同步器
- 同步队列
同步器依赖内部的同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个Node并将器加入到同步队列中,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。 - 独占式同步状态释放与获取
在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列(或停止自旋)的条件是前驱结点为头节点且成功获取了同步状态(tryAcquire成功)。在释放同步状态时,同步器调用 tryRelease(int args) 方法释放同步状态,然后唤醒头结点的后继节点。 - 共享式同步状态获取与释放
共享式获取与独占式获取最主要的区别在于同一时刻能够有多个线程同时获取到同步状态。在共享式获取的过程中,成功获取到同步状态并退出自旋的条件是 tryAcquireShared(int arg) 方法返回值大于等于0。在 doAcquireShared(int arg) 方法自旋的过程中,如果当前节点的前驱结点为头结点时,尝试获取同步状态,如果返回值大于等于0,表示该次获取同步状态成功并从自旋过程中 退出。释放同步状态主要通过调用 releaseShared(int arg) ,该方法在释放同步状态后,将会唤醒后续处于等待状态的节点,对于能够支持多个线程访问的并发组件来说,它和独占式获取的主要区别是 tryReleaseShared(int arg) 方法必须确保同步状态(或者资源数)线程安全释放,一般是通过循环和CAS来保证的,因为释放的过程会同时来自多个线程。 - 独占式超时获取同步状态
通过调用 doAcquireNanos(int arg, long nanosTimeout) 方法来实现的。当节点的前驱结点为头结点时尝试获取同步状态,如果获取成功则从方法中返回,这个过程和独占式同步获取的过程相似,但是在同步状态获取失败的处理上有所不同。如果当前节点获取同步状态失败,则判断是否超时(nanosTimeout小于等于0表示已经超时), 如果没有超时,重新计算nanosTimeout ,然后当前线程等待 nanosTimeout 纳秒。
读写锁
读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大的提升。
- 读写状态的设计
读写锁同样依赖自定义同步器来实现同步功能,而读写状态就是其同步器的同步状态。由于同步状态的变量是一个int类型的值,如果在一个整形变量上维护多种状态,就一定需要“按位切割使用”这个变量,读写锁被切分成了两个部分,高16位表示读,低16位表示写。 根据状态的划分,能得出一个推论:假设当前同步状态为S,那么当S不等于0 时,当写状态(S & 0x0000FFFF) 等于0 时,则读状态(S >>> 16)大于0时,即读锁已经被获取。 - 写锁的获取与释放
写锁是一个支持重进入的排他锁,如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁的时候,读锁已经被获取(读状态不为0)或者该线程不是已经获取了写锁的线程,则当前线程进入等待状态。protected final boolean tryAcquire(int acquires) { /* * Walkthrough: * 1. If read count nonzero or write count nonzero * and owner is a different thread, fail. * 2. If count would saturate, fail. (This can only * happen if count is already nonzero.) * 3. Otherwise, this thread is eligible for lock if * it is either a reentrant acquire or * queue policy allows it. If so, update state * and set owner. */ Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }
写锁的释放与ReentrantLock 的释放过程基本类似,每次释放均减少写状态,当写状态为0 时表示写锁已经被释放,从而等待的读写线程能够继续访问读写锁,同时前次写线程的修改对后续读写线程可见。
- 读锁的释放与获取
读锁是一个支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问(或者写状态是0)的时候,读锁总是会被成功的获取到,而所做的也只是(线程安全的)增加读状态。简化的读锁获取过程如下:protected final int tryAcquireShared(int unused) { for(;;) { int c = getState(); int nextc = c + (1 << 16); if(nextc < 0) { throw new Error("Maximum lock count exceeded."); } if(exclusiveCount != 0 && owner != Thread.currentThread()) return -1; if(compareAndSetState(c, nextc)) { return 1; } } }
在该方法中,如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态。如果当前线程获取了写锁或者写锁未被获取,则当前线程(线程安全,依靠CAS保证)增加读状态,成功获取读锁。
- 锁降级
锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有的)写锁,在获取到读锁,随后释放(先前拥有的)写锁的过程。- Condition接口
Condition定义了等待/通知两种类型的方法,当前线程调用这些方法的时候,需要提前获取到Condition对象的锁。Condition对象是Lock对象创建出来的。也就是说,Condition对象是依赖Lock对象的。下面是典型的使用Condition接口的方法:public class BoundedQueue<T> { private int count, addIndex, removeIndex; private Object[] items; private Lock lock = new ReentrantLock(); private Condition notEmpty = lock.newCondition(); private Condition notFull = lock.newCondition(); public BoundedQueue(int size) { items = new Object[size]; } public void add(T t) throws InterruptedException { lock.lock(); try { while(count == items.length) { notFull.await(); } items[addIndex] = t; if(++addIndex == items.length) { addIndex = 0; } ++count; notEmpty.signal(); } finally { lock.unlock(); } } public T remove() throws InterruptedException { lock.lock(); try { while(count == 0) { notEmpty.await(); } Object t = items[removeIndex]; if(++removeIndex == items.length) { removeIndex = 0; } --count; notFull.signal(); return (T) t; } finally { lock.unlock(); } } }
ConditionObject是同步器AbstractQueuedSynchronizer 的内部类,因为Condition的操作需要获取相关的锁, 所以作为同步器的内部类也较为合理。每个Condition对象都包含着一个队列(以下称为等待队列),该队列是Condition对象实现等待/通知功能的关键。在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中Lock(更确切的说是同步器)拥有一个同步队列和多个等待队列。
- Condition接口