容器是所有编程中的基础工具,这其中也包括并发编程。出于这个原因,像Vector和Hashtable这类早期容器具有许多synchronized方法,当他们用于非多线程的应用程序中时,便会导致不可接受的开销。在Java1.2中,新的容器类库是不同步的,并且Collections类提供了各种static的同步的装饰方法,从而来同步不同类型的容器。尽管这是一种改进,因为它使你可以选择在你的容器中是否要使用同步,但是这种开销仍旧是基于synchronized加锁机制的。Java SE5特别添加了新容器,通过使用更灵巧的技术消除加锁,从而提高线程的安全的性能。

这些免锁容器背后的通用策略是:对容器的修改可以与读取操作同时发生,只要读取者只能看到完成修改后的结果即可。修改是在容器数据结构的某一部分的一个单独的副本(有时是整个数据结构的副本)上执行的,并且这个副本在修改过程中是不可视的。只有当修改完成时,被修改的结构才会自动地与主数据结构进行交换,之后读取者就可以看到这个修改了。

在CopyOnWriteArrayList中,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全的执行。当修改完成时,一个原子性操作将把新的数组换入,使得新的读取操作可以看到这个新的修改。CopyOnWriteArrayList的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出ConcurrentModificationException,因此你不必编写特殊的代码去防范这种异常,就像你以前必须作的那样。

迭代器上进行的元素更改操作(removeset 和 add)不受支持。这些方法将抛出 UnsupportedOperationException

CopyOnWriteArraySet将使用CopyOnWriteArrayList来实现其免锁行为。

ConcurrentHashMap和ConcurrentLinkedQueue使用了类似的技术,允许并发的读取和写入,但是容器中只有部分内容而不是整个容器可以被复制和修改。然而,任何修改在完成之前,读取者仍旧不能看到它们。ConcurrentHashMap不会抛出ConcurrentModificationException异常。

CopyOnWriteArrayList适合用在“读多,写少”的“并发”应用中,换句话说,它适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在“扩容”的概念,每次写操作(add or remove)都要copy一个副本,在副本的基础上修改后改变array引用,所以称为“CopyOnWrite”,因此在写操作是加锁,并且对整个list的copy操作时相当耗时的,过多的写操作不推荐使用该存储结构。

一个ConcurrentHashMap由多个segment组成,每一个segment都包含了一个HashEntry数组的hashtable, 每一个segment包含了对自己的hashtable的操作,比如get,put,replace等操作,这些操作发生的时候,对自己的hashtable进行锁定。由于每一个segment写操作只锁定自己的hashtable,所以可能存在多个线程同时写的情况,性能无疑好于只有一个hashtable锁定的情况。

源码分析 在ConcurrentHashMap的remove,put操作还是比较简单的,都是将remove或者put操作交给key所对应的segment去做的,所以当几个操作不在同一个segment的时候就可以并发的进行。

public V remove(Object key) {int hash = hash(key.hashCode());return segmentFor(hash).remove(key, hash, null);}

而segment中的remove操作除了加锁之外和HashMap中的remove操作基本无异

   V remove(Object key, int hash, Object value) {lock();try {int c = count - 1;HashEntry<K,V>[] tab = table;int index = hash & (tab.length - 1);HashEntry<K,V> first = tab[index];HashEntry<K,V> e = first;while (e != null && (e.hash != hash || !key.equals(e.key)))e = e.next;V oldValue = null;if (e != null) {V v = e.value;if (value == null || value.equals(v)) {oldValue = v;// All entries following removed node can stay// in list, but all preceding ones need to be// cloned.++modCount;HashEntry<K,V> newFirst = e.next;for (HashEntry<K,V> p = first; p != e; p = p.next)newFirst = new HashEntry<K,V>(p.key, p.hash,newFirst, p.value);tab[index] = newFirst;count = c; // write-volatile
                    }}return oldValue;} finally {unlock();}}

package tij;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** Created by huaox on 2017/4/21.**/
public class CopyOnWriteArrayListDemo {private static class ReadTask implements Runnable {List<String> list;ReadTask(List<String> list) {this.list = list;}public void run() {for (String str : list) {System.out.println(str);}}}private static class WriteTask implements Runnable {List<String> list;int index;WriteTask(List<String> list, int index) {this.list = list;this.index = index;}public void run() {list.remove(index);list.add(index, "write_" + index);}}public void run() {final int NUM = 5;List<String> list = new ArrayList<String>();for (int i = 0; i < NUM; i++) {list.add("main_" + i);}ExecutorService executorService = Executors.newFixedThreadPool(NUM);for (int i = 0; i < NUM; i++) {executorService.execute(new ReadTask(list));executorService.execute(new WriteTask(list, i));}executorService.shutdown();}public static void main(String[] args) {new CopyOnWriteArrayListDemo().run();}
}

输出结果:

    Exception in thread "pool-1-thread-3" java.util.ConcurrentModificationException
main_0
main_1at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
main_2
main_3at java.util.ArrayList$Itr.next(ArrayList.java:851)
main_4
main_0
main_1
write_2
main_3
main_4
main_0
main_1
write_2
write_3
main_4
main_0
write_0
write_1at tij.CopyOnWriteArrayListDemo$ReadTask.run(CopyOnWriteArrayListDemo.java:22)
write_2
write_3at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
write_4at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)at java.lang.Thread.run(Thread.java:745)Process finished with exit code 0

免锁容器的源码为赋值底层数组,然后赋新值,改引用

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();}}

修改为CopyOnWriteArrayList后

 public void run() {final int NUM = 5;List<String> list = new CopyOnWriteArrayList<>();for (int i = 0; i < NUM; i++) {list.add("main_" + i);}ExecutorService executorService = Executors.newFixedThreadPool(NUM);for (int i = 0; i < NUM; i++) {executorService.execute(new ReadTask(list));executorService.execute(new WriteTask(list, i));}executorService.shutdown();}

输出结果:

main_0
main_1
main_2
main_3
main_4
main_0
main_1
write_2
main_3
main_4
write_0
main_1
write_2
write_3
main_4
write_0
main_1
write_2
write_3
write_4
write_0
write_1
write_2
write_3
write_4Process finished with exit code 0

转载于:https://www.cnblogs.com/soar-hu/p/6742694.html

java多线程基本概述(二十六)——免锁容器相关推荐

  1. 【Java学习笔记之二十六】深入理解Java匿名内部类

    在[Java学习笔记之二十五]初步认知Java内部类中对匿名内部类做了一个简单的介绍,但是内部类还存在很多其他细节问题,所以就衍生出这篇博客.在这篇博客中你可以了解到匿名内部类的使用.匿名内部类要注意 ...

  2. 【零基础学Java】—static关键字概述(十六)

    [零基础学Java]-static关键字概述(十六) 一.static关键字 二.static关键字修饰成员变量 /*** @author :CaiCai* @date : 2022/4/8 11:1 ...

  3. JAVA之旅(二十八)——File概述,创建,删除,判断文件存在,创建文件夹,判断是否为文件/文件夹,获取信息,文件列表,文件过滤...

    JAVA之旅(二十八)--File概述,创建,删除,判断文件存在,创建文件夹,判断是否为文件/文件夹,获取信息,文件列表,文件过滤 我们可以继续了,今天说下File 一.File概述 文件的操作是非常 ...

  4. JAVA面经复习(二十六)面试难度:☆☆☆☆

    JAVA面经复习(二十六)面试难度:☆☆☆☆ 面试难度:☆☆☆☆ 推荐指数:☆☆☆☆☆ 推荐原因:总体来说本篇面经难度不高,且基本都是基础知识,不涉及复杂的分布式应用的工具,适合新手复习. 声明:答案 ...

  5. JAVA之旅(二十八)——File概述,创建,删除,判断文件存在,创建文件夹,判断是否为文件/文件夹,获取信息,文件列表,文件过滤

    JAVA之旅(二十八)--File概述,创建,删除,判断文件存在,创建文件夹,判断是否为文件/文件夹,获取信息,文件列表,文件过滤 我们可以继续了,今天说下File 一.File概述 文件的操作是非常 ...

  6. 从零开始学java(二十六)--多维数组,多维数组存储表格数据

    从零开始学java(二十六)--多维数组 多维数组 多维数组存储表格数据 多维数组 多维数组可以看成以数组为元素的数组.可以有二维.三维.甚至更多维数组,但是实际开发中用的非常少.最多到二维数组(学习 ...

  7. 教妹学Java(二十六):static 关键字解析

    你好呀,我是沉默王二,(目前是)CSDN 周排名前十的博客专家.这是<教妹学 Java>专栏的第二十六篇,今天我们来谈谈 Java 的 static 关键字--什么是静态变量?什么是静态方 ...

  8. Java多线程系列(一):最全面的Java多线程学习概述

    Java并发编程的技能基本涵括以下5方面: 多线程 线程池 线程锁 并发工具类 并发容器 多线程的4种创建方式 继承Thread 实现Runnable接口 实现Callable接口 以及线程池来创建线 ...

  9. JVM 学习笔记二十六、JVM监控及诊断工具-GUI篇

    二十六.JVM监控及诊断工具-GUI篇 1.工具概述 使用上一张命令行工具或组合能帮您获取目标Java应用性能相关的基础信息,但他们存在下列局限: (1)无法获取方法级别的分析数据,如方法间的调用关系 ...

最新文章

  1. keras提取模型中的某一层_keras获得某一层或者某层权重的输出实例
  2. java接口详解+练习题
  3. 快速小目标检测--Feature-Fused SSD: Fast Detection for Small Objects
  4. linux 设置代理_用Android手机做电脑的HTTP代理服务器
  5. Android Studio检测内存泄露和性能
  6. 心态决定你的人生高度
  7. java实训 :异常(try-catch执行顺序与自定义异常)
  8. java名人_识别名人 · Find the Celebrity
  9. DHCP服务器在企业里的各种应用方案
  10. cocos2dx 显示html,cocos2dx - 在游戏中显示HTML页面
  11. route路由失败,为何?
  12. 看完书就忘,怎么办?告诉你六种方法(转载)
  13. 【Unity优化篇】| Unity3D场景 常用优化策略,遮挡剔除、层消隐距离技术 和 LOD多层次细节
  14. 网页游戏未来发展的一些趋势
  15. python分割文件_python简单分割文件的方法
  16. 【随机算法梗概】遗传算法通俗的讲解案例~~
  17. 区块链是什么,如何去理解?
  18. Attention 机制是什么?
  19. Kinect开发遇到的问题
  20. Python-Flask开发微电影网站(八)

热门文章

  1. python极客项目编程_Python极客项目编程 ([美]Mahesh Venkitachalam) 中文pdf完整版
  2. hyperworks2019安装教程
  3. OpenCV | 双目相机标定之OpenCV获取左右相机图像+MATLAB单目标定+双目标定
  4. 共模电感适用的频率_分析共模电感和差模电感寄生电容抵消的方法
  5. 桌面计算机硬盘打不开,我电脑的磁盘打不开,是什么问题?
  6. 提高你开发效率的十五个 Visual Studio 使用技巧
  7. keil查看程序运行时间_Keil系列教程05_工程目标选项配置(一)
  8. php语言培训费用,PHP语言编程的优势在哪里
  9. 重磅嘉宾公布,第四范式AI新品发布会进入报名倒计时
  10. python3精要(51)-json