Java之List系列--ArrayList保证线程安全的方法
原文网址:Java之List系列--ArrayList保证线程安全的方法_IT利刃出鞘的博客-CSDN博客
简介
本文介绍Java中的ArrayList、LinkedList如何进行线程安全的操作、为什么ArrayList不是线程安全的。
这几个问题也是Java后端面试中经常问到的问题。
线程安全的操作方法
ArrayList
方法 |
示例 |
原理 |
Vector |
List list = new ArrayList(); 替换为List arrayList = new Vector<>(); |
使用了synchronized关键字 |
Collections .synchronizedList(list) |
List<String> list = Collections .synchronizedList(new ArrayList<String>()); 操作外部list,实际上修改的是原来list的数据。 |
所有方法都加了synchronized修饰。加锁的对象是当前SynchronizedCollection实例。 |
JUC中的 CopyOnWriteArrayList |
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); 适用于读多写少的并发场景。 |
Write的时候总是要Copy(将原来array复制到新的array,修改后,将引用指向新数组)。任何可变的操作(add、set、remove等)都通过ReentrantLock 控制并发。 |
LinkedList
方法 | 示例 | 原理 |
Collections.synchronizedList(List) | public static List linkedList = Collections.synchronizedList(new LinkedList()); | 所有方法都加了synchronized修饰。加锁的对象是当前SynchronizedCollection实例。 |
JUC中的ConcurrentLinkedQueue | ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue(); |
线程不安全问题复现
实例
package org.example.a;import java.util.ArrayList;
import java.util.List;class MyThread extends Thread{public void run(){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}Demo.arrayList.add(Thread.currentThread().getName() + " " + System.currentTimeMillis());}
}public class Demo{public static List arrayList = new ArrayList();public static void main(String[] args) {Thread[] threadArray = new Thread[1000];for(int i = 0;i < threadArray.length;i++){threadArray[i] = new MyThread();threadArray[i].start();}for(int i = 0;i < threadArray.length;i++){try {threadArray[i].join();} catch (InterruptedException e) {e.printStackTrace();}}for(int i = 0;i < arrayList.size(); i++){System.out.println(arrayList.get(i));}}
}
运行结果
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 49at java.util.ArrayList.add(ArrayList.java:459)at org.example.a.MyThread.run(Demo.java:13)
Thread-3 1590288167830
Thread-7 1590288167834
Thread-57 1590288167834
...
null
Thread-951 1590288168255
Thread-254 1590288168255
...
总共有四种情况:
- 正常输出
- 输出值为null;
- 数组越界异常;
- 某些线程没有输出值;
线程不安全的原因分析
ArrayList源码
public boolean add(E e) {// 确保ArrayList的长度足够ensureCapacityInternal(size + 1); // Increments modCount!!// ArrayList加入elementData[size++] = e;return true;
}private void ensureCapacityInternal(int minCapacity) {if (elementData == EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);
}private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity);
}// 如果超过界限 数组长度增长
private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);
}
在上述过程中,会出问题的地方是: 1. 增加元素 2. 扩充数组长度;
情景1:增加元素
增加元素过程中较为容易出现问题的地方是elementData[size++] = e;。赋值的过程可以分为两个步骤elementData[size] = e; size++;。
例如size为1,有两个线程,分别加入字符串“a”与字符串“b”:
如果四条语句按照:1,2,3,4执行,那么没有问题。
如果按照1,3,2,4来执行,就会出错。以下步骤按时间先后排序:
- 线程1 赋值 element[1] = "a"; 随后因为时间片用完而中断;
- 线程2 赋值 element[1] = "b; 随后因为时间片用完而中断;
此处导致了之前所说的一个问题(有的线程没有输出); 因为后续的线程将前面的线程的值覆盖了。 - 线程1 自增 size++; (size=2)
- 线程2 自增 size++; (size=3)
此处导致了某些值为null的问题。因为原来size=1, 但是因为线程1与线程2都将值赋值给了element[1],导致了element[2]内没有值,被跳过了。此时指针index指向了3,所以导致了值为null的情况。
情景2:数组越界
例如:size为2,数组长度限制为2,有两个线程,分别加入字符串“a”与字符串“b”:
如果四条语句按照:1,2,3,4,5,6执行,那么没有问题。
前提条件: 当前size=2 数组长度限制为2。
如果按照1,3,2,4来执行,就会出错。以下步骤按时间先后排序:
- 语句1:线程1 判断数组是否越界。因为size=2 长度为2,没有越界,将进行赋值操作。但是因为时间片用完而中断。
- 语句4:线程2 判断数组是否越界。因为size=2 长度为2,没有越界,将进行赋值操作。但是因为时间片用完而中断。
- 语句2,3:线程1重新获取到时间片,上文判断了数组不会越界,所以进行赋值操作element[size]=“a”,并且size++
- 语句5,6:线程2重新获取到时间片,上文判断了数组不会越界,所以进行赋值操作。但是此时的size=3了,再执行element[3]="b"导致了数组越界。
由此处可以看出因为数组的当前指向size并未进行加锁的操作,导致了数组越界的情况出现。
其他网址
ArrayList 线程安全问题_Java_Be yourself.-CSDN博客
LinkedList的线程安全处理_筱白to的博客-CSDN博客_线程安全的linkedlist
Java之List系列--ArrayList保证线程安全的方法相关推荐
- Java之List系列--ArrayList扩容的原理
原文网址:Java之List系列--ArrayList扩容的原理_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍Java的ArrayList是如何进行扩容的.即:扩容的机制. 重要大小 类 初 ...
- JAVA基础知识系列---进程、线程安全
1.1 临界区 保证在某一时刻只有一个线程能访问数据的简便方法,在任意时刻只允许一个线程对资源进行访问.如果有多个线程试图同时访问临界区,那么在有一个线程进入后,其他所有试图访问临界区的线程将被挂起, ...
- JAVA源码系列-ArrayList
前言 ArrayList是一个基于数组的数据结构,Java1.8版本加入了Lambda匿名内部类的新特性.而ArrayList实现了java.util.function的接口,进而为了支持Lambda ...
- java runnable 参数_Java向Runnable线程传递参数方法实例解析
java Runnable接口:是一个接口,它里面只有一个run()方法,没有start()方法,继2113承Runnable并实现这个方法就可以实现多线程了,但是5261这个run()方法不能自41 ...
- java学习(93):线程的创建方法二
//创建线程的第二种方法class Xc2 implements Runnable {public void run(){for (int i=0;i<20;i++){System.out.pr ...
- java全栈系列之JavaSE-面向对象(方法重写)037
子类在调用父类的私有方法中不能直接调用,但是可以通过get方法进行调用,修改属性的值可以通过set方法进行修改.而子类想要修改父类中的方法可以使用方法重写进行操作. 方法重写与之前的方法重载不同 回顾 ...
- java全栈系列之JavaSE-面向对象(方法的定义与调用)030
java面向对象编程 面向对象&面向过程 前言: 之前的代码大多数都是属于面向过程的,在程序中有main()方法也叫主方法,程序会由上至下执行main()里面的内容.程序里的方法:例如编写一个 ...
- Java中,通过synchronizedXxx()将线程不安全的集合转换成线程安全的集合
保证线程安全的方法 synchronizedXxx():该方法可以使指定的集合包装成线程安全的集合,并返回 例如: Collections.synchronizedList(List) 将线程不安全的 ...
- Java集合Collection源码系列-ArrayList源码分析
Java集合系列-ArrayList源码分析 文章目录 Java集合系列-ArrayList源码分析 前言 一.为什么想去分析ArrayList源码? 二.源码分析 1.宏观上分析List 2.方法汇 ...
最新文章
- Connot resolve Symbol '.......'
- linux cpu占用率 监控工具 简介
- 行为型模式:策略模式
- UVALive 4394 String painter
- Linux基础学习五(1):java项目部署实战教程
- 深圳行:1207-Day 1 - 到达
- call center外包公司如何营销成“茶颜悦色”?
- js实现斗地主计分器
- word2013 打开一个新文档,之前打开的最小化的文档也会弹出来?
- 什么是EMC?什么是EMI?什么是EMS?电磁兼容详解(一)
- 关于华为和荣耀手机升级鸿蒙系统之后无法连接magicbook多屏协同的问题
- listview距离顶端白边
- 手把手教你如何通过大厂面试
- r语言和python的区别-Python和R语言之分析对比
- SQL注入的原理、过程及如何防范
- 香料图片加名称及其作用
- C语言名题精选百则——数字问题
- SSM学生档案管理系统的设计与实现
- 吾生也有涯,吾知也无涯_乌拉(3)
- 【Java】JDK彻底卸载