java 多线程 集合的包装方法Collections.synchronizedXXXXX;线程安全的集合类:Java.util.concurrent.ConcurrentXXX;java.util.

问题:ArrayList  等线程不安全

当多线程并发修改一个集合数据时,可能同一个下标位置被覆盖。

示例代码:

一个List,我们创建10个线程,每个线程往这个List中添加1000条数据,结果往往不是预期的10000个大小:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName ForkJoinPoolArrayListNotSafe
 * @projectName: object1
 * @author: Zhangmingda
 * @description: XXX
 * date: 2021/4/28.
 */
public class ForkJoinPoolArrayListNotSafe {
    public static void main(String[] args) throws InterruptedException {
        /**
         * 存放数据的集合
         */
        List<Integer> nums = new ArrayList<>();
        /**
         * 随机数类
         */
        Random random = new Random();
        /**
         * 线程池
         */
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        /**
         * 线程池提交任务类
         */
        for (int j=0; j<10; j++){
            forkJoinPool.submit(new RecursiveAction() {
                @Override
                protected void compute() {
                    for (int i=0; i<1000; i++){
                        nums.add(random.nextInt());
                    }
                }
            });
            System.out.println((j+1) + "千次提交");
        }
        /**
         * 等待执行结束
         */
        forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);
        /**
         * 关闭提交入口
         */
        forkJoinPool.shutdown();
        /**
         * 查看执行结果
         */
        System.out.println("计算结果:num.size():" + nums.size());
    }
}

java 多线程 集合的包装方法Collections.synchronizedXXXXX;线程安全的集合类:Java.util.concurrent.ConcurrentXXX;java.util.

 

1、非线程安全集合~转~线程安全包装方法:Collections.synchronizedXXXXX(非线程安全集合)

将非线程安全集合转为线程安全集合(底层实现逻辑:synchronized 效果变为串行)Collctions提供了如下几个静态方法

  • static <T> Collection<T> synchronizedCollection(Collection<T> c): 通过c返回一个线程安全的Collection
  • static <T> List synchronizedList(List<T> list):通过List返回一个线程安全的List
  • static <K,V> Map<K,V> synchronizedMap(Map<K,V> map):通过map返回一个线程安全的map
  • static <T> Set<T> synchronizedSet(Set set): 通过set返回一个线程安全的set
  • static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> sortedMap): 通过sortedMap返回一个线程安全的SortedMap
  • static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> sortedSet): 通过SortedSet返回一个线程安全的SortedSet

如上示例代码包装后:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName ForkJoinPoolArrayListSynchornized
 * @projectName: object1
 * @author: Zhangmingda
 * @description: XXX
 * date: 2021/4/28.
 */
public class ForkJoinPoolArrayListSynchornized {
    public static void main(String[] args) throws InterruptedException {
        /**
         * 集合包装类包装线程不安全集合
         */
        List<Integer> nums = Collections.synchronizedList(new ArrayList<>());
        Random random = new Random();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        /**
         * 提交多线程任务向集合添加1万个元素
         */
        for (int j=0; j<10; j++){
            for (int i=0; i<1000; i++){
                forkJoinPool.submit(new RecursiveAction() {
                    @Override
                    protected void compute() {
                        nums.add(random.nextInt());
                    }
                });
            }
        }
        /**
         * 等待执行结果
         */
        forkJoinPool.awaitTermination(1, TimeUnit.SECONDS);
        forkJoinPool.shutdown();
        System.out.println("num.size():" + nums.size());
    }
}

2、线程安全的集合(Java.util.concurrent包下)

从Java5开始,在Java.util.concurrent包下提供了大量支持高效并发访问的集合接口和实现类,如下图所示以Concurrent开头的集合类,和 CopyOnWrite开头的集合类都是。   以Concurrent开头的集合类,代表了支持并发访问的集合,它们可以支持多个线程并发写入访问,这些写入线程的所有操作都是线程安全的,但读取操作不必锁定。Concurrent开头的集合类采用了更复杂的算法来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。
  •  ConcurrentLinkedQueue
  • ConcurrentHashMap
当多个线程共享访1个公共集合时,ConcurrentLinkedQueue是很好的选择 ConcurrentLinkedQueue不允许使用null元素,ConcurrentLinkedQueue 实现了多线程的高效访问。多个线程访问 ConcurrentLinkedQueue 集合时无须等待。   在默认情况下ConcurrentHashMap支持16个线程并发写入,当有超过16个线程并发向该Map中写入数据时,可能有某些线程需要等待。实际上程序通过设置ConcurrentHashMap构造参数(默认值为16)来支持更多的并发写入线程。 与普通集合不同的是,因为ConcurrentLinkedQueue,ConcurrentHashMap支持多线程并发访问,所以当使用迭代器来遍历集合元素时,该迭代器可能不能反映出创建迭代器之后所做的修改,但程序不会抛出任何异常

3、CopyOnWrite集合的介绍:

当线程对 CopyOnWriteArrayList 集合执行读取操作时,线程将会直接读取集合本身,无须加锁与阻塞。当线程对CopyOnWriteArrayList集合执行写入操作 , 该集合会在底层复制一份新的数组,接下来对新的数组执行写入操作。由于对CopyOnWriteArrayList集合写入操作都是对数组的副本执行复制操作,因此它是线程安全的。 但是写入的时候需要频繁的复制底层的数组,所以会造成写入的性能很差。所以它适合于大量读,但是写很少的情况。 CopyOnWriteArraySet底层其实就是封装了一个CopyOnWriteArrayList所以,他们两个底层原理一样  

java 多线程 集合的包装方法Collections.synchronizedXXXXX;线程安全的集合类:Java.util.concurrent.ConcurrentXXX;java.util.

如上示例用CopyOnWriteArrayList代替

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName ForkJoinPoolArrayListSynchornized
 * @projectName: object1
 * @author: Zhangmingda
 * @description: XXX
 * date: 2021/4/28.
 */
public class ForkJoinPoolCopyOnWriteArrayList {
    public static void main(String[] args) throws InterruptedException {
        /**
         * 集合包装类包装线程不安全集合
         */
        List<Integer> nums = new CopyOnWriteArrayList<>();
        Random random = new Random();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        /**
         * 提交多线程任务向集合添加1万个元素
         */
        for (int j=0; j<10; j++){
            for (int i=0; i<1000; i++){
                forkJoinPool.submit(new RecursiveAction() {
                    @Override
                    protected void compute() {
                        nums.add(random.nextInt());
                    }
                });
            }
        }
        /**
         * 等待执行结果
         */
        forkJoinPool.awaitTermination(1, TimeUnit.SECONDS);
        forkJoinPool.shutdown();
        System.out.println("num.size():" + nums.size());
    }
}

java 多线程 集合的包装方法Collections.synchronizedXXXXX;线程安全的集合类:Java.util.concurrent.ConcurrentXXX;java.util.

测试ConcurrentHashMap:

import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName ForkJoinPoolConcurrentHashMapTest
 * @projectName: object1
 * @author: Zhangmingda
 * @description: XXX
 * date: 2021/4/28.
 */
public class ForkJoinPoolConcurrentHashMapTest {
    public static void main(String[] args) throws InterruptedException {
        Map<String,Integer> persons = new ConcurrentHashMap<>();
        ForkJoinPool pool = new ForkJoinPool();
        Random random = new Random();
        for (int i=0; i<10; i++){
            for (int j=0; j<1000; j++) {
                pool.submit(new RecursiveAction() {
                    @Override
                    protected void compute() {
                        persons.put("random:" + random.nextInt(), random.nextInt());
                    }
                });
            }
        }
        pool.awaitTermination(15, TimeUnit.MILLISECONDS);
        persons.forEach((k,v) -> System.out.println(k + "=" +v));
        pool.shutdown();
        System.out.println("persons.size:" + persons.size());
    }
}

java 多线程 集合的包装方法Collections.synchronizedXXXXX;线程安全的集合类:Java.util.concurrent.ConcurrentXXX;java.util.

 

 

 

 

上一篇:【死磕JVM】看完这篇我也会排查JVM内存过高了 就是玩儿!


下一篇:技术问答-13 Java线程的实现方式