介绍前面我们介绍了《看了CopyOnWriteArrayList后自己实现了一个CopyOnWriteHashMap》关于CopyOnWrite容器,但是它也有一些缺点:内存使用问题:因为CopyOnWrite的copy-on-write机制,每次写操作都会有两个内存数组对象,如果数组对象占用内存大,频繁写入会导致频繁的YongGC和FullGC数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。读操作的线程可能不会立即读取新修改的数据,因为修改操作发生在副本上。但最终修改操作会完成并更新容器,因此这就是最终一致性。当时说我们可以用Collections.synchronizedList()来解决这两个缺点。找一个无非就是在列表的增删改查方法中加入synchronized实现。我们知道synchronized其实就是排他锁(exclusivelock)。如果你不知道排他锁是什么,可以看这篇这篇文章,基本上介绍了java中所有的锁。但是这样的话,就会出现性能问题。如果读多写少,每次读都需要获取锁,读完再释放锁。这将导致每个读取请求都获取锁。但是读不会造成数据不安全,会造成性能瓶颈。为了解决这个问题,出现了一种新的锁,ReadWriteLock。什么是读写锁根据名字我们也可以大致猜到,就是有两种锁,分别是读锁和写锁。一个读锁可以让多个读线程同时获取,但是当一个写线程访问它时,所有的读线程和其他写线程都会被阻塞。同一时刻只有一个写线程能够成功获取到写锁,其他的都会被阻塞。读写锁实际上维护了两把锁,读锁和写锁,以读锁和写锁来区分。在读多写少的情况下,并发性相比排他锁有很大的提升。java中读写锁的实现是ReentrantReadWriteLock,它有以下特点:公平选择:支持非公平(默认)和公平两种锁获取方式,吞吐量还是不公平大于公平;Reentrant:支持可重入Input,读锁获取后可以再次获取,写锁获取后可以再次获取写锁,也可以同时获取读锁;锁降级:按照获取写锁,获取读锁,释放写锁的顺序,写锁可以降级为读锁的使用ReentrantReadWriteLock以官网https://docs.oracle.com/为例javase/8/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html看看它是如何使用的privatefinalLockr=rwl.readLock();privatefinalLockw=rwl.writeLock();publicDataget(Stringkey){r.lock();try{returnm.get(key);}finally{r.unlock();}}publicString[]allKeys(){r.lock();try{returnm.keySet().toArray();}finally{r.unlock();}}publicDataput(Stringkey,Datavalue){w.lock();try{returnm.put(key,value);}finally{w.unlock();}}publicvoidclear(){w.lock();try{m.clear();}finally{w.unlock();}}}这是使用起来非常简单明了。和ReentrantLock的用法基本一样。它在写时获取写锁,写完后释放写锁。,读的时候获取读锁,读完释放读写。读写锁的实现分析我们知道,ReentrantLock是通过状态来控制锁的状态的,前面介绍的《Java高并发编程基础三大利器之Semaphore》《Java高并发编程基础三大利器之CountDownLatch》《Java高并发编程基础三大利器之CyclicBarrier》就是通过状态来实现的。ReentrantReadWriteLock无疑是通过AQS的state来实现的,但是state是一个int值怎么来读锁和写锁。读写锁状态的实现分析如果看过线程池的源码就知道,线程池的状态和线程数是由一个int类型的原子变量控制的(高3位保存运行状态,低29位保存线程数)的。同样的ReentrantReadWriteLock也是通过一个状态的高16位和低16位分别控制读状态和写状态。我们来看看它是如何通过一个字段实现读写分离的,staticfinalintSHARED_SHIFT=16;staticfinalintSHARED_UNIT=(1<
