1.普通锁

只有lock功能, Java实现:ReentrantLock lock = new ReentrantLock();

  • lock和unlock:
lock.lock();
lock.unlock();

2.读写锁

读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥。

总之,读的时候上读锁,写的时候上写锁! Java里面的实现:ReentrantReadWriteLock

ReadWriteLock mylock = new ReentrantReadWriteLock(false);
  • 读锁lock、unlock:
myLock.readLock().lock();
myLock.readLock().unlock();
  • 写锁lock、unlock:
myLock.writeLock().lock();
myLock.writeLock().unlock();

3.总结

读写锁的本意是分别对读写状态进行互斥区分,有互斥时才加锁,否则放行.

互斥的情况有:

  1. 读写互斥.
  2. 写写互斥.

不互斥的情况是:读读,

读写锁使用更加高效,尤其适用于读线程多于写线程的场景。

4.CopyOnWrite

  • 使用场景:并发少量的写,多个读(确实适合Kafka的应用场景)。
  • JDK源码有CopyOnWriteArrayList和 CopyOnWriteArraySet的实现
  • Kafka的CopyOnWriteMap是自己实现的,基于lock的;另外还有基于CAS实现的,比如com.sun.jersey.client.impl.CopyOnWriteHashMap,具体实现暂且不讨论。

CopyOnWriteArrayList的核心思想是利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性,当然写操作的锁是必不可少的了。

Kafka-0.10的队列RecordAccumulator使用了自实现的CopyOnWriteMap.关键点如下:

  • map声明: volatile
  • read操作不加锁
  • put操作加锁:synchronized

A simple read-optimized map implementation that synchronizes only writes and does a full copy on each modification

private volatile Map<K, V> map; // volatile
this.map = Collections.unmodifiableMap(map); // 不可变map public V get(Object k) { // read操作不加锁
return map.get(k);
} // put操作加锁,先复制,在put.
public synchronized V put(K k, V v) {
Map<K, V> copy = new HashMap<K, V>(this.map);
V prev = copy.put(k, v);
this.map = Collections.unmodifiableMap(copy);
return prev;
}

5. CopyOnWriteArrayList - 写时复制

JDK里的CopyOnWriteArrayList,核心是任何时候,保障只有一个写者。因此,也是对读不加锁、对写进行加锁。

基本思路是,从共享同一个内容,当想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。

  • 底层实现是数组Object[],声明:volatile
  • 加锁的方式是ReentrantLock
  • copy的方法是Arrays.copyOf

5.1 add 加锁

public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

5.2 读不加锁

读的时候不需要加锁,如果读的时候有多个线程正在向 CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的 CopyOnWriteArrayList

public E get(int index) {
return get(getArray(), index);
}

很简单,只要了解了CopyOnWrite机制,我们可以实现各种CopyOnWrite容器,并且在不同的应用场景中使用。

5.3 CopyOnWrite的应用场景

CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。

注意
  1. 减少扩容开销。根据实际需要,初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销。
  2. 使用批量添加。因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数。

5.4 缺点

  • 内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象。
  • 数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
  • 从上面那点,我想到另外一个知识点:volatile保证的是实时一致性。对比来看,CopyOnWriteArrayList的底层结构未使用volatile关键字;CopyOnWriteMap使用了volatile,能够对map的引用保证实时一致性,可以理解成对数据也是实时一致性的。

锁 和 CopyOnWrite的实现的更多相关文章

  1. Java 集合 线程安全

    Java中常用的集合框架中的实现类HashSet.TreeSet.ArrayList.ArrayDeque.LinkedList.HashMap.TreeMap都是线程不安全的,如果多个线程同时访问它 ...

  2. Java 面试题基础概念收集(高级)

    JVM垃圾回收: GC又分为 minor GC 和 Full GC (也称为 Major GC ).Java 堆内存分为新生代和老年代,新生代中又分为1个 Eden 区域 和两个 Survivor 区 ...

  3. 3.ConcurrentHashMap 锁分段机制 Copy-On-Write

    /*ConcurrentHashMap*/ Java 5.0 在 java.util.concurrent 包中提供了 多种 并发容器来改进同步容器的性能 ConcurrentHashMap 同步容器 ...

  4. Copy-On-Write容器

    Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改, ...

  5. java多线程-Java中的Copy-On-Write容器

    Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改, ...

  6. Java并发编程:CopyOnWrite容器的实现

    Java并发编程:并发容器之CopyOnWriteArrayList(转载) 原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW ...

  7. copy-on-write学习

    最近知识梳理不够,那就整理点以前blog的东西.这儿就看COW(copy-on-write),cow技术主要是为了提高程序在单步操作时的系统响应速度而设计的,它通过将不是立即必要的空间分配,数据复制等 ...

  8. java高并发锁的3种实现

    初级技巧 - 乐观锁 乐观锁适合这样的场景:读不会冲突,写会冲突.同时读的频率远大于写. 以下面的代码为例,悲观锁的实现: Java代码   public Object get(Object key) ...

  9. Java并发包中CopyOnWrite容器相关类简介

    简介: 本文是主要介绍,并发容器CopyOnWriteArrayList和CopyOnWriteArraySet(不含重复元素的并发容器)的基本原理和使用示例. 欢迎探讨,如有错误敬请指正 如需转载, ...

随机推荐

  1. Andriod学习笔记 - 参考

    Andriod学习笔记 - 参考 自定义实现圆形播放进度条(android,飞一般的感觉) 盘点Android开发者必备的十大开发工具

  2. JAVA语法题

    import java.util.*; public class Birthdays { public static void main(String[] args){ Map<Friends, ...

  3. 编写一个python脚本功能-备份

    版本一 解决方案当我们基本完成程序的设计,我们就可以编写代码了,它是对我们的解决方案的实施.版本一例10.1 备份脚本——版本一 #!/usr/bin/python # Filename: backu ...

  4. (中等) POJ 1054 The Troublesome Frog,记忆化搜索。

    Description In Korea, the naughtiness of the cheonggaeguri, a small frog, is legendary. This is a we ...

  5. css颜色渐变在不同浏览器的设置

    在web开发中,难免会遇到浏览器之间的兼容问题,关于Css设置颜色渐变下面有解决的办法,直接上代码: 适用于谷歌浏览器: background: -webkit-gradient(linear, 0 ...

  6. jQuery简单实现图片预加载

    我们在做网站的时候经常会遇到这样的问题:一个页面有大量的图片导致页面加载速度缓慢,经常会出现一个白页用户体验很不好.那么如何解决这个问题呢?下面我来介绍一种在实际应用中经常会使用到的js预加载的方法. ...

  7. 苹果应用商店AppStore审核中文指南 分类: ios相关 app相关 2015-07-27 15:33 84人阅读 评论(0) 收藏

    目录 1. 条款与条件 2. 功能 3. 元数据.评级与排名 4. 位置 5. 推送通知 6. 游戏中心 7. 广告 8. 商标与商业外观 9. 媒体内容 10. 用户界面 11. 购买与货币 12. ...

  8. 【转】Linux 上的最佳 C/C++ IDE

    IDE介绍收藏篇: 个人linux下开发经验不多,一般也都使用shell远程连接使用命令行模式开发.如果自己在自己机器上开发还是有IDE要方便很多,看到这篇帖子就果断的转过来先收藏下,之前自己使用过E ...

  9. UIButton常用属性小结(编辑中。。。)

    Button的功能很黄很暴力,即能显示文字,又能显示图片,还能随时调整内部图片和文字的位置,用的地方很多. (1)按钮常用的四种状态: normal(普通状态) 默认情况(Default) 对应的枚举 ...

  10. java学习(四) excel读取

    private static void readExcel() { String filePath = "C:/Standardzid.xls"; File file = new ...