Java中线程安全的集合浅析 您所在的位置:网站首页 java中集合线程安全的是哪些 Java中线程安全的集合浅析

Java中线程安全的集合浅析

2024-06-26 11:11| 来源: 网络整理| 查看: 265

1、JDK1.5之前

旧版本的集合主要有两个Vector和Hashtable,在java.util包下。

这两个类保证线程安全都是采用synchronized修饰方法的方式。在1.5之前,效率不高,现在已基本弃用。

1.1、Vector 1.2、Hashtable 1.3、Collections工具类

在JDK1.5之前,可以通过java.util.Collections工具类将非线程安全的集合通过public static Collection synchronizedCollection(Collection c)方法转化为线程安全的集合

2、JDK1.5之后

jdk1.5之后,在java.util.concurrent包下,新加入了几个高效的线程安全集合类。

常用的有CopyOnWriteArrayList、CopyOnWriteArraySet

2.1、CopyOnWriteArrayList

线程安全的ArrayList,加强版的读写分离。

写加锁,读无锁,读写不阻塞,优于读写锁。

线程安全实现方式:写入时,先copy一个容器副本,再添加新元素,最后替换引用。是一种用空间换取线程安全的实现方式。

使用方式和一般的ArrayList一样。

2.1.1、使用示例 //jdk1.5之前可以使用collections将线程不安全的集合转为线程安全的 /*List list = new ArrayList(); List synchronizedList = Collections.synchronizedList(list);*/ //jdk1.5之后可以使用java.util.concurrent包下线程安全的集合 List list = new CopyOnWriteArrayList(); ExecutorService threadPool = Executors.newFixedThreadPool(5); for (int i = 0; i < 5; i++) { int t = i; threadPool.submit(new Runnable() { @Override public void run() { for (int j = 0; j < 10; j++) { list.add(Thread.currentThread().getName() +"添加:"+ t + "---" + j); } } }); } threadPool.shutdown(); while (!threadPool.isTerminated()) { } System.out.println(list.toString()); 2.1.2、源码解析 2.1.2.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(); } } 2.1.2.2、set()方法 public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); E oldValue = get(elements, index); //判断新元素是否和就元素相等 //不相等则替换,相等则不做操作 if (oldValue != element) { int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics setArray(elements); } return oldValue; } finally { lock.unlock(); } } 2.1.2.3、get()方法 @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; } /** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { return get(getArray(), index); } 2.1.2.4、remove方法 public E remove(int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; E oldValue = get(elements, index); int numMoved = len - index - 1; if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { Object[] newElements = new Object[len - 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return oldValue; } finally { lock.unlock(); } } 2.2、CopyOnWriteArraySet

线程安全的set集合,底层是一个CopyOnWriteArrayList。

与CopyOnWriteArrayList的不同是添加元素的add()方法使用的CopyOnWriteArrayList的addIfAbsent()方法实现。会遍历整个数组。如果元素已存在,则不添加,丢弃副本。

2.2.1、使用示例 CopyOnWriteArraySet set = new CopyOnWriteArraySet(); set.add("123"); set.add("456"); set.add("abc"); set.add("123"); set.forEach(o -> System.out.println(o)); 2.2.2、源码解析 2.2.2.1、实例化 public class CopyOnWriteArraySet extends AbstractSet implements java.io.Serializable { private static final long serialVersionUID = 5457747651344034263L; private final CopyOnWriteArrayList al; /** * Creates an empty set. */ public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList(); } } 2.2.2.2、add()方法 public boolean add(E e) { return al.addIfAbsent(e); } ==>CopyOnWriteArrayList public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); //先判断元素是否存在与旧数组中 //存在则直接返回false,不做操作,反之则调用addIfAbsent(e, snapshot)方法 return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot); } private boolean addIfAbsent(E e, Object[] snapshot) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] current = getArray(); int len = current.length; if (snapshot != current) { // Optimize for lost race to another addXXX operation int common = Math.min(snapshot.length, len); for (int i = 0; i < common; i++) if (current[i] != snapshot[i] && eq(e, current[i])) return false; if (indexOf(e, current, common, len) >= 0) return false; } Object[] newElements = Arrays.copyOf(current, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } } 2.2.2.3、remove()方法 public boolean remove(Object o) { return al.remove(o); } 2.3、ConcurrentLinkedQueue

无界队列,元素个数无上限,可以一直添加。

线程安全,高效可读写的队列,高并发下性能最好。

没有使用锁的方式(无锁),而是采用CAS算法(Compare And Switch ——比较交换算法)实现线程安全。

添加方法包括三个核心参数(V/E/N):V—需要更新的值,E—预期值(V的备份),N—更新值。更新前,先将V赋值给E,拿到N准备更新时,先比较此时的V和E是否相等,相等则更新,否则放弃更新。即:只有当V==E时,才会执行V=N,否则表示已经更新过,取消操作。

CAS是基于硬件的一种原子操作算法,效率极高。

2.3.1、使用示例 ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); // queue.add("1");//添加方法,底层使用offer()实现,会抛出异常 // queue.add("a"); queue.offer("2");//添加方法,不会抛出异常 queue.offer(3); System.out.println("---" + queue.size()); for (int i = 0; i ArrayBlockingQueue public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } ===>LinkedBlockingQueue public E take() throws InterruptedException { E x; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { while (count.get() == 0) { notEmpty.await(); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; } 2.5、ConcurrentHashMap

jdk1.8之前:

采用分段锁设计的线程安全Map集合,初始容量默认为16段(Segment)。

不是对整个map加锁,而是对每个segment加锁,以提高效率。最理想的状态是16个对象分别存入16个segment,并发量为16。

jdk1.8开始:

采用CAS算法实现同步。

使用方式与HashMap无异。

2.5.1、使用示例 Map map = new ConcurrentHashMap(); for (int i = 0; i < 5; i++) { int t = i; new Thread(new Runnable() { @Override public void run() { map.put(t, new Random().nextInt(100)); } }).start(); } map.forEach((key, value) -> System.out.println(key + " ==> " + value));



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有