问题: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. ES6学习 第五章 正则的扩展

    前言 本章介绍正则的扩展.有些不常用的知识了解即可. 本章原文链接:正则的扩展 RegExp 构造函数 从 ES6 开始,如果RegExp构造函数第一个参数是一个正则对象,并且第二个标志存在且为标志参 ...

  2. 洛谷 P4663 - [BalticOI 2008]魔法石(dp)

    题面传送门 A:我该是有多无聊来写这种题的题解啊 B:大概是因为这题题解区里没有题解所以我来写一篇了,说明我有高尚的济世情怀(大雾 跑题了跑题了 首先看到字典序第 \(i\) 小小可以自然地想到按位决 ...

  3. Python基础之列表内置方法

    目录 1. 列表 1.1 序列 1.2 通用的序列操作 1.3 列表的基本操作 1.4 列表方法 1. 列表 数据结构:以某种方式(如通过编号)组合起来的元素(如数,字符乃至其他数据结构)集合. 在p ...

  4. Label -- 跳出循环的思路

    let num = 0 ; outPoint: //label for (let i = 0; i < 10; i++) { for ( let j = 0; j < 10; j++) { ...

  5. 日常Java 2021/10/19

    Java集合框架 Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射. Collection接口又有3种子类型,Li ...

  6. The Ultimate Guide to Buying A New Camera

    [photographyconcentrate] 六级/考研单词: embark, thrill, excite, intimidate, accessory, comprehensive, timi ...

  7. day31 协程

    day31 协程 一.死锁与递归锁 ​ 所谓死锁:是指两个或者两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产 ...

  8. KMP算法思路

    题目 给定一个字符串\(S\),求\(M\)字符串是否是\(S\)字符串中的子串.如果是,返回\(M\)对应\(S\)的第一个下标,否则返回-1. 例如:S串为a b c d a b c d a b ...

  9. [php代码审计] 通读审计之shangfancms

    前言 大部分的MVC框架,访问的控制器大部分是由外部参数来决定的,那么本篇文章所通读的MVC框架与之前的一系列MVC框架不太一样,它的路由是由程序本身的路由表来决定的. 源码下载 https://ww ...

  10. python 从ubantu环境迁移到windows环境

    下载安装Anaconda3 Anaconda3-2021.05-Windows-x86_64.exe 默认安装目录 C:\ProgramData\Anaconda3 可以启动Anaconda查看不同的 ...