java 集合类的线程安全
java valueof集合类的线程安全
为什么不是线程安全的
我们都知道在java中,经常会⽤到三⼤集合类Set,List,Map。但是像ArrayList, HashMap,HashSet这些常⽤的集合类是线程不安全的。在⾼并发的场景下使⽤这些集合类会导致很多的问题,⽐如丢失数据,数据的不⼀致性等等,甚⾄导致异常,给⽣产环境带来严重的损失。⾸先我们以List集合来举⼀个例⼦,来看看会导致的问题。
来看⼀下运⾏结果:
可能会出现空的情况,也可能丢失数据,也有可能正常,这个只是3个线程同时访问,我们加⼤线程的数量到30(代码省略,只需将for循环的3改成30即可!),就会报错:java.util.ConcurrentModificationException
当然,Set和Map集合的线程不安全也是⼀样的,下边看⼀下两者线程不安全的体现。
Set: List <String > list = new  ArrayList <>(); // 3个线程向list 集合中添加数据    for  (int  i = 0; i < 3; i ++)
{            new  Thread (() -> {                list .add (UUID .randomUUID ().toString ().substring (0,4));                System .out .println (list );            }).start ();        }
1
2
3
4
5
6
7
8第⼀种运⾏结果[null, e6bd ][null, e6bd ][null, e6bd ]第⼆种运⾏结果[ce2f, 0c63, 3ce0][ce2f, 0c63, 3ce0][ce2f, 0c63, 3ce0]第三种运⾏结果[55fe, e03f ][55fe, e03f ][55fe, e03f ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14Exception in  thread "Thread-2" Exception in  thread "Thread-3" java.util.ConcurrentModificationException  at java.util.ArrayList $Itr .checkForComodification (ArrayList.java:911) at java.util.ArrayList $Itr .next (ArrayList.java:861) at java.String (AbstractCollection.java:461) at java.lang.String.valueOf (String.java:2994) at java.io.PrintStream.println (PrintStream.java:821) at com.chunqiu.learn.Test1.lambda $main$0(Test1.java:20) at java.lang.Thread.run (Thread.java:748)
1
2
3
4
5
6
7
8
9
Map:
当然,报错都是java.util.ConcurrentModificationException。
出错原因
这个异常翻译成中⽂就是并发修改异常。为什么会报这样的⼀个异常呢?
⾸先先举⼀个⽣活中的例⼦:假如我们进考场签到,进考场之前,我们需要签到,每个⼈在花名册上签上⾃⼰的名字,假如张三正在签到,但是忘了⾃⼰的名字怎么写了,签的很慢,后边的李四等不及了,很嫌弃张三,就从张三的⼿中抢签到的笔,此时张三正在写字,这样就容易导致在争抢的过程中在花名册上留下⼀道长长的笔迹。这个由于多个线程同时在争抢添加操作所导致的异常就是我们的
java.util.ConcurrentModificationException。
我们来看⼀下List中的添加⽅法。
ArrayList的add⽅法为了保证效率并没有加锁,⽆法保证线程安全。那么应该如何保证线程的安全性呢?
⼆、如何保证线程安全
List 的线程安全
List保证线程安全有三种⽅式:
1. 使⽤线程安全的⼦类:Vector。 Vector的⽅法使⽤synchronized修饰,进⾏了加锁,可以保证线程安全。
2. 使⽤集合的⼯具类:Collections。Collections可以创建线程安全的集合类。Collections.synchronizedList(new ArrayList<>())
3. 使⽤JUC包中的⼀个类:CopyOnWriteArrayList。
CopyOnWriteArrayList
原理介绍:CopyOnWriteArrayList采⽤的是⼀种写时复制的思想,也就是读写分离。 CopyOnWriteArrayList容器即写时复制的容器,往⼀个容器添加元素的时候,不直接往当前容器Object[]添加,⽽是先将当前容器Object[]进⾏copy,夫指出⼀个新的容器Object[]
newElements,然后新的容器newElements中添加元素,添加完元素之后,在将原容器的引⽤指向新的容器setArray(newElements);。这样做的好处是可以对CopyOnWriteArrayList容器进⾏并发的读,⽽不需要加锁,因为当前容器不会添加任何蒜素,所以
CopyOnWriteArrayList容器也是有⼀种读写分离的思想,读和写不同的容器。 Set <String > set = new  HashSet <>();    for  (int  i = 0; i < 3; i ++) {            new  Thread (() -> {                set .add (UUID .randomUUID ().toString ().substring (0,4));                System .out .println (set );            }).start ();        }1
2
3
4
5
6
7
8 Map <String , String > map = new  HashMap <>();    for  (int  i = 0; i < 3; i ++) {            new  Thread (() -> {            String param = UUID .randomUUID ().toString ().substring (0,4);                map .put (param , param );                System .out .println (map );            }).start ();        }
1
2
3
4
5
6
7
8
9 public  boolean  add (E e ) {        ensureCapacityInternal (size + 1);  // Increments modCount!!        elementData [size ++] = e ;        return  true ;    }
1
2
3
4
5
查看CopyOnWriteArrayList的源码,我们发现有两个变量:ReentrantLock类型的lock和volatile关键字修饰的数组对象array。(volatile保证多线程对共享变量的可见性。)volatile的具体介绍可参见我之前的⽂章:
我们看⼀下CopyOnWriteArrayList的add⽅法。
Set
Set保证线程安全有两种⽅式:
1. 使⽤集合的⼯具类:Collections。Collections可以创建线程安全的集合类。Collections.synchronizedSet(new HashSet<>())
2. 使⽤JUC包中的⼀个类:CopyOnWriteArraySet。
查看CopyOnWriteArraySet的源码发现,其实底层还是CopyOnWriteArrayList。
在此呢多说⼀个⾯试题吧。
HashSet的底层是⼀个HashMap。 但是HashMap是K,V键值对的形式,HashSet只有⼀个,那HashSet的底层怎么是⼀个HashMap 呢? 那就看⼀下源码: /** The lock protecting all mutators */    final  transient  ReentrantLock lock = new  ReentrantLock ();    /** The array, accessed only via getArray/setArray. */    private  transient  volatile  Object [] array ;
1
2
3
4
5public  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 ();        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 private  final  CopyOnWriteArrayList <E > al ;    /**    * Creates an empty set.    */    public  CopyOnWriteArraySet () {        al = new  CopyOnWriteArrayList <E >();    }
1
2
3
4
5
6
7
8
Map
Map保证线程安全有两种⽅式:
1. 使⽤集合的⼯具类:Collections。Collections可以创建线程安全的集合类。Collections.synchronizedSet(new HashSet<>())
2. 使⽤JUC包中的⼀个类:ConcurrentHashMap。ConcurrentHashMap的内容较多,后续会专门出⼀⽚⽂章进⾏讲解。 private  transient  HashMap <E ,Object > map ;    private  static  final  Object PRE
SENT = new  Object (); /**  构造⽅法:  构造⼀个新的空集; 后备HashMap 实例具有默认初始容量 (16) 和负载因⼦ (0.75)。 */  public  HashSet () {        map = new  HashMap <>();  } /**  添加⽅法,发现添加的元素⾷作为map 的key,value 是⼀个固定的对象 */  public  boolean  add (E e ) {        return  map .put (e , PRESENT )==null ;    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。