• JUC并发编程——集合类不安全及Callable(基于狂神说的学习笔记)


    集合类不安全

    List不安全

    package unsafe;
    
    import PC.A;
    
    import java.util.*;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    // ArrayList线程不安全,在多线程下使用ArrayList会报错:
    // java.util.ConcurrentModificationException 并发修改异常
    public class ListTest {
    
        public static void main(String[] args) {
            // 并发下ArrayList 不安全
            /**
             * 解决方案:
             * 1、List list= new Vector<>();
             * 实际上,Vector在jdk1.0就已经出来了,ArrayList在jdk1.2才出来
             * Vector实际上就是在add时将方法synchronized锁起来了
             * 2、List list = Collections.synchronizedList(new ArrayList<>());// 通过工具类将其转换乘synchronized保证线程安全
             * 3、List list = new CopyOnWriteArrayList<>();
             */
            //List list = new ArrayList<>();
            //List list = Collections.synchronizedList(new ArrayList<>());
            List<String> list = new CopyOnWriteArrayList<>();
            // CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
            // 多个线程调用的时候 list,读取的时候,固定的,写入的时候可能会覆盖
            // 在写入的时候避免覆盖,造成数据问题
            // CopyOnWriteArrayList比Vector的优势在于,copyOnWriteArrayList在并发下效率要比vector更高,因为CopyOnWriteArrayList使用的是写时复制策略,而非使用synchronized锁
            for (int i = 1; i <= 10; i++) {
                new Thread(()->{
                    list.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    CopyOnWriteArrayList与vector对比,以下来自CSDN智能助手的回答:

    Java中的CopyOnWriteArrayList和Vector都是线程安全的动态数组,可以在多线程环境下使用。

    CopyOnWriteArrayList使用了一种特殊的写时复制机制,它在对数组进行修改时,会创建一个新的副本,而不是直接在原数组上进行修改。这样可以确保在多线程并发修改时不会发生问题。然而,由于每次修改都需要创建一个新的副本,所以在写入操作频繁的情况下,CopyOnWriteArrayList的性能可能会比较低。

    Vector是一个同步的动态数组,它使用了synchronized关键字来保证多线程安全。当多个线程同时修改Vector时,每个线程会依次获得锁,并按顺序执行相应的修改操作。虽然Vector具有线程安全性,但在高并发情况下,由于需要频繁的获取锁和释放锁,可能会导致性能下降。

    因此,如果在多线程环境下需要频繁进行读操作,而写操作较少的情况下,CopyOnWriteArrayList可能会更适合。而如果需要频繁进行写操作或者对线程安全性要求不是非常高,可以考虑使用Vector。

    而后笔者在IDEA中翻阅关于CopyOnWriteArralyList的add()方法源码发现,它还是使用synchronized关键字,源码如下:

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    然后笔者去查了文心一言的回答,得到的答案依然是CopyOnWriteArralyList没有使用synchronized锁,回答如下:

    CopyOnWriteArrayListadd()方法不是使用synchronized关键字来实现的。实际上,CopyOnWriteArrayListadd()方法内部使用的是一种称为"写时复制"(Copy-on-Write)的技术。

    在写时复制策略中,数据结构被修改时,修改操作会在一个新的副本上进行,而不是直接在原始数据上进行。这样,读取操作可以在原始数据上进行,而不需要等待写入操作完成。写入操作完成后,原始数据会被丢弃,新的副本会成为新的原始数据。

    CopyOnWriteArrayList中,当调用add()方法时,它会创建一个新的数组副本,将新元素添加到该副本中,然后将原始数组引用指向新数组。由于创建新数组和修改操作是在一个单独的写锁上进行的,因此多个线程可以同时读取原始数组,而不会看到正在进行的写入操作。

    因此,CopyOnWriteArrayListadd()方法并没有使用synchronized关键字来实现线程安全。而是使用写时复制技术和读写锁来实现更细粒度的并发控制。

    因此,copyWriteArrayList在并发下是否效率更高有待商榷

    Set不安全

    package unsafe;
    
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.Set;
    import java.util.UUID;
    import java.util.concurrent.CopyOnWriteArraySet;
    
    public class SetTest {
    
        public static void main(String[] args) {
    
           // Set set = new HashSet<>(); // 线程不安全
           // Set set = Collections.synchronizedSet(new HashSet<>());// 通过工具类将其转换成synchronized保证线程安全
            Set<String> set = new CopyOnWriteArraySet<>();// 通过读写复制策略保证线程安全
    
            for (int i = 1; i <= 20; i++) {
                new Thread(()->{
                    set.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(set);
                },String.valueOf(i)).start();
    
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    hashSet的底层是什么?

    HashSet本质上就是HashMap,源码:

    /**
     * Constructs a new, empty set; the backing {@code HashMap} instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }
    
    // add() set本质就是map key是无法重复的
        /**
         * Adds the specified element to this set if it is not already present.
         * More formally, adds the specified element {@code e} to this set if
         * this set contains no element {@code e2} such that
         * {@code Objects.equals(e, e2)}.
         * If this set already contains the element, the call leaves the set
         * unchanged and returns {@code false}.
         *
         * @param e element to be added to this set
         * @return {@code true} if this set did not already contain the specified
         * element
         */
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
    // PRESENT 是一个常量
        // Dummy value to associate with an Object in the backing Map
        private static final Object PRESENT = new Object();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    HashMap不安全

    ConcurrentHashMap<>()

    package unsafe;
    
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    
    public class MapTest {
    
        public static void main(String[] args) {
            // 线程不安全
            // map 是下面这条语句这样用的码?----> 不是,工作中不用HashMap
            // 默认等价于什么? -----> Map map = new HashMap<>(16,0.75);
            // Map map = new HashMap<>();
    
            // 线程安全
            Map<String,String> map = new ConcurrentHashMap<>();
            for (int i = 1; i <= 30; i++) {
                new Thread(()->{
                    map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                    System.out.println(map);
                },String.valueOf(i)).start();
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    Callable

    特点

    1、可以有返回值

    2、可以抛出异常

    3、方法不同,run() / call()

    package callable;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class CallableTest {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            // new Thread(new Runnable()).start();
            // 等价于 new Thread(new FutureTask()).start();
            // new Thread(new FutureTask(Callable)).start();--->FutureTask的构造器为Callable
            new Thread().start();// 怎么启动callable
            MyThread thread = new MyThread();
            FutureTask futureTask = new FutureTask(thread);// 适配类
            new Thread(futureTask,"A").start();
            new Thread(futureTask,"B").start();// 结果会被缓存,提高效率
            // 这个get方法可能会产生阻塞,加入call()是一个耗时操作,则get需要等待返回值
            // 一般将get放在最后,或者使用异步通信来处理
            Integer o = (Integer) futureTask.get();// 获取Callable的返回结果
            System.out.println(o);
    
        }
    }
    
    class MyThread implements Callable<Integer> {
    
    
        @Override
        public Integer call() throws Exception {
            System.out.println("call()");
            return 1024;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    细节:

    1、 有缓存

    2、结果可能需要等待,会阻塞

  • 相关阅读:
    安卓期末大作业——售票APP源码和设计报告
    vite打包部分页面不显示问题+图片不显示问题
    原生js--购物车案例
    集合框架的认识(三)
    安装 Unity 个人免费版
    python爬虫基础-response响应头
    【集装箱调度】基于粒子群算法实现考虑重量限制和时间约束的集装箱码头满载AGV自动化调度附matlab代码
    JavaWeb三层架构
    docker-compose一键部署mysql
    量化INT8模型报错记录及解决方案
  • 原文地址:https://blog.csdn.net/whale_cat/article/details/133827749