天天看点

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap

JUC之线程不安全的ArrayList、HashSet、HashMap

  • 1 线程不安全的ArrayList
    • 1.1 前言
    • 1.2 单线程下的ArrayList
    • 1.3 多线程下的ArrayList
    • 1.4 线程不安全的ArrayList,解决方案
      • 1.4.1 方案一: Vector
      • 1.4.2 方案二: Collections.synchronized()
      • 1.4.3 方案三: 采用JUC里面的方法-CopyOnWriteArrayList
  • 2 线程不安全的HashSet
    • 2.1 单线程下的HashSet
    • 2.2 多线程下的HashSet
      • 2.2.1 解决方案 CopyOnWriteArraySet
  • 3 线程不安全的HashMap
    • 3.1 解决方案一-Collections.synchronizedMap(new HashMap<>())
    • 3.2 解决方案二-ConcurrentHashMap

1 线程不安全的ArrayList

1.1 前言

当我们执行下面语句的时候,底层进行了什么操作?

底层创建了一个空的数组,伴随着初始值为10。

当执行add方法后,如果超过了10,那么会进行扩容,扩容的大小为原值的一半,也就是5个,使用下列方法扩容。

1.2 单线程下的ArrayList

单线程环境的ArrayList是不会有问题的

public class ArrayListNotSafeDemo {
    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        for(String element : list) {
            System.out.println(element);
        }
    }
}
           

1.3 多线程下的ArrayList

当我们同时启动30个线程去操作List的时候:

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

输出结果:

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap

这个时候出现了错误,也就是java.util.ConcurrentModificationException,这个异常是 并发修改的异常。

1.4 线程不安全的ArrayList,解决方案

1.4.1 方案一: Vector

使用Vector替换ArrayList,关于Vector如何实现线程安全的,而是在方法上加了锁,即synchronized。

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap

这样就每次只能够一个线程进行操作,所以不会出现线程不安全的问题,但是因为加锁了,导致并发性下降。

1.4.2 方案二: Collections.synchronized()

采用Collections集合工具类,在ArrayList外面包装一层 同步 机制

代码:

private static void demo02() {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        for(int i=0; i<30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
           

输出结果:

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap

来看一看Collections.synchronized()是怎么做到的:

  • 首先进到synchronized()方法看源码:
    JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap
  • 再来看一看ArrayList的源码:
    JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap
    ArrayList实现了RandomAccess接口,所以ArrayList是支持随机访问的,所以创建一个SynchronizedRandomAccessList对象。
  • 再进入SynchronizedRandomAccessList类源码中瞅一瞅:
    JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap
    SynchronizedRandomAccessList是一个静态类,再看看他的父类:
    JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap
    SynchronizedRandomAccessList的父类是SynchronizedList,但是SynchronizedList又继承了SynchronizedCollection。
  • 最后再看一看SynchronizedCollection
    JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap
    到顶了,由于SynchronizedCollection实现了Collection接口,重写了add方法,我们再看看add()方法:
    JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap
    可以看到synchronized修饰了最开始传进来的list对象,就使得在同一时刻只有一个线程能对list进行操作。

1.4.3 方案三: 采用JUC里面的方法-CopyOnWriteArrayList

CopyOnWriteArrayList:写时复制,主要是一种读写分离的思想。

代码:

private static void demo03() {
        List<String> list = new CopyOnWriteArrayList();
        for(int i=0; i<30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
           

输出结果:

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap

莫得问题!!!

写时复制,CopyOnWrite容器即写时复制的容器,往一个容器中添加元素的时候,不直接往当前容器Object[]添加,而是先将Object[]进行copy,复制出一个新的容器object[] newElements,然后新的容器Object[] newElements里添加原始,添加元素完后,在将原容器的引用指向新的容器 setArray(newElements);这样做的好处是可以对copyOnWrite容器进行并发的度,而不需要加锁,因为当前容器不需要添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

就是写的时候,把ArrayList扩容一个出来,然后把值填写上去,在通知其他的线程,ArrayList的引用指向扩容后的

查看底层add方法源码:

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap

2 线程不安全的HashSet

2.1 单线程下的HashSet

private static void demo01() {
        Set<String> set = new HashSet<>();
        set.add("aaa");
        set.add("bbb");
        set.add("ccc");
        for (String element : set){
            System.out.println(element);
        }
    }
           

2.2 多线程下的HashSet

代码:

private static void demo02() {
        Set<String> set = new HashSet<>();
        for(int i=0; i<30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
           

输出结果:

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap

可以看到跟多线程下ArrayList的一样的异常。

2.2.1 解决方案 CopyOnWriteArraySet

代码:

private static void demo03() {
        Set<String> set = new CopyOnWriteArraySet<>();
        for(int i=0; i<30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
           

输出结果

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap

莫得问题!!!

点开进CopyOnWriteArraySet类的源码看一看。。。

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap

底层还是使用CopyOnWriteArrayList进行实例化。。。

顺便说一下调用 HashSet.add()的方法,只需要传递一个元素,而HashMap是需要传递key-value键值对?

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap

HashSet的底层结构就是HashMap。

再看下add方法的源码:

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap
JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList2 线程不安全的HashSet3 线程不安全的HashMap

我们能发现但我们调用add的时候,存储一个值进入map中,只是作为key进行存储,而value存储的是一个Object类型的常量,也就是说HashSet只关心key,而不关心value。

3 线程不安全的HashMap

话就不多说了,多线程下的HashMap会和之前的多线程下的ArrayList和HashSet报同样的异常错误。

3.1 解决方案一-Collections.synchronizedMap(new HashMap<>())

3.2 解决方案二-ConcurrentHashMap

未完,待补充。。。