前言
本篇简单介绍Android以及Java提供了的数据并发类。
什么是锁
并发锁分为两种,一种是悲观锁synchronized,认为所有的访问都存在竞争关系,所以直接在底层阻塞当前线程,竞争规则不可控,所以效率相对较低。
第二种是乐观锁:优先非竞争关系,例如首次访问,认为不存在竞争关系,所以只是标记该对象处于访问中,并未加锁,此时第二线程并发访问,认为当前访问者很快就会访问结束,所以原地等待(自旋锁),也并未上锁。乐观锁是上层实现,可控性好,使用得当,效率比悲观锁更高,但是等待时间过长,占用CPU的时间也较多,导致效率不佳。
ReentrantLock
ReentrantLock的是上层实现的乐观锁,内部维护了请求访问的线程队列,再细分为公平锁和非公平锁:
公平锁:每次访问都需要重新排队。
非公平锁:如果当前线程已经获得锁,再次申请直接允许,不会出现竞争。
CountDownLatch
乐观锁的一种,优点是可以自由控制上锁和解锁的时间。只可以使用一次:
// 构造参数表示需要countDown2次
final CountDownLatch latch = new CountDownLatch(2);
// 等待countDown结束
latch.await();
// countDown调用两次,代码才会从await的位置继续运行
latch.countDown();
latch.countDown();
CountDownLatch的使用更适合类似倒计时的场景。
CyclicBarrier
乐观锁的一种,与CountDownLatch不同的是:可以反复使用:
// 构造函数表示等待的次数
CyclicBarrier barrier = new CyclicBarrier(3);
// 当await调用3次后,才会继续执行之后的代码
barrier.await();
CyclicBarrier的使用场景更适合类似等待的场景。
ConcurrentHashMap
ConcurrentHashMap的整体结构于HashMap类似,在Java 8以前采用:哈希表 + 链表的形式保存数据,采用分段锁(乐观锁,继承ReentrantLock)控制写入的并发。缺点在于节点Hash碰撞率高的时候,查找效率低下。
在Java 8 以后采用:哈希表 + (链表/红黑树)的形式保存数据。红黑树可以提高节点Hash碰撞高时,查找数据的效率。抛弃分段锁,直接使用synchronized控制单节点并发。获取数据无加锁,所以不保证获取的数据一定是最新的。
CopyOnWriteArrayList
采用写入时复制的方法解决并发问题:
数据写入时,拷贝一份当前数据,然后把新数据加入,最后把当前数据的指针指向新的数组。
并发锁使用synchronized。获取数据无加锁,所以不能保证获取的数据一定是最新的。
Collections工具
如果对于数据同步的要求必须是最新的,Java提供了Collections工具类对集合直接上锁,相当于对集合的所有的方法都上锁,以下是提供了Collections提供的方法:
synchronizedCollection
synchronizedSet
synchronizedNavigableMap
synchronizedCollection
synchronizedSet
synchronizedNavigableSet
synchronizedSortedSet
synchronizedList
synchronizedList
synchronizedMap
synchronizedSortedMap