Java多线程编程中的线程安全集合:保护数据的铁壁

简介: Java多线程编程中的线程安全集合:保护数据的铁壁

前言


Java多线程编程是现代软件开发的重要组成部分,然而,多线程环境下数据的安全性一直是一个棘手的问题。本文将探讨如何通过线程安全集合来解决这一挑战。我们将深入研究Java中的Concurrent包,介绍诸如ConcurrentHashMap、CopyOnWriteArrayList等强大的数据结构,它们为多线程应用提供了高效的数据管理方式。无论您是初学者还是有经验的开发人员,都将从本文中学到如何确保数据在多线程环境下的安全性,为您的Java多线程应用程序打造坚实的基础。




线程安全集合


CopyOnWriteArrayList重点


  • 线程安全的ArrayList,加强版读写分离。
  • 写有锁,读无锁,读写之间不阻塞,优于读写锁。
  • 写入时,先copy一个容器副本、再添加新元素,最后替换引用。
  • 使用方式与ArrayList无异。

示例:

public class TestCopyOnWriteArrayList {
  public static void main(String[] args) {
    //1创建集合
    CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();
    //2使用多线程操作
    ExecutorService es=Executors.newFixedThreadPool(5);
    //3提交任务
    for(int i=0;i<5;i++) {
      es.submit(new Runnable() {
        @Override
        public void run() {
          for(int j=0;j<10;j++) {
            list.add(Thread.currentThread().getName()+"...."+new Random().nextInt(1000));
          }
        }
      });
    }
    //4关闭线程池
    es.shutdown();
    while(!es.isTerminated()) {}
    //5打印结果
    System.out.println("元素个数:"+list.size());
    for (String string : list) {
      System.out.println(string);
    }
  }
}


CopyOnWriteArrayList如何做到线程安全的


CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。

当有新元素加入的时候,如下图,创建新数组,并往新数组中加入一个新元素,这个时候,array这个引用仍然是指向原数组的。


当元素在新数组添加成功后,将array这个引用指向新数组。

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


CopyOnWriteArrayListadd操作的源代码如下:

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

由于所有的写操作都是在新数组进行的,这个时候如果有线程并发的写,则通过锁来控制,如果有线程并发的读,则分几种情况:


1、如果写操作未完成,那么直接读取原数组的数据;


2、如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据;


3、如果写操作完成,并且引用已经指向了新的数组,那么直接从新数组中读取数据。

可见,CopyOnWriteArrayList读操作是可以不用加锁的。




CopyOnWriteArraySet


示例:

public class TestCopyOnWriteArraySet {
  public static void main(String[] args) {
    //1创建集合
    CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet<>();
    //2添加元素
    set.add("pingguo");
    set.add("huawei");
    set.add("xiaomi");
    set.add("lianxiang");
    set.add("pingguo");
    //3打印
    System.out.println("元素个数:"+set.size());
    System.out.println(set.toString());
  }
}


ConcurrentHashMap重点


  • 初始容量默认为16段(Segment),使用分段锁设计。
  • 不对整个Map加锁,而是为每个Segment加锁。
  • 当多个对象存入同一个Segment时,才需要互斥。
  • 最理想状态为16个对象分别存入16个Segment,并行数量16。
  • 使用方式与HashMap无异。

注意⚠️: ConcurrentHashMap jdk1.8之前才是分段锁,1.8之后没有使用分段锁了,而是乐观锁加Synchronized,这里注意一下

示例:

public class TestConcurrentHashMap {
  public static void main(String[] args) {
    //1创建集合
    ConcurrentHashMap<String, String> hashMap=new ConcurrentHashMap<String, String>();
    //2使用多线程添加数据
    for(int i=0;i<5;i++) {
      new Thread(new Runnable() {
        @Override
        public void run() {
          for(int k=0;k<10;k++) {
            hashMap.put(Thread.currentThread().getName()+"--"+k, k+"");
            System.out.println(hashMap);
          }
        }
      }).start();
    }
  }
}




最后


本期结束咱们下次再见👋~

🌊 关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗

相关文章
|
17天前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
160 0
|
29天前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
116 6
|
1月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
103 1
|
1月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
108 1
|
2月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
Java 开发者 Windows
Java 数据类型划分(字符型)|学习笔记
快速学习 Java 数据类型划分(字符型)
195 0
|
Java 开发者
Java 数据类型划分(整型类型)|学习笔记
快速学习 Java 数据类型划分(整型类型)
152 0
|
Java 开发者
Java 数据类型划分(初见 String 类)|学习笔记
快速学习 Java 数据类型划分(初见 String 类)
142 0
|
Java 开发者
Java 数据类型划分(布尔型)|学习笔记
快速学习 Java 数据类型划分(布尔型)
154 0
|
Java C语言 C++
Java 的数据类型划分(数据类型划分)| 学习笔记
快速学习 Java 的数据类型划分(数据类型划分)
184 0
Java 的数据类型划分(数据类型划分)| 学习笔记