IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> JUC并发编程基础学习之List集合线程安全问题 -> 正文阅读

[Java知识库]JUC并发编程基础学习之List集合线程安全问题

2.4 List集合线程不安全

之前我们在使用ArrayList的时候,觉得它并没有不安全,是因为我们是在单线程环境下使用的,如果在多线程环境下,那么ArrayList就不够安全了!

2.4.1 测试List集合是否安全

1.单线程下测试ArrayList集合

  • 源码分析
package java.util;

/**
 * @author  Josh Bloch
 * @author  Neal Gafter
 * @see     Collection
 * @see     List
 * @see     LinkedList
 * @see     Vector
 * @since   1.2 ArrayList从JDK1.2版本开始使用 
 */
//ArrayList集合
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    /**
     * 在当前list集合的尾部追加指定的元素
     *
     * @param 被追加到该list集合中的一个元素
     * @return 布尔型值true
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    /**
     * 在当前list集合的尾部追加指定的元素
     *
     * @param e 被追加到该list集合中的一个元素
     * @return 布尔型值true
     */
    public void add(int index, E e) {
        rangeCheckForAdd(index);
        checkForComodification();
        parent.add(parentOffset + index, e);
        this.modCount = parent.modCount;
        this.size++;
    }
}
  • 测试代码
package com.kuang.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @ClassName ListTest
 * @Description List安全性测试
 * @Author 狂奔の蜗牛rz
 * @Date 2021/7/29
 */
public class ListTest {

    public static void main(String[] args) {

        //1.使用新型语法方式
        //获取一个ArrayList
        List<String> list = Arrays.asList("1","2","3");
        //打印输出数组集合(forEach底层是一个函数式接口)
        list.forEach(System.out::println);
        
}
  • 测试结果

在这里插入图片描述

结果执行成功,没有出现异常!

2.多线程下测试ArrayList集合

  • 源码分析
package java.util;

/**
 * @author  Josh Bloch
 * @author  Neal Gafter
 * @see     Collection
 * @see     List
 * @see     LinkedList
 * @see     Vector
 * @since   1.2 ArrayList从JDK1.2版本开始使用 
 */
//ArrayList集合
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    /**
     * 在当前list集合的尾部追加指定的元素
     *
     * @param 被追加到该list集合中的一个元素
     * @return 布尔型值true
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    /**
     * 在当前list集合的尾部追加指定的元素
     *
     * @param e 被追加到该list集合中的一个元素
     * @return 布尔型值true
     */
    public void add(int index, E e) {
        rangeCheckForAdd(index);
        checkForComodification();
        parent.add(parentOffset + index, e);
        this.modCount = parent.modCount;
        this.size++;
    }
}
  • 测试代码
package com.kuang.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @ClassName ListTest
 * @Description List安全性测试
 * @Author 狂奔の蜗牛rz
 * @Date 2021/7/29
 */
public class ListTest {

    public static void main(String[] args) {

        //2.使用传统的语法方式
        //获取ArrayList集合
       List<String> list = new ArrayList<>();
        //使用for循环模拟多线程环境
        for (int i = 1; i <= 10; i++) {
            //使用Lambda表达式创建和启动线程
            new Thread(()->{
                /**
                 * 将字符串添加到list集合中
                 * 使用UUID.randomUUID().toString()获取UUID随机字符串
                 * 使用substring(0,5),返回的字符串中下标索引从0到5的子字符串
                 */
                list.add(UUID.randomUUID().toString().substring(0,5));
                //打印输出list集合
                System.out.println(list);
            //String.valueOf(i)表示获取下标为i的字符串
            },String.valueOf(i)).start();
        }
    }
}
  • 测试结果

在这里插入图片描述

结果出现异常报错:java.util.ConcurrentModificationException(并发修改异常)!

测试结论并发情况下,ArrayList线程不安全

2.4.2 解决List线程不安全问题

1.将ArrayList替换为Vector集合

2.使用Collections集合工具类的synchronizedList方法

3.使用CopyOnWriteArrayList(写时复制数组集合)

1.使用Vector集合

  • 源码分析
package java.util;

/**
 * @author  Lee Boynton
 * @author  Jonathan Payne
 * @see Collection
 * @see LinkedList
 * @since JDK1.0 Vector从JDK1.0就开始使用
 */
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{    
	/**
     * 追加指定的元素到该Vector集合尾部
     *
     * @param e 被追加到当前Vector集合中的一个元素
     * @return 布尔型值true
     * @since 1.2
     */
	//使用synchronized同步锁修饰add方法,保证线程安全
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
  • 测试代码
package com.kuang.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @ClassName ListTest
 * @Description List安全性测试
 * @Author 狂奔の蜗牛rz
 * @Date 2021/7/29
 */
public class ListTest {

    public static void main(String[] args) {

        //2.使用传统方式
        //获取ArrayList集合
//        List<String> list = new ArrayList<>();
        //方案1: 使用Vector集合
        List<String> list = new Vector<>();
        //使用for循环模拟多线程环境
        for (int i = 1; i <= 10; i++) {
            //使用Lambda表达式创建和启动线程
            new Thread(()->{
                /**
                 * 将字符串添加到list集合中
                 * 使用UUID.randomUUID().toString()获取UUID随机字符串
                 * 使用substring(0,5),返回的字符串中下标索引从0到5的子字符串
                 */
                list.add(UUID.randomUUID().toString().substring(0,5));
                //打印输出list集合
                System.out.println(list);
            //String.valueOf(i)表示获取下标为i的字符串
            },String.valueOf(i)).start();
        }
    }
}
  • 测试结果

在这里插入图片描述

结果执行成功,没有出现异常!

2.使用Collections工具类的synchronizedList方法

  • 源码分析

Collections工具类

package java.util;

/**
 *
 * @author  Josh Bloch
 * @author  Neal Gafter
 * @see     Collection
 * @see     Set
 * @see     List
 * @see     Map
 * @since   1.2
 */
//Collections工具类
public class Collections {
    // 抑制默认的构造函数, 确保其非实例化
    private Collections() {
    }
    /**
     * 如果定义的集合是序列化的,返回的集合也将会序列化
     * @param  <T> list集合中的Object类
     * @param  list list集合被包装在同步的list集合中
     * @return 指定集合的同步视图
     */
    public static <T> List<T> synchronizedList(List<T> list) {
        /**
         * instanceof定义:
         * instanceof是Java的一个二元操作符,类似于 ==,>,< 等操作符。
         * instanceof是Java的保留关键字。
         * 作用:测试它左边的对象是否是它右边的类的实例,返回boolean的数据类型。
         */
        /**
         * 返回值是使用三元运算符来获取
         * list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list)
         * 判断list对象是否是RandomAccess(随机访问接口)的实例,若是,则创建一个SynchronizedRandomAccessList对象(同步随机访问集合), 否则将创建一个同步集合SynchronizedList, 二者的参数都是list集合
         */
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }
}

RandomAccess接口

package java.util;

/**
 * @since 1.4
 */
//随机访问接口
public interface RandomAccess {
}
  1. RandomAccess接口是一个标志接口,实现了该接口的List集合,可以支持快速随机访问
  2. 如果实现了该接口的List集合,使用for循环的方式获取数据将优于迭代器获取
  • 使用for循环形式
for (int i=0, n=list.size(); i &lt; n; i++)
list.get(i);
  • 使用迭代器形式
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
  • 测试代码
package com.kuang.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @ClassName ListTest
 * @Description List安全性测试
 * @Author 狂奔の蜗牛rz
 * @Date 2021/7/29
 */
public class ListTest {

    public static void main(String[] args) {

        //2.使用传统方式
        //获取ArrayList集合
//        List<String> list = new ArrayList<>();
        //方案2: 使用Collections集合工具类的synchronizedList方法
	    List<String> list = Collections.synchronizedList(new ArrayList<>());
        //方案3: 使用CopyOnWriteArrayList(写时复制数组集合)
        List<String> list = new CopyOnWriteArrayList<>();
        //使用for循环模拟多线程环境
        for (int i = 1; i <= 10; i++) {
            //使用Lambda表达式创建和启动线程
            new Thread(()->{
                /**
                 * 将字符串添加到list集合中
                 * 使用UUID.randomUUID().toString()获取UUID随机字符串
                 * 使用substring(0,5),返回的字符串中下标索引从0到5的子字符串
                 */
                list.add(UUID.randomUUID().toString().substring(0,5));
                //打印输出list集合
                System.out.println(list);
            //String.valueOf(i)表示获取下标为i的字符串
            },String.valueOf(i)).start();
        }
    }
}
  • 测试结果

在这里插入图片描述

结果执行成功,没有出现异常!

3.CopyOnWriteArrayList简单了解

CopyOnWriteArrayList:顾名思义,写入时进行复制

在这里插入图片描述

什么是写入时复制

写入时复制(全称为Copy On Write,简称为COW),是计算机程序设计领域的一种优化策略;

核心思想是:如果有多个调用者同时请求相同资源(如内存或磁盘上的数据存储),它们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的。

此作法主要的优点是如果调用者没有修改该资源,就不会有副本被创建,因此多个调用者只是读取操作时可以共享同一份资源。

简而言之,就是读操作直接在正本上进行,一旦有写操作,就复制一份副本出来,并在副本上做修改

在这里插入图片描述

4.使用CopyOnWriteArrayList集合

  • 源码分析
package java.util.concurrent;

/**
 * Java集合框架
 * @since 1.5
 * @author Doug Lea
 * @param <E> 用于容纳这个collection集合的元素类型
 */
//CopyOnWriteArrayList(写时复制数组集合), 实现了List<E>(List集合),RandomAccess(快速访问)接口和序列化接口
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    //私有静态其最终的连续版本UID
    private static final long serialVersionUID = 8673264195747942595L;
      
    /** 
     * lock锁保护所有的调整器
     * 我们发现它使用transient来修饰lock锁:
     * 简单理解:将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化
     */
    final transient ReentrantLock lock = new ReentrantLock();

   /** 
    * 数组, 只能通过getArray或者setArray方法获取 
    */
    private transient volatile Object[] array;

   /** 
    * 获取数组. 非私有的也可以通过CopyOnWriteArraySet类访问
    */
    final Object[] getArray() {
        return array;
    }

    /**
     * 设置数组
     */
    final void setArray(Object[] a) {
        array = a;
    }

    /**
     * 无参构造方法
     * 创建一个空list集合
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
    
    ...(中间省略部分代码)...
        
    /**
     * 在集合后面追加指定元素
     *
     * @param e 被追加到当前集合的指定元素
     * @return 布尔型值true
     */
    public boolean add(E e) {
        //获取ReentrantLock(重入锁)
        final ReentrantLock lock = this.lock;
        //首先上锁
        //lock作用:使得不会进行覆盖,复制时写入是防止遍历时出现异常
        lock.lock();
        //执行添加操作的业务代码
        try {
            //调用getArray方法获取元素数组elements
            Object[] elements = getArray();
		   //设置数组长度	
            int len = elements.length;
            //复制一份原数组(elements)元素到新数组(newElements)中
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //新数组中的要被追加的指定元素
            newElements[len] = e;
            //设置数组为newElements
            setArray(newElements);
            //返回真值
            return true;
        } finally {
            //最后解锁
            lock.unlock();
        }
    }    
    
    ...(后面省略部分代码)...
        
}

transient关键字使用总结

1、transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。
2、被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
3、一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。也可以认为在将持久化的对象反序列化后,被transient修饰的变量将按照普通类成员变量一样被初始化。

  • 测试代码
package com.kuang.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @ClassName ListTest
 * @Description List安全性测试
 * @Author 狂奔の蜗牛rz
 * @Date 2021/7/29
 */
public class ListTest {

    public static void main(String[] args) {

        //2.使用传统方式
        //获取ArrayList集合
//        List<String> list = new ArrayList<>();
        //方案3: 使用CopyOnWriteArrayList(写时复制数组集合)
        List<String> list = new CopyOnWriteArrayList<>();
        /**
         * 多个线程调用时,list集合读取时是固定的,写入时可能存在覆盖,
         * 为了避免写入时被覆盖,造成数据问题,可以进行读写分离
         */
        //使用for循环模拟多线程环境
        for (int i = 1; i <= 10; i++) {
            //使用Lambda表达式创建和启动线程
            new Thread(()->{
                /**
                 * 将字符串添加到list集合中
                 * 使用UUID.randomUUID().toString()获取UUID随机字符串
                 * 使用substring(0,5),返回的字符串中下标索引从0到5的子字符串
                 */
                list.add(UUID.randomUUID().toString().substring(0,5));
                //打印输出list集合
                System.out.println(list);
            //String.valueOf(i)表示获取下标为i的字符串
            },String.valueOf(i)).start();
        }
    }
}
  • 测试结果

在这里插入图片描述

结果执行成功,没有出现异常!

到这里,今天的有关List集合线程安全的学习就结束了,欢迎大家学习和讨论!

参考视频链接:https://www.bilibili.com/video/BV1B7411L7tE (B站UP主遇见狂神说的JUC并发编程基础)

参考博客链接

https://baijiahao.baidu.com/s?id=1636557218432721275&wfr=spider&for=pc (Java中的关键字transient,这篇文章你再也不愁了)

https://www.cnblogs.com/liuling/archive/2013/05/05/transient.html (Java transient关键字使用小结)

https://blog.csdn.net/nazeniwaresakini/article/details/104473981 (写时复制(COW)技术与其在Linux、Java中的应用)

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-31 16:29:19  更:2021-07-31 16:30:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/2 1:26:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码