JDK源码——java.util.concurrent(四)

来源:互联网 发布:mac chrome 全屏 编辑:程序博客网 时间:2024/05/21 14:04

测试代码:
https://github.com/kevindai007/springboot_houseSearch/tree/master/src/test/java/com/kevindai/juc

ReadWriteLock

先看看ReadWriteLock的用法和特点

public class ReadWriteLockTest {    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    public static void main(String[] args) throws InterruptedException {        ReadWriteLockObject data = new ReadWriteLockObject();        ReadWriteLockThread r1 = new ReadWriteLockThread(data,false);        ReadWriteLockThread r2 = new ReadWriteLockThread(data,false);        ReadWriteLockThread r3 = new ReadWriteLockThread(data,true);        Thread t1 = new Thread(r1);        Thread t2 = new Thread(r2);        Thread t3 = new Thread(r3);        t1.start();        t2.start();        t3.start();    }    static class ReadWriteLockThread implements Runnable{        boolean flag;        ReadWriteLockObject object;        public ReadWriteLockThread(ReadWriteLockObject object,boolean flag){            this.object = object;            this.flag = flag;        }        @Override        public void run() {            if(flag){                try {                    object.set();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }else {                try {                    object.get();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }    static class ReadWriteLockObject{        private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();        private Lock readLock = readWriteLock.readLock();        private Lock writeLocck = readWriteLock.writeLock();        public void set() throws InterruptedException {            writeLocck.lock();            System.out.println("begin set" + sdf.format(new Date()));            TimeUnit.SECONDS.sleep(5);            System.out.println("end set" + sdf.format(new Date()));            writeLocck.unlock();        }        public void get() throws InterruptedException {            readLock.lock();            System.out.println("begin get" + sdf.format(new Date()));            TimeUnit.SECONDS.sleep(5);            System.out.println("end get" + sdf.format(new Date()));            readLock.unlock();        }    }}

通过这个demo可以看出,ReadWriteLock的读锁可以同时运行,不会阻塞;写锁会阻塞.

ReadWriteLock的实现是ReentrantReadWriteLock,也是通过AQS来实现的,ReentrantReadWriteLock中有一个Sync类,Sync继承自AQS,读锁、写锁均是基于此实现的.

ReentrantReadWriteLock最关键的就是Sync类;咱们前面说过AQS中有一个状态变量state来表示锁状态,但读锁、写锁如何用一个变量表示呢?这里不得不佩服前辈们的天才,他们用state的前16位表示读锁,用后16位表示写锁,所以无论是读锁还是写锁最多只能被持有2^16次.在判断读锁和写锁的时候,需要进行位运算:

  1. 由于读写锁共享同一个状态变量,所以状态不为0,只能说明是有锁,可能是读锁,也可能是写锁
  2. 读锁是高16为表示的,所以读锁加1,就是状态的高16位加1,低16位不变,所以要加的不是1,而是2^16,减一同样是这样。
  3. 写锁用低16位表示,要获得写锁的次数,要用状态&2^16-1(16个1),结果的高16位全为0,低16位就是写锁被持有的次数。

咱们来看看Sync的代码

abstract static class Sync extends AbstractQueuedSynchronizer {         private static final long serialVersionUID = 6317671515068378041L;         //最多支持65535个写锁和65535个读锁;低16位表示写锁计数;高16位表示持有读锁的线程数         static final int SHARED_SHIFT   = 16;         //由于读锁用高位部分,读锁个数加1,其实是状态值加 2^16         static final int SHARED_UNIT    = (1 << SHARED_SHIFT);         static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;         /**写锁的掩码,用于状态的低16位有效值 */         static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;         /** 读锁计数,当前持有读锁的线程数,c的高16位 */         static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }         /** 写锁的计数,也就是它的重入次数,c的低16位*/         static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }         /**         * 每个线程持有读锁的计数         */         static final class HoldCounter {             int count = 0;             //使用id而不是引用是为了避免保留垃圾。注意这是个常量。             final long tid = Thread.currentThread().getId();         }         /**         * 采用继承是为了重写 initialValue 方法,这样就不用进行这样的处理:         * 如果ThreadLocal没有当前线程的计数,则new一个,再放进ThreadLocal里。         * 可以直接调用 get。         * */         static final class ThreadLocalHoldCounter             extends ThreadLocal<HoldCounter> {             public HoldCounter initialValue() {                 return new HoldCounter();             }         }         /**         * 当前线程持有的可重入读锁的数量,仅在构造方法和readObject(反序列化)         * 时被初始化,当持有锁的数量为0时,移除此对象。         */         private transient ThreadLocalHoldCounter readHolds;         /**         * 最近一个成功获取读锁的线程的计数。这省却了ThreadLocal查找,         * 通常情况下,下一个释放线程是最后一个获取线程。这不是 volatile 的,         * 因为它仅用于试探的,线程进行缓存也是可以的         * (因为判断是否是当前线程是通过线程id来比较的)。         */         private transient HoldCounter cachedHoldCounter;         /**firstReader是第一个获得读锁的线程;         * firstReaderHoldCount是firstReader的重入计数;         * 更准确的说,firstReader是最后一个把共享计数从0改为1,并且还没有释放锁。         * 如果没有这样的线程,firstReader为null;         * firstReader不会导致垃圾堆积,因为在tryReleaseShared中将它置空了,除非         * 线程异常终止,没有释放读锁。         *          * 跟踪无竞争的读锁计数时,代价很低         */         private transient Thread firstReader = null;         private transient int firstReaderHoldCount;         Sync() {             readHolds = new ThreadLocalHoldCounter();             setState(getState()); // ensures visibility of readHolds         }

它的两个子类,FairSync和NonFairSync比较简单,它们就是决定在某些情况下读锁或者写锁是否需要阻塞,通过两个方法的返回值决定:

    static final class NonfairSync extends Sync {        private static final long serialVersionUID = -8159625535654395037L;        final boolean writerShouldBlock() {            return false; // writers can always barge        }        final boolean readerShouldBlock() {            return apparentlyFirstQueuedIsExclusive();        }    }    /**     * Fair version of Sync     */    static final class FairSync extends Sync {        private static final long serialVersionUID = -2274990926593161451L;        final boolean writerShouldBlock() {            return hasQueuedPredecessors();        }        final boolean readerShouldBlock() {            return hasQueuedPredecessors();        }    }


读锁是共享锁,同一时刻可以被多个线程获得,下面是获得读锁的代码:

/**  * 获取读锁,如果写锁不是由其他线程持有,则获取并立即返回;  * 如果写锁被其他线程持有,阻塞,直到读锁被获得。  */  public void lock() {      sync.acquireShared(1);  }  

acquireShared()方法位于AQS中,代码如下

/**  * 以共享模式获取对象 */  public final void acquireShared(int arg) {      if (tryAcquireShared(arg) < 0)          doAcquireShared(arg);  }  

其中tryAcquireShared()方法又位于AQS的实现Sync中

protected final int tryAcquireShared(int unused) {              Thread current = Thread.currentThread();              int c = getState();              //持有写锁的线程可以获得读锁              if (exclusiveCount(c) != 0 &&                  getExclusiveOwnerThread() != current)                  return -1;//写锁被占用,且不是由当前线程持有,返回-1              //执行到这里表明:写锁可用,或者写锁由当前线程持有              //获得读锁的数量              int r = sharedCount(c);              /** 如果不用阻塞,且没有溢出,则使用CAS修改状态,并且修改成功 */              if (!readerShouldBlock() &&                  r < MAX_COUNT &&                  compareAndSetState(c, c + SHARED_UNIT)) {//修改高16位的状态,所以要加上2^16                  //这是第一个占有读锁的线程,设置firstReader                  if (r == 0) {                      firstReader = current;                      firstReaderHoldCount = 1;                  } else if (firstReader == current) {//重入计数加1                      firstReaderHoldCount++;                  } else {                      // 非 firstReader 读锁重入计数更新                      //将cachedHoldCounter设置为当前线程                      HoldCounter rh = cachedHoldCounter;                      if (rh == null || rh.tid != current.getId())                          cachedHoldCounter = rh = readHolds.get();                      else if (rh.count == 0)                          readHolds.set(rh);                      rh.count++;                  }                  return 1;              }              //获取读锁失败,放到循环里重试              return fullTryAcquireShared(current);          }  

重点关注Sync中的tryAcquireShared(),注意,在所有的读写锁中,获取锁和释放锁每次都是一个计数行为,锁其计数都是1,而在获得读锁的过程中,参数根本就没有意义.上面的代码包含的逻辑:
1、如果当前写锁被其他线程持有,则获取读锁失败;
2、写锁空闲,或者写锁被当前线程持有(写锁可降级为读锁),在公平策略下,它可能需要阻塞,那么tryAcquireShared()就可能失败,则需要进入队列等待;如果是非公平策略,会尝试获取锁,使用CAS修改状态,修改成功,则获得读锁,否则也会进入同步队列等待;
3、进入同步队列后,就是由AQS来完成唤醒。

下面咱们看一看读锁的释放过程,咱们直接看Sync的实现:

 protected final boolean tryReleaseShared(int unused) {            Thread current = Thread.currentThread();            /**             * 当前线程是第一个获取到锁的,如果此线程要释放锁了(firstReaderHoldCount==1),则firstReader置空             * 否则,将线程持有的锁计数减1             */            if (firstReader == current) {                // assert firstReaderHoldCount > 0;                if (firstReaderHoldCount == 1)                    firstReader = null;                else                    firstReaderHoldCount--;            } else {                HoldCounter rh = cachedHoldCounter;                //如果最近一个读取锁的线程为空,后者不是当前线程                if (rh == null || rh.tid != current.getId())                    //获取当前线程持有的锁的数量                    rh = readHolds.get();                int count = rh.count;                if (count <= 1) {                    readHolds.remove();                    if (count <= 0)                        throw unmatchedUnlockException();                }                --rh.count;            }            //cas+自旋更改状态(可能有其他的线程也在释放读锁)            for (;;) {                int c = getState();                //高16位-1                int nextc = c - SHARED_UNIT;                if (compareAndSetState(c, nextc)                    return nextc == 0;            }        }

释放读锁很简单,就是把状态的高16位减1,同时把当前线程持有锁的计数减1。在释放的过程中,其他线程可能也在释放读锁,所以修改状态有可能失败,因此利用cas+自旋,直到成功为止.

下面看看写锁的获取,一样咱们直接看Sync中的实现:

        protected final boolean tryAcquire(int acquires) {            Thread current = Thread.currentThread();            int c = getState();            //获取写锁被持有的次数,通过与低16位做与操作得到            int w = exclusiveCount(c);            if (c != 0) {                // c!=0,w==0,说明读锁存在                  //w != 0 && current != getExclusiveOwnerThread() 表示其他线程获取了写锁                if (w == 0 || current != getExclusiveOwnerThread())                    return false;                //如果超过最大限制,则抛出异常                if (w + exclusiveCount(acquires) > MAX_COUNT)                    throw new Error("Maximum lock count exceeded");                //执行到这里,说明存在写锁,且由当前线程持有                  //重入计数(写锁可重入、互斥)                setState(c + acquires);                return true;            }           //执行到这里,说明不存在任何锁             //WriterShouldBlock留给子类实现公平策略             //使用CAS修改状态             if (writerShouldBlock() ||                !compareAndSetState(c, c + acquires))                return false;            setExclusiveOwnerThread(current);            return true;        }

主要逻辑:
(1)首先获得锁状态,保存到c中,获得写锁的计数保存到w中;这个时候需要根据c的值来判断是否存在锁
(2)如果c!=0,说明存在锁,如果w==0,说明存在读锁,获取写锁不能成功;如果w!=0,但是写锁是由其他线程持有的,那么当前线程获取写锁也不能成功;只有c!=0,w!=0,且写锁是由当前线程持有的,才能获得成功
(3)如果c==0,说明不存在锁,如果是公平策略,还需要进入同步队列;如果是非公平策略,会尝试获得写锁

下面看看写锁的释放,一样直接看Sycn中的实现

protected final boolean tryRelease(int releases) {              if (!isHeldExclusively())                  throw new IllegalMonitorStateException();              int nextc = getState() - releases;              boolean free = exclusiveCount(nextc) == 0;              //如果锁是可用的              if (free)                  setExclusiveOwnerThread(null);              setState(nextc);              return free;          } 

释放写锁很简单,就是状态的低16位减1,如果为0,说明写锁可用,返回true,如果不为0,说明当前线程仍然持有写锁,返回false;

0 0