集合类不安全之Set

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/longgeqiaojie304/article/details/89819422

大厂面试题:我们知道Set是线程不安全的,请编码写一个不安全的案例并给出解决方案?

1、Set线程不安全问题产生

import java.util.HashSet;

import java.util.Set;

import java.util.UUID;

 

public class ContainerNotSafeDemoTwo {

    public static void main(String[] args) {

        Set<String> set = new HashSet<>();

        for (int i = 0; i < 30; i++) {

            new Thread(() -> {

                set.add(UUID.randomUUID().toString());

                System.out.println(set.toString());

            }, "T1").start();

        }

    }

}

程序执行结果如下:报java.util.ConcurrentModificationException异常

 

2、ConcurrentModificationException产生的原因

一个线程正在写,另一个线程过来抢占资源,会造成数据不一致,进而报并发修改异常。

 

3、Set线程不安全解决方案

(1)第一种解决方案

使用Collections工具类创建同步集合类

 

import java.util.Collections;

import java.util.HashSet;

import java.util.Set;

import java.util.UUID;

 

public class ContainerSafeSetDemoOne {

    public static void main(String[] args) {

        Set<String> set = Collections.synchronizedSet(new HashSet<>());

        for (int i = 0; i < 30; i++) {

            new Thread(() -> {

                set.add(UUID.randomUUID().toString());

                System.out.println(set.toString());

            }, String.valueOf(i)).start();

        }

    }

}

(2)第二种解决方案

使用并发编程类CopyOnWriteArraySet替换HashSet

 

import java.util.Set;

import java.util.UUID;

import java.util.concurrent.CopyOnWriteArraySet;

 

public class ContainerSafeSetDemoTwo {

    public static void main(String[] args) {

        Set<String> set = new CopyOnWriteArraySet<>();

        for (int i = 0; i < 30; i++) {

            new Thread(() -> {

                set.add(UUID.randomUUID().toString());

                System.out.println(set.toString());

            }, String.valueOf(i)).start();

        }

    }

}

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

 

    public boolean add(E e) {

        return al.addIfAbsent(e);

}

 

    /**

     * Appends the element, if not present.

     *

     * @param e element to be added to this list, if absent

     * @return {@code true} if the element was added

     */

    public boolean addIfAbsent(E e) {

        Object[] snapshot = getArray();

        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :

            addIfAbsent(e, snapshot);

}

 

    /**

     * A version of addIfAbsent using the strong hint that given

     * recent snapshot does not contain e.

     */

    private boolean addIfAbsent(E e, Object[] snapshot) {

        final ReentrantLock lock = this.lock;

        lock.lock();

        try {

            Object[] current = getArray();

            int len = current.length;

            if (snapshot != current) {

                // Optimize for lost race to another addXXX operation

                int common = Math.min(snapshot.length, len);

                for (int i = 0; i < common; i++)

                    if (current[i] != snapshot[i] && eq(e, current[i]))

                        return false;

                if (indexOf(e, current, common, len) >= 0)

                        return false;

            }

            Object[] newElements = Arrays.copyOf(current, len + 1);

            newElements[len] = e;

            setArray(newElements);

            return true;

        } finally {

            lock.unlock();

        }

    }

猜你喜欢

转载自blog.csdn.net/longgeqiaojie304/article/details/89819422