Java多线程之同步集合和并发集合
不管是同步集合还是并发集合他们都支持线程安全,他们之间主要的区别体现在性能和可扩展性,还有他们如何实现的线程安全。
同步集合类
- Hashtable
- Vector
- 同步集合包装类,Collections.synchronizedMap()和Collections.synchronizedList()
并发集合类
- ConcurrentHashMap
- CopyOnWriteArrayList
- CopyOnWriteHashSet
性能
同步集合比并发集合会慢得多,主要原因是锁,同步集合会对整个May或List加锁
并发集合的实现原理
- ConcurrentHashMap:把整个Map 划分成几个片段,只对相关的几个片段上锁,同时允许多线程访问其他未上锁的片段。
- CopyOnWriteArrayList:允许多个线程以非同步的方式读,当有线程写的时候它会将整个List复制一个副本给它。如果在读多写少这种对并发集合有利的条件下使用并发集合,这会比使用同步集合更具有可伸缩性。
并发集合的使用建议
- 一般不需要多线程的情况,只用到HashMap、ArrayList,只要真正用到多线程的时候就一定要考虑同步。所以这时候才需要考虑同步集合或并发集合。
ConcurrentHashMap实现原理
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
什么是CopyOnWrite容器
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
CopyOnWriteArrayList的实现原理
可以发现在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来。
public boolean add(T 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(); } } final void setArray(Object[] a) { array = a; }
读的时候不需要加锁,如果读的时候有多个线程正在向ArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的ArrayList。
public E get(int index) { return get(getArray(), index); }
JDK中并没有提供CopyOnWriteMap,我们可以参考CopyOnWriteArrayList来实现一个,基本代码如下:
import java.util.Collection; import java.util.Map; import java.util.Set; public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable { private volatile Map<K, V> internalMap; public CopyOnWriteMap() { internalMap = new HashMap<K, V>(); } public V put(K key, V value) { synchronized (this) { Map<K, V> newMap = new HashMap<K, V>(internalMap); V val = newMap.put(key, value); internalMap = newMap; return val; } } public V get(Object key) { return internalMap.get(key); } public void putAll(Map<? extends K, ? extends V> newData) { synchronized (this) { Map<K, V> newMap = new HashMap<K, V>(internalMap); newMap.putAll(newData); internalMap = newMap; } } }
CopyOnWrite的应用场景
CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。实现代码如下:
package com.ifeve.book; import java.util.Map; import com.ifeve.book.forkjoin.CopyOnWriteMap; /** * 黑名单服务 * * @author fangtengfei * */ public class BlackListServiceImpl { private static CopyOnWriteMap<String, Boolean> blackListMap = new CopyOnWriteMap<String, Boolean>( 1000); public static boolean isBlackList(String id) { return blackListMap.get(id) == null ? false : true; } public static void addBlackList(String id) { blackListMap.put(id, Boolean.TRUE); } /** * 批量添加黑名单 * * @param ids */ public static void addBlackList(Map<String,Boolean> ids) { blackListMap.putAll(ids); } }
注意两点:
- 减少扩容开销。根据实际需要,初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销。
- 使用批量添加。因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数。如使用上面代码里的addBlackList方法。
CopyOnWrite的缺点
内存占用问题
因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。
针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。
数据一致性问题
CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
相关推荐
1.1为什么需要多线程 1.2不安全示例 1.3并发问题的根源 1.4JMM 1.5线程安全的分类 1.6线程安全的方法 二、线程基础 2.1状态 2.2使用方式 2.3基础机制 2.4中断 2.5互斥同步 2.6线程合作 三、...
可以使用Java中的同步机制,如使用synchronized关键字或者使用线程安全的集合类来保证多线程操作的安全性。 实现步骤: 创建一个下载管理器类,用于管理下载任务和线程池。 在下载管理器中实现多线程下载的逻辑,...
00 IBM developerWorks 中国 : Java 多线程与并发编程专题 02 Java 程序中的多线程 03 编写多线程的 Java 应用程序 04 如果我是国王:关于解决 Java编程语言线程问题的建议 (2) 05 构建Java并发模型框架 (2) 06...
JUC(Java Util Concurrent)是Java中用于并发编程的工具包,提供了一组接口和类,用于处理多线程和并发操作。JUC提供了一些常用的并发编程模式和工具,如线程池、并发集合、原子操作等。 JUC的主要特点包括: ...
如果你是一名Java开发人员,并且想进一步掌握并发编程和多线程技术,并挖掘Java 7并发的新特性,那么本书是你的合适之选。 《Java 7并发编程实战手册》 第1章 线程管理 1 1.1 简介 1 1.2 线程的创建和运行...
高并发与多线程 Stargazers over time 线程 线程的创建和启动 线程的sleep、yield、join 线程的状态 代码在 部分。 synchronized关键字(悲观锁) synchronized(Object) 不能用String常量、Integer、Long。 锁住的是...
线程:Java多线程的实现方式,包括继承Thread类和实现Runnable接口。 锁:Java中的锁机制,包括synchronized关键字和ReentrantLock类。 线程池:Java中的线程池机制,包括线程池的创建、执行任务、关闭等操作。 并发...
从多线程的基础、线程同步、线程间通信、线程调度、线程池、并发容器、线程安全的集合、原子变量等方面去罗列主要知识点,以思维导图的方式进行呈现,可以让读者更条理清晰的在最短的时间内掌握多线程的主要知识
全书分为9章,涵盖了线程管理、线程同步、线程执行器、Fork/Join框架、并发集合、定制并发类、测试并发应用等内容。全书通过60多个简单而非常有效的实例,帮助读者快速掌握Java7多线程应用程序的开发技术。学习完...
Java多线程 多线程中的常见问题 同步关键字原理 多线程的三大核心 对锁的一些认知 ReentrantLock实现原理 ConcurrentHashMap 的实现原理 如何优雅地使用和理解线程池 深入理解线程通信 一个线程召集的诡异事件 ...
线程安全性:ConcurrentSkipListSet 是线程安全的,可以在多线程环境中使用而无需显式地进行同步操作。它通过利用并发技术来实现线程安全,能够支持高效的并发访问。 有序性:ConcurrentSkipListSet 是有序集合,...
2.熟练使用常用的java集合类以及常用集合的源码,熟悉多线程以及同步容器以及并发容器的使用,AQS,CAS,lock,volatilte,synchronized等; 3.对Java虚拟机、JMM、垃圾收集机制、GC算法、JVM常用配置参数、GC参数、...
ConcurrentHashMap使用了分段锁(Segment)来实现并发的读写操作,每个Segment都相当于一个小的HashMap,将...因此,如果需要保证精确的操作顺序或避免并发更新带来的问题,可以考虑使用更高级的同步工具或并发集合类。
多线程和并发: Java支持多线程编程,允许同时执行多个任务。Java中的并发编程机制包括线程、同步、锁等,以及java.util.concurrent包提供的高级并发功能。 异常处理: Java通过异常处理机制提供了一种结构化的错误...
多线程并发访问,同步控制 线程间通信,等待/通知机制 锁锁机制,API详解 Fork/Join 框架机制详解 Executor线程池框架简介 面向对象 泛型机制与反射原理 Proxy动态代理机制详解 从整体上观察对象 网络开发 Servlet...
多线程与并发:这部分问题涉及到Java的线程模型、同步机制、并发工具类等。 JVM与性能优化:这部分问题涵盖了JVM内存模型、垃圾收集、性能调优等内容。例如,解释JVM的内存区域划分和作用;理解垃圾收集算法和调优...
Java面试通常涵盖多个...并发编程:了解Java中的线程、同步、锁等机制,以及Java并发包中的工具类。 JVM与性能调优:对Java虚拟机(JVM)有一定了解,包括内存管理、垃圾回收等方面,并知道如何进行基本的性能调优。
在准备Java面试时,我们需要掌握以下几个方面的知识:核心Java、常见算法、多线程与并发、设计模式等。这些内容不仅是面试的重要考点,也是我们在实际工作中需要掌握的基本技能。接下来,小编将从这些方面为大家做...
JUC(java.util.concurrent)库是 Java 标准库的一部分,提供了丰富的多线程并发工具,旨在帮助开发者编写高性能、高可伸缩性的并发程序。下面综合介绍 JUC 库的几个核心概念以及它们在并发编程中的重要性。 1. ...