多线程下ArrayList类线程不安全的解决方法及原理

ArrayList类在多线程环境下是线程不安全的,在多线程读写情况下会抛出并发读写异常(ConcurrentModificationException):

 1 import java.util.ArrayList;
 2 import java.util.List;
 3 import java.util.UUID;
 4
 5 public class NoSafeArrayList {
 6     public static void main(String[] args) {
 7
 8         List<String> list=new ArrayList();
 9         for (int i=0;i<30;i++) {
10             new Thread(()->{
11                 list.add(UUID.randomUUID().toString().substring(8));  //UUID工具类,取一个八位的随机字符串  ,还有一个常用的取不重复字符串的方法:system.currentTime()  当前时间戳
12                 System.out.println(list);
13             }).start();
14         }
15     }
16 }

解决方法:

1,用vector类

  Vector类 是可以实现自动增长的对象数组,其add操作是用synchronized关键字修饰的,从而保证了add方法的线程安全。保证了数据的一致性,但由于加锁导致访问性能大大降低。

  vector类的add方法:

2,使用Collections工具类

用Collections工具类将线程不安全的ArrayList类转换为线程安全的集合类。小体量数据的ArrayList类可以使用这种方法创建线程安全的类。

1 List<String> list=Collections.synchronizedList(new ArrayList);

3,使用CopyOnWriteArrayList类(写时复制,读写分离)

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

 1  public boolean add(E e) {
 2     //1、先加锁
 3     final ReentrantLock lock = this.lock;
 4     lock.lock();
 5     try {
 6         Object[] elements = getArray();
 7         int len = elements.length;
 8         //2、拷贝数组
 9         Object[] newElements = Arrays.copyOf(elements, len + 1);
10         //3、将元素加入到新数组中
11         newElements[len] = e;
12         //4、将array引用指向到新数组
13         setArray(newElements);
14         return true;
15     } finally {
16        //5、解锁
17         lock.unlock();
18     }
19 }

CopyOnWriteArrayList的整个add操作都是在锁的保护下进行的。
这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的。

线程并发的写,则通过锁来控制,如果有线程并发的读,则分几种情况:
1、如果写操作未完成,那么直接读取原数组的数据;
2、如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据;
3、如果写操作完成,并且引用已经指向了新的数组,那么直接从新数组中读取数据。

原文地址:https://www.cnblogs.com/fangtingfei/p/12019313.html

时间: 2024-10-28 11:40:08

多线程下ArrayList类线程不安全的解决方法及原理的相关文章

多线程下的进程同步(线程同步问题总结篇)

之前写过两篇关于线程同步问题的文章(一,二),这篇中将对相关话题进行总结,本文中也对.NET 4.0中新增的一些同步机制进行了介绍. 首先需要说明的是为什么需要线程功能同步.MSDN中有这样一段话很好的解释了这个问题: 当多个线程可以调用单个对象的属性和方法时,对这些调用进行同步处理是非常重要的.否则,一个线程可能会中断另一个线程正在执行的任务,使该对象处于一种无效状态. 也就说在默认无同步的情况下,任何线程都可以随时访问任何方法或字段,但一次只能有一个线程访问这些对象.另外,MSDN中也给出定

Application.Exit()结束程序,但线程还在的解决方法。

Application.Exit()结束程序,但线程还在的解决方法. 出现此情况大多原因是使用了多线程编程,或者你所调用的dll使用了多线程.我们知道,一般情况下的线程执行完指定的任务之后是会关闭了的,但是如果对于一些循环类线程,或者忘记关掉的线程时,这个时候就需要我们手动将之强制关闭.用以下三个中的一个即可尝试强制关闭线程. 复制内容到剪贴板 代码: Application.ExitThread();//退出当前线程上的消息循环,并关闭该线程上的所有窗口.    复制内容到剪贴板 代码: Sy

Centos下忘记mysql的root密码的解决方法

Centos下忘记mysql的root密码的解决方法 一:(停掉正在运行的mysql) [[email protected] ~]# service mysql stop 二:使用 “--skip-grant-tables”参数重新启动mysql [[email protected] ~]# mysqld_safe --skip-grant-tables & [1] 23810 Starting mysqld daemon with databases from /var/lib/mysql 三

虚拟机下centos时间不正确的方便解决方法

就是用NTP了,通过外部的服务同步时间. ntpdate us.pool.ntp.org | logger -t NTP 如果没有ntpdate ,可以使用 yum install ntpdate 进行安装. 当然最好放在crontab里面,各一段时间同步一次就OK. crontab -e * * * * * /usr/sbin/ntpdate us.pool.ntp.org | logger -t NTP 每分钟同步一次,够狠吧. ===============================

stm32学习笔记之win8系统下,keil4出现黑块的解决方法

前不久,笔者安装keil4启动会出现黑块,如图所示 当时询问了不少技术群都没有找到解决办法,并且还在百度贴吧发贴,最终都无果而终 这是当时发贴地址 http://tieba.baidu.com/p/3176578044 后来重做了个系统,才勉强能使用.直至今天又出现了同样的状况.在此之间笔者发现当keil4出现黑块,win8自带的记事本也会出现未响应状况,于是上网找解决方法,最终网友 oafaq给了我思路 这是他的原文地址 http://blog.sina.com.cn/key9928 .原来我

CentOS下找不到eth0设备的解决方法

问题描述: ifconfig命令无法找到eth0设备,且/etc/sysconfig/network-scripts/中只有ifcfg-lo文件,而没有ifcfg-eth0. 临时解决方法一: 使用命令ifconfig eth0 192.168.1.x可以正常设置eth0的IP,该方法仅为临时处理办法,系统重启后即失效了. 永久解决方法二: 1.在/etc/sysconfig/network-scripts/目录下新建ifcfg-eth0文件: 2.正确设置ifcfg-eth0的DEVICE.B

C#多线程开发4:线程的Abort和ResetAbort方法

使用Abort方法可以中止线程,而使用ResetAbort方法可以取消中止线程的操作. 下面的实例演示了Abort和ResetAbort方法的使用. <span style="font-size:14px;">using System; using System.Threading; namespace AbortAndResetabortExp { class Program { static void Main(string[] args) { Thread t = n

Linux下error while loading shared libraries的解决方法

1. 出现error while loading shared libraries的原因 1-1. 不存在该共享库,如果是这个原因,需要下载或者编译该共享库先了. 1-2. 存在该共享库,但是找不到或者共享库的不对 如果是第二种情况,请继续往下看. 2.原因分析 系统查看共享库的过程:首先查找 /etc/ld.so.cache文件,如果找不到就查找环境变量里的LD_LIBRARY_PATH的值,如果找到了就到对应的目录加载该共享库,如果找不到就报error while loading share

centos下bash: XXX: command not found的解决方法

最近想在centos下做Android项目构建,配置好jdk和Android sdk后,同时也在/etc/profile将java和Android的环境变量配置进去,但是却无法像windows 下一样,直接使用android update project命令. 出现情况如下:bash: android: command not found 此时,我们可以使用ln命令将android 添加到/usr/bin目录下,详细解决方法如下: 1.使用cd /usr/bin 进入到该目录 2.使用ln -s