Java集合多线程安全

线程安全与不安全集合

线程不安全集合:

  • ArrayList
  • LinkedList
  • HashMap
  • HashSet
  • TreeMap
  • TreeSet
  • StringBulider

线程安全集合:

  • Vector
  • HashTable
  • Properties

集合线程安全与解决方案

ArrayList线程安全问题

1package com.raicho.mianshi.mycollection; 2 3import java.util.ArrayList; 4import java.util.List; 5import java.util.UUID; 6 7/** 8 * @author: Raicho 9 * @Description: 10 * @program: mianshi 11 * @create: 2020-07-17 15:32 12 **/ 13public class ArrayListConcurrentDemo { 14 public static void main(String[] args) { 15 List<String> list = new ArrayList<>(); 16 for (int i = 0; i < 30; ++i) { 17 new Thread(() -> { 18 list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4)); 19 System.out.println(list); 20 }).start(); 21 } 22 } 23}

运行报错:

ArrayList是线程不安全的,add()方法并没有加锁(synchronized),多线程环境下会抛出ConcurrentModificationException

解决方案:

  • 使用Vector类(使用了synchronized),效率极低

  •  使用Collections.synchronizedList(new ArrayList<>()):内部直接将接受的List对象传递给静态内部类SynchronizedList对象,然后Collections.synchronizedList(new ArrayList<>())返回的List对象的调用方法都是直接调用输入List对象的方法,但是加了synchronized,类似装饰器模式,也是对输入List的一种增强:

    package com.raicho.mianshi.mycollection;

    import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID;

    /**

    • @author: Raicho

    • @Description:

    • @program: mianshi

    • @create: 2020-07-17 15:32 **/ public class ArrayListConcurrentDemo {

      public static void main(String[] args) { List<String> list = Collections.synchronizedList(new ArrayList<>()); for (int i = 0; i < 50; ++i) { new Thread(() -> { list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4)); System.out.println(list); },String.valueOf(i)).start(); } } }

源码:

1static <T> List<T> synchronizedList(List<T> list, Object mutex) { 2 return (list instanceof RandomAccess ? 3 new SynchronizedRandomAccessList<>(list, mutex) : 4 new SynchronizedList<>(list, mutex)); 5} 6 7static class SynchronizedList<E> 8 extends SynchronizedCollection<E> 9 implements List<E> { 10 private static final long serialVersionUID = -7754090372962971524L; 11 12 final List<E> list; 13 14 SynchronizedList(List<E> list) { 15 super(list); 16 this.list = list; 17 } 18 SynchronizedList(List<E> list, Object mutex) { 19 super(list, mutex); 20 this.list = list; 21 } 22 23 public boolean equals(Object o) { 24 if (this == o) 25 return true; 26 synchronized (mutex) {return list.equals(o);} 27 } 28 public int hashCode() { 29 synchronized (mutex) {return list.hashCode();} 30 } 31 32 public E get(int index) { 33 synchronized (mutex) {return list.get(index);} 34 } 35 public E set(int index, E element) { 36 synchronized (mutex) {return list.set(index, element);} 37 } 38 public void add(int index, E element) { 39 synchronized (mutex) {list.add(index, element);} 40 } 41 public E remove(int index) { 42 synchronized (mutex) {return list.remove(index);} 43 } 44 45 public int indexOf(Object o) { 46 synchronized (mutex) {return list.indexOf(o);} 47 } 48 public int lastIndexOf(Object o) { 49 synchronized (mutex) {return list.lastIndexOf(o);} 50 } 51 52 public boolean addAll(int index, Collection<? extends E> c) { 53 synchronized (mutex) {return list.addAll(index, c);} 54 } 55 56 public ListIterator<E> listIterator() { 57 return list.listIterator(); // Must be manually synched by user 58 } 59 60 public ListIterator<E> listIterator(int index) { 61 return list.listIterator(index); // Must be manually synched by user 62 } 63 64 public List<E> subList(int fromIndex, int toIndex) { 65 synchronized (mutex) { 66 return new SynchronizedList<>(list.subList(fromIndex, toIndex), 67 mutex); 68 } 69 } 70 71 @Override 72 public void replaceAll(UnaryOperator<E> operator) { 73 synchronized (mutex) {list.replaceAll(operator);} 74 } 75 @Override 76 public void sort(Comparator<? super E> c) { 77 synchronized (mutex) {list.sort(c);} 78 } 79 80 private Object readResolve() { 81 return (list instanceof RandomAccess 82 ? new SynchronizedRandomAccessList<>(list) 83 : this); 84 } 85}
  • CopyOnWriteArrayList:写时复制是一种读写分离的思想,在并发读的时候不需要加锁,因为它能够保证并发读的情况下不会添加任何元素。而在并发写的情况下,需要先加锁,但是并不直接对当前容器进行写操作。而是先将当前容器进行复制获取一个新的容器,进行完并发写操作之后,当之前指向原容器的引用更改指向当前新容器。也就是说,并发读和并发写是针对不同集合,因此不会产生并发异常

    package com.raicho.mianshi.mycollection;

    import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList;

    /**

    • @author: Raicho

    • @Description:

    • @program: mianshi

    • @create: 2020-07-17 15:32 **/ public class ArrayListConcurrentDemo {

      public static void main(String[] args) { List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 30; ++i) { new Thread(() -> { list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4)); System.out.println(list); },String.valueOf(i)).start(); } } }

源码:

1// CopyOnWriteArrayList.java 2public boolean add(E e) { 3 // 写操作加锁 4 final ReentrantLock lock = this.lock; 5 lock.lock(); 6 try { 7 // 原有容器复制一份 8 Object[] elements = getArray(); 9 int len = elements.length; 10 // 创建一个容器,将原来的数据复制到新容器中,并且还有一个位置空余 11 Object[] newElements = Arrays.copyOf(elements, len + 1); 12 // 将新元素添加到空余位置 13 newElements[len] = e; 14 // 将原来指向旧容器的引用指向新容器 15 setArray(newElements); 16 return true; 17 } finally { 18 // 写操作完成,解锁 19 lock.unlock(); 20 } 21} 22 23public E set(int index, E element) { 24 // 更新操作类似 25 final ReentrantLock lock = this.lock; 26 lock.lock(); 27 try { 28 Object[] elements = getArray(); 29 E oldValue = get(elements, index); 30 31 if (oldValue != element) { 32 int len = elements.length; 33 Object[] newElements = Arrays.copyOf(elements, len); 34 newElements[index] = element; 35 setArray(newElements); 36 } else { 37 // Not quite a no-op; ensures volatile write semantics 38 setArray(elements); 39 } 40 return oldValue; 41 } finally { 42 lock.unlock(); 43 } 44} 45 46// 读操作不加锁 47private E get(Object[] a, int index) { 48 return (E) a[index]; 49}

在添加元素e完后,再调用setArray(newElements);函数重新赋值,之前指向原容器的引用更改指向当前新容器

HashSet线程安全问题

HashSet底层就是一个HashMap,默认的HashSet是一个初始大小为16,负载因子为0.75的HashMap:

HashSet的多线程安全问题实际上就是HashMap的多线程安全问题:

1package com.raicho.mianshi.mycollection; 2 3import java.util.HashSet; 4import java.util.Set; 5import java.util.UUID; 6/** 7 * @author: Raicho 8 * @Description: 9 * @program: mianshi 10 * @create: 2020-07-17 17:03 11 * 12 * HashSet多线程不安全问题 13 * HashSet底层就是HashMap,因此这个案例也是HashMap多线程不安全问题的演示 14 */ 15public class HashSetThreadUnsafe { 16 public static void main(String[] args) { 17 Set<String> sets = new HashSet<>(); 18 for (int i = 0; i < 100; ++i) { 19 new Thread(() -> { 20 sets.add(UUID.randomUUID().toString().substring(0, 4)); 21 System.out.println(sets); 22 },String.valueOf(i)).start(); 23 } 24 } 25}

 解决方案:

  • Collections集合类的static方法SynchronizedSet

  • CopyOnWriteArraySet:也是写时复制思想,但是内部还是使用CopyOnWriteArrayList实现:

    public class CopyOnWriteArraySet<E> extends AbstractSet<E> implements java.io.Serializable { private static final long serialVersionUID = 5457747651344034263L;

    1private final CopyOnWriteArrayList<E> al; 2 3/** 4 * Creates an empty set. 5 */ 6public CopyOnWriteArraySet() { 7 // 构造器内部实例化了一个CopyOnWriteArrayList 8 al = new CopyOnWriteArrayList<E>(); 9} 10// ...

    }

HashMap多线程安全的解决方案

相比于HashSet,HashMap除了可以使用Collections集合类的synchronizedMap方法外,还可以使用juc包下ConcurrentHashMap类。

1package com.raicho.mianshi.mycollection; 2 3import java.util.Map; 4import java.util.Set; 5import java.util.UUID; 6import java.util.concurrent.ConcurrentHashMap; 7import java.util.concurrent.CopyOnWriteArraySet; 8 9/** 10 * @author: Raicho 11 * @Description: 12 * @program: mianshi 13 * @create: 2020-07-17 17:03 14 */ 15public class HashMapThreadUnsafe { 16 public static void main(String[] args) { 17 Map<String,String> map = new ConcurrentHashMap<>(); 18 for (int i = 0; i < 100; ++i) { 19 new Thread(() -> { 20 map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 4)); 21 System.out.println(map); 22 },String.valueOf(i)).start(); 23 } 24 } 25}
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

KVM调整cpu和内存

一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid