原文网址: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
...

总共有四种情况:

  1. 正常输出
  2. 输出值为null;
  3. 数组越界异常;
  4. 某些线程没有输出值;

线程不安全的原因分析

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. 线程1 赋值 element[1] = "a"; 随后因为时间片用完而中断;
  2. 线程2 赋值 element[1] = "b; 随后因为时间片用完而中断;
        此处导致了之前所说的一个问题(有的线程没有输出); 因为后续的线程将前面的线程的值覆盖了。
  3. 线程1 自增 size++; (size=2)
  4. 线程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:线程1 判断数组是否越界。因为size=2 长度为2,没有越界,将进行赋值操作。但是因为时间片用完而中断。
  2. 语句4:线程2 判断数组是否越界。因为size=2 长度为2,没有越界,将进行赋值操作。但是因为时间片用完而中断。
  3. 语句2,3:线程1重新获取到时间片,上文判断了数组不会越界,所以进行赋值操作element[size]=“a”,并且size++
  4. 语句5,6:线程2重新获取到时间片,上文判断了数组不会越界,所以进行赋值操作。但是此时的size=3了,再执行element[3]="b"导致了数组越界。

由此处可以看出因为数组的当前指向size并未进行加锁的操作,导致了数组越界的情况出现。

其他网址

ArrayList 线程安全问题_Java_Be yourself.-CSDN博客

LinkedList的线程安全处理_筱白to的博客-CSDN博客_线程安全的linkedlist

Java之List系列--ArrayList保证线程安全的方法相关推荐

  1. Java之List系列--ArrayList扩容的原理

    原文网址:Java之List系列--ArrayList扩容的原理_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍Java的ArrayList是如何进行扩容的.即:扩容的机制. 重要大小 类 初 ...

  2. JAVA基础知识系列---进程、线程安全

    1.1 临界区 保证在某一时刻只有一个线程能访问数据的简便方法,在任意时刻只允许一个线程对资源进行访问.如果有多个线程试图同时访问临界区,那么在有一个线程进入后,其他所有试图访问临界区的线程将被挂起, ...

  3. JAVA源码系列-ArrayList

    前言 ArrayList是一个基于数组的数据结构,Java1.8版本加入了Lambda匿名内部类的新特性.而ArrayList实现了java.util.function的接口,进而为了支持Lambda ...

  4. java runnable 参数_Java向Runnable线程传递参数方法实例解析

    java Runnable接口:是一个接口,它里面只有一个run()方法,没有start()方法,继2113承Runnable并实现这个方法就可以实现多线程了,但是5261这个run()方法不能自41 ...

  5. java学习(93):线程的创建方法二

    //创建线程的第二种方法class Xc2 implements Runnable {public void run(){for (int i=0;i<20;i++){System.out.pr ...

  6. java全栈系列之JavaSE-面向对象(方法重写)037

    子类在调用父类的私有方法中不能直接调用,但是可以通过get方法进行调用,修改属性的值可以通过set方法进行修改.而子类想要修改父类中的方法可以使用方法重写进行操作. 方法重写与之前的方法重载不同 回顾 ...

  7. java全栈系列之JavaSE-面向对象(方法的定义与调用)030

    java面向对象编程 面向对象&面向过程 前言: 之前的代码大多数都是属于面向过程的,在程序中有main()方法也叫主方法,程序会由上至下执行main()里面的内容.程序里的方法:例如编写一个 ...

  8. Java中,通过synchronizedXxx()将线程不安全的集合转换成线程安全的集合

    保证线程安全的方法 synchronizedXxx():该方法可以使指定的集合包装成线程安全的集合,并返回 例如: Collections.synchronizedList(List) 将线程不安全的 ...

  9. Java集合Collection源码系列-ArrayList源码分析

    Java集合系列-ArrayList源码分析 文章目录 Java集合系列-ArrayList源码分析 前言 一.为什么想去分析ArrayList源码? 二.源码分析 1.宏观上分析List 2.方法汇 ...

最新文章

  1. Connot resolve Symbol '.......'
  2. linux cpu占用率 监控工具 简介
  3. 行为型模式:策略模式
  4. UVALive 4394 String painter
  5. Linux基础学习五(1):java项目部署实战教程
  6. 深圳行:1207-Day 1 - 到达
  7. call center外包公司如何营销成“茶颜悦色”?
  8. js实现斗地主计分器
  9. word2013 打开一个新文档,之前打开的最小化的文档也会弹出来?
  10. 什么是EMC?什么是EMI?什么是EMS?电磁兼容详解(一)
  11. 关于华为和荣耀手机升级鸿蒙系统之后无法连接magicbook多屏协同的问题
  12. listview距离顶端白边
  13. 手把手教你如何通过大厂面试
  14. r语言和python的区别-Python和R语言之分析对比
  15. SQL注入的原理、过程及如何防范
  16. 香料图片加名称及其作用
  17. C语言名题精选百则——数字问题
  18. SSM学生档案管理系统的设计与实现
  19. 吾生也有涯,吾知也无涯_乌拉(3)
  20. 【Java】JDK彻底卸载

热门文章

  1. 开发一个类似美团的外卖小程序多少钱
  2. 西安恒智小寨java_长安反编译工具 java
  3. get技能 TCP(ip socket 关系)通信的三次握手和四次撒手的详细流程(顿悟)
  4. 160413、生成随机校验码
  5. 遗传算法--函数最值问题
  6. NSA方程式泄漏工具包浅析
  7. 碰到文件名目录名或卷标语法不正确怎样解决?
  8. python制作阴阳师挂机脚本
  9. 11种图像清晰度评价函数附MATLAB代码
  10. PB控件属性之Tab