Java 进阶(15)线程安全集合

CopyOnWriteArrayList

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

写有锁,读⽆锁,读写之间不阻塞,优于读写锁。

写⼊时,先copy⼀个容器副本、再添加新元素,最后替换引⽤。

使⽤⽅式与ArrayList⽆异。

示例:

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

        CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
        //创建一个线程池
        ExecutorService pool= Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        list.add("content"+new Random().nextInt(100));
                    }
                }
            });
        }

        //关闭线程池
        //4关闭线程池
        pool.shutdown();
        while(!pool.isTerminated()){

        }
        //5打印结果
        System.out.println("元素个数:"+list.size());
        for (String string : list) {
            System.out.println(string);
        }
    }
}

CopyOnWriteArrayList如何做到线程安全的

CopyOnWriteArrayList使⽤了⼀种叫写时复制的⽅法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷⻉⼀份出来,然后在新的数组做写操作,写完之后,再将原来的数组引⽤指向到新数组。

当有新元素加⼊的时候,如下图,创建新数组,并往新数组中加⼊⼀个新元素,这个时候,array这个引⽤仍然是指向原数组的。

CopyOnWriteArrayList 的整个add操作都是在锁的保护下进⾏的。 这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的。

CopyOnWriteArrayList 的 add 操作的源代码如下:

public boolean add(E e) {
    //1、先加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        //2、拷⻉数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //3、将元素加⼊到新数组中
        newElements[len] = e;
        //4、将array引⽤指向到新数组
        setArray(newElements);
        return true;
    } finally {
        //5、解锁
        lock.unlock();
    }
}

由于所有的写操作都是在新数组进⾏的,这个时候如果有线程并发的写,则通过锁来控制,如果有线程并发的读,则分⼏种情况:

1、如果写操作未完成,那么直接读取原数组的数据;

2、如果写操作完成,但是引⽤还未指向新数组,那么也是读取原数组数据;

3、如果写操作完成,并且引⽤已经指向了新的数组,那么直接从新数组中读取数据。

可见, CopyOnWriteArrayList 的读操作是可以不用加锁的。

CopyOnWriteArraySet

CopyOnWriteArraySet基于CopyOnWriteArrayList实现,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent(若没有则增加)方法

CopyOnWriteArraySet介绍

它是线程安全的无序的集合,可以将它理解成线程安全的HashSet。有意思的是,CopyOnWriteArraySet和HashSet虽然都继承于共同的父类AbstractSet;但是,HashSet是通过“散列表(HashMap)”实现的,而CopyOnWriteArraySet则是通过“动态数组(CopyOnWriteArrayList)”实现的,并不是散列表。

和CopyOnWriteArrayList类似,CopyOnWriteArraySet具有以下特性:

1. 它最适合于具有以下特征的应用程序:Set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。

2. 它是线程安全的。

3. 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。

4. 迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等 操作。

5. 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。

public class TestCopyOnWriteArraySet {
    public static void main(String[] args) {
        //1创建集合
        CopyOnWriteArraySet set=new CopyOnWriteArraySet<>();
        //2添加元素
        set.add("pingguo");
        set.add("huawei");
        set.add("xiaomi");
        set.add("lianxiang");
        set.add("pingguo");
        //3打印
        System.out.println("元素个数:"+set.size());
        System.out.println(set.toString());
    }
}

ConcurrentHashMap

初始容量默认为16段(Segment),使⽤分段锁设计。

不对整个Map加锁,⽽是为每个Segment加锁。

当多个对象存⼊同⼀个Segment时,才需要互斥。

最理想状态为16个对象分别存⼊16个Segment,并⾏数量16。

使⽤⽅式与HashMap⽆异。

示例:

public class TestConcurrentHashMap {
    public static void main(String[] args) {
        ConcurrentHashMap map = new ConcurrentHashMap<>();

        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        map.put(Thread.currentThread().getName()+"--->"+j,j+"");
                        System.out.println(map);
                    }
                }
            }).start();;
        }
    }
}

你可能感兴趣的:(Java,进阶,java,开发语言)