JUC——安全容器类(CopyOnWriteArrayList,CopyOnWriteArraySet 和 ConcurrentHashMap)

引入

在多线程下,List ,Set ,Map 都是不安全的。

先拿List举个例子。

import java.util.*;
/**
 * @ClassName ListTest
 * @Description  
 * @Author SkySong
 * @Date 2020-10-11 22:26
 */
@SuppressWarnings("ALL")
public class ListTest {
    public static void main(String[] args) {
       
        List<String> list = new ArrayList<>();

        for (int i = 1; i <= 10; i++) {//开10条线程去修改list
            new Thread(()->{
                String uuid = UUID.randomUUID().toString().substring(0,5);
                list.add(uuid);
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

用多个线程同时去修改一个 List 列表。

并发修改异常
报了 并发修改异常,这一点是毋庸置疑的。

(java.util.ConcurrentModificationException 线程并发修改异常。)

解决方案

并发问题,首先想到的是 synchronize。

所以有了我们的第一个解决方案: new Vector()

import java.util.*;
/**
 * @ClassName ListTest
 * @Description  
 * @Author SkySong
 * @Date 2020-10-11 22:26
 */
@SuppressWarnings("ALL")
public class ListTest {
    public static void main(String[] args) {
       
        List<String> list = new Vector<String>();

        for (int i = 1; i <= 10; i++) {//开10条线程去修改list
            new Thread(()->{
                String uuid = UUID.randomUUID().toString().substring(0,5);
                list.add(uuid);
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

来看看 Vector 的 addElement() 方法:
Vector addElement()

这是最传统的方法了。


我们还可以借助工具类 Collections(集合老大哥)

import java.util.*;
/**
 * @ClassName ListTest
 * @Description  Collections
 * @Author SkySong
 * @Date 2020-10-11 22:26
 */
@SuppressWarnings("ALL")
public class ListTest {
    public static void main(String[] args) {
       
        List<String> list = Collections.synchronizedList(new ArrayList<>());

        for (int i = 1; i <= 10; i++) {//开10条线程去修改list
            new Thread(()->{
                String uuid = UUID.randomUUID().toString().substring(0,5);
                list.add(uuid);
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

我们借助工具类 Collections 把 new 出来的 ArrayL<>() 变成一个安全的。

接下来便是我们的主角了:JUC解决方案——COW计算机优化策略



CopyOnWriteArrayList


import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @ClassName ListTest
 * @Description  java.util.ConcurrentModificationException 线程并发修改异常。
 * @Author SkySong
 * @Date 2020-10-11 22:26
 */
@SuppressWarnings("ALL")
public class ListTest {
    public static void main(String[] args) {
        /**
         * 解决线程并发问题:
         * 1.List<String> list = new Vector<String>();
         * 2.List<String> list = Collections.synchronizedList(new ArrayList<>());
         * 3.List<String> list = new CopyOnWriteArrayList<>();
         */

        List<String> list = new CopyOnWriteArrayList<>();

        for (int i = 1; i <= 10; i++) {//开10条线程去修改list
            new Thread(()->{
                String uuid = UUID.randomUUID().toString().substring(0,5);
                list.add(uuid);
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

来初步感受一下 CopyOnWriteArrayList 的设计思想:
COWListAdd

从 CopyOnWriteArrayList 的 add() 方法中可以看出,他是将 数组资源 复制一版给调用者,调用者操作的是复制版,这样就避免了 多个线程共同操作一个 “母版” 的情况。

说了这么多,那么JUC方案的优势是什么呢?

CopyOnWriteArrayList 的 add() 方法避免使用了 synchronized ,而是使用了灵活性较高的 Lock 锁,在加上 COW 优化策略,使其效率有了很大程度的提高。

有了 List 当然 少不了 Set



CopyOnWriteArraySet


Set 和 List 都是 Collection老大哥的手下,所以很像。
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @ClassName SetTest
 * @Description java.util.ConcurrentModificationException
 * @Author SkySong
 * @Date 2020-10-12 22:57
 */
@SuppressWarnings("ALL")
public class SetTest {
    public static void main(String[] args) {
        /**
         * 解决方案:
         * 1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
         * 2.Set<String> set = new CopyOnWriteArraySet<>();
         */
        Set<String> set = new CopyOnWriteArraySet<>();

        for (int i = 1; i <= 10; i++) {//开10条线程去修改set
            new Thread(()->{
                String uuid = UUID.randomUUID().toString().substring(0,5);
                set.add(uuid);
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

这里提了一下 HashSet ,简单聊聊:
HashSet

其实 HashSet 底层就是个 HashMap。

  • HashMap 的 key 是不允许重复的,有了这一点就不难联想了。

HashSet 的 add() 方法:
HashSet add

这里的 value 值就是一个静态常量,不用太在意。

List 和 Set 都说的了, 容器三剑客 怎么能少了 Map。



ConcurrentHashMap


在多线程并发的情况下 Map 也是不安全的。

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @ClassName MapTest
 * @Description
 * @Author SkySong
 * @Date 2020-10-13 19:44
 */
public class MapTest {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName().toString(), UUID.randomUUID().toString());
                System.out.println(map);
            }).start();
        }
    }
}

同样会报如下异常:
map并发错误
这里补充一点HashMap的知识:
hashmap知识
解决思路

这里当然可以用 Collections.synchronizedMap(new HashMap<>());

但这不是我们今天的主角。 JUC 才是我们今天的主角。

public static void main(String[] args) {
    Map<String,String> map = new ConcurrentHashMap<>();
    for (int i = 1; i <= 10; i++) {
        new Thread(()->{
            map.put(Thread.currentThread().getName().toString(), UUID.randomUUID().toString());
            System.out.println(map);
        }).start();
    }
}

关于 ConcurrentHashMap 的巧妙之处有很多。我们下次再聊!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值