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 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> List线程安全问题 -> 正文阅读

[数据结构与算法]List线程安全问题

1. 发现问题
List<Integer> list = new ArrayList<>();

new Thread(() -> {
    for (int i = 0; i < 10000; i++) {
        list.add(1);
    }
},"A").start();

new Thread(() -> {
    for (int i = 0; i < 10000; i++) {
        list.add(1);
    }
},"B").start();

new Thread(() -> {
    for (int i = 0; i < 10000; i++) {
        list.add(1);
    }
},"C").start();



try {
    TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
    e.printStackTrace();
}

System.out.println("list.size() = " + list.size());

开启三个线程,每个线程向ArrayList中插入1w条数据。

之后等待三秒,等到每个线程都执行完毕时再查看ArrayList中的元素个数。

运行结果:

image-20220420213925263

  • 问题一:ArrayIndexOutOfBoundsException
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 检查容量,必要时扩容
    elementData[size++] = e;  //扩容后 添加元素
    return true;
}

在多线程环境中时,多个线程同时进入add()方法,同时检查容量,例如当前容量为5,而已占用4

三个线程同时检查,都发现还有容量,则都同时添加元素。

由此导致ArrayIndexOutOfBoundsException

  • 问题二:实际插入元素个数小于预期插入元素个数

从运行结果可以看出,最终list.size()只有18935 <= 30000。我们希望能够插入30000个元素,可是实际上只插入了<= 30000个元素。

还是从源码入手:

public boolean add(E e) {
    ensureCapacityInternal(size + 1); 
    elementData[size++] = e;   //添加元素后   size自增
    return true;
}

试想一下,如果多个线程同时向size位插入元素,且都没有来得及size++,那么导致的结果就是

多个元素被插入在了同一个位置,相互抵消。

2. 解决问题(一)Vector

早期,IT前人为了解决List在并发时出现的问题,引入了Vector实现类。

Vetor的实现方式与ArrayList大同小异,它的底层也是一个数组,在添加时自增长。

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);   //检查容量 必要时增长
    elementData[elementCount++] = e;
    return true;
}

ArrayList不同的是,它的add()方法带有synchronized关键字。

这表明当线程调用该方法时,会自动占用锁,直到这个线程的任务完成,期间不会放弃该锁。

而且当线程占有该锁时,别的线程无法进入Vetor类调用带有synchronized关键字的方法。

这很好的避免了多线程竞争的现象,从而保证了并发安全。

试一试?

Vector<Integer> list = new Vector<>();

new Thread(() -> {
    for (int i = 0; i < 10000; i++) {
        list.add(1);
    }
},"A").start();

new Thread(() -> {
    for (int i = 0; i < 10000; i++) {
        list.add(1);
    }
},"B").start();

new Thread(() -> {
    for (int i = 0; i < 10000; i++) {
        list.add(1);
    }
},"C").start();



try {
    TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
    e.printStackTrace();
}

System.out.println("list.size() = " + list.size());  // 30000
3.解决问题(二)Collections.synchronizedList(List<T> list)

废话不多说,直接上源码。

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


// Collections.synchronizedList 源码
public static <T> List<T> synchronizedList(List<T> list) { 
    return (list instanceof RandomAccess ?   //暂且不说
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));   //创建一个 SynchronizedList 实例
}

SynchronizedList(List<E> list) {
    super(list);   // 调用父类构造器
    this.list = list;
}

SynchronizedCollection(Collection<E> c) {
    this.c = Objects.requireNonNull(c);   //要求传入的 集合类实例 非空  并将这个集合赋值给 c 变量
    mutex = this;   // 将自己赋值给 互斥锁变量
}

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();   //为空则抛出异常
    return obj;
}

Collections.synchronizedList()方法会返回一个SynchronizedList类的实例,其中包含了调用该方法时传入的集合,在构造期间,将SynchronizedCollection作为互斥锁。

此时,当我们再调用add()方法:

public boolean add(E e) {
    synchronized (mutex) {  //锁住 SynchronizedCollection 集合类
        return c.add(e);
    }
}

这是,当调用add()方法,SynchronizedCollection会锁住自己,从而保证线程安全。

当有线程正在使用mutex互斥锁时,其他变量无法占有该锁。

4. 手写实现一个简易版SynchronizedCollection
package top.ptcc9;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

/**
 * @Author HE LONG CAN
 * @Description TODO
 * @Date 2022-04-21 22:55:24
 */
public class MySynchronizedArrayList<E> implements List<E> {

    private Collection<E> list = null;

    /**
     * 充当锁 final避免object被重新赋值
     */
    private final Object object = new Object();

    public MySynchronizedArrayList(Collection<E> t) {
        this.list = t;
    }

    @Override
    public int size() {
        return list.size();
    }

    @Override
    public boolean add(E e) {
        synchronized (object) {  //枷锁
            list.add(e);
        }
        return true;
    }
    
    //..... 此处省略 List 接口的一万个方法
}



List<Integer> list = new MySynchronizedArrayList<>(new ArrayList<>());

new Thread(() -> {
    for (int i = 0; i < 10000; i++) {
        list.add(1);
    }
},"A").start();

new Thread(() -> {
    for (int i = 0; i < 10000; i++) {
        list.add(1);
    }
},"B").start();

new Thread(() -> {
    for (int i = 0; i < 10000; i++) {
        list.add(1);
    }
},"C").start();


try {
    TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
    e.printStackTrace();
}

System.out.println("list.size() = " + list.size());    // 30000

未完待更新呢

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-04-22 19:01:27  更:2022-04-22 19:04:26 
 
开发: 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年11日历 -2024/11/26 7:39:36-

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