问题: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());
}
}

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所以,他们两个底层原理一样
 

如上示例用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());
}
}

测试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.concurrent.CopyOnWriteXXXX的更多相关文章

  1. “全栈2019”Java多线程第十二章:后台线程setDaemon()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  2. Java 多线程基础(十)interrupt()和线程终止方式

    Java 多线程基础(十)interrupt()和线程终止方式 一.interrupt() 介绍 interrupt() 定义在 Thread 类中,作用是中断本线程. 本线程中断自己是被允许的:其它 ...

  3. “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  4. 最全java多线程总结3——了解阻塞队列和线程安全集合不

      看了前两篇你肯定已经理解了 java 并发编程的低层构建.然而,在实际编程中,应该经可能的远离低层结构,毕竟太底层的东西用起来是比较容易出错的,特别是并发编程,既难以调试,也难以发现问题,我们还是 ...

  5. java多线程中用到的方法详细解析

    在多线程学习的过程中涉及的方法和接口特别多,本文就详细讲解下经常使用方法的作用和使用场景. 1.sleep()方法.      当线程对象调用sleep(time)方法后,当前线程会等待指定的时间(t ...

  6. 【JAVA多线程中使用的方法】

    一.sleep和wait的区别. 1.wait可以指定时间,也可以不指定. 而sleep必须制定. 2.在同步的时候,对于CPU的执行权和以及锁的处理不同. wait:释放执行权,释放锁. sleep ...

  7. Java多线程中的join()方法

    一.join()方法介绍 join() 定义在Thread.java中.join()方法把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程.比如在线程B中调用了线程A的join( ...

  8. java 多线程中的wait方法的详解

    java多线程中的实现方式存在两种: 方式一:使用继承方式 例如: PersonTest extends Thread{ String name; public PersonTest(String n ...

  9. java 多线程——join()方法

    在java语言中,join()方法的作用是让调用该方法的线程在执行完run()方法后,再执行join 方法后面的代码. 简单点说就是,将两个线程合并,用于实现同步的功能. 具体而言:可以通过线程A的j ...

随机推荐

  1. Identity Server 4 从入门到落地(一)—— 从IdentityServer4.Admin开始

    最近项目中需要使用Identity Server 4,以前对这个技术只是有些了解,没有系统研究过,网上相关的资料不少,大多是从编写一个简单的认证服务开始,离能够落地使用有相当的距离,理论学习如何不结合 ...

  2. BehaviorTree.CPP行为树BT的选择节点(四)

    Fallback 该节点家族在其他框架中被称为"选择器Selector"或"优先级Priority". 他们的目的是尝试不同的策略,直到找到可行的策略. 它们具 ...

  3. 【机器学*与R语言】2-懒惰学*K*邻(kNN)

    目录 1.理解使用KNN进行分类 KNN特点 KNN步骤 1)计算距离 2)选择合适的K 3)数据准备 2.用KNN诊断乳腺癌 1)收集数据 2)探索和准备数据 3)训练模型 4)评估模型的性能 5) ...

  4. kafka的安装及使用

    前言花絮 今天听了kafka开发成员之一的饶军老师的讲座,讲述了kafka的前生今世.干货的东西倒是没那么容易整理出来,还得刷一遍视频整理,不过两个比较八卦的问题,倒是很容易记住了. Q:为什么kaf ...

  5. EXCEL-排名前三名显示小红旗,后三名显示小黑旗

    总结(用的WPS):第一步:用=IF(RANK(数值,引用范围) <=3,"小红旗",IF(RANK(数值,引用,1) <=3,"小黑旗",&quo ...

  6. 学习java第十九天

    一.今日收获 1.java完全学习手册第三章算法的3.2排序,比较了跟c语言排序上的不同 2.观看哔哩哔哩上的教学视频 二.今日问题 1.快速排序法的运行调试多次 2.哔哩哔哩教学视频的一些术语不太理 ...

  7. 1005.K次取反后最大化的数组和

    1005.K次取反后最大化的数组和 目录 1005.K次取反后最大化的数组和 题目 题解 排序+维护最小值min 题目 给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 ...

  8. 学习java 7.25

    学习内容: 特殊边框 1. TitledBorder:它的作用并不是直接为其他组件添加边框,而是为其他边框设置标题,创建该类的对象时,需要传入一个其他的Border对象; 2. CompoundBor ...

  9. 如何在 ASP.NET Core 中构建轻量级服务

    在 ASP.NET Core 中处理 Web 应用程序时,我们可能经常希望构建轻量级服务,也就是没有模板或控制器类的服务. 轻量级服务可以降低资源消耗,而且能够提高性能.我们可以在 Startup 或 ...

  10. 用户体验再升级!Erda 1.2 版本正式发布

    来源|尔达 Erda 公众号 Erda v1.2 Changelog: https://github.com/erda-project/erda/blob/master/CHANGELOG/CHANG ...