前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Java多线程编程中的线程安全集合:保护数据的铁壁

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

原创
作者头像
杨不易呀
修改2023-09-27 22:13:31
修改2023-09-27 22:13:31
1970
举报
文章被收录于专栏:杨不易呀杨不易呀

前言

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

线程安全集合

CopyOnWriteArrayList重点

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

示例:

代码语言:java
复制
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操作的源代码如下:

代码语言:java
复制
 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

示例:

代码语言:java
复制
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,这里注意一下

示例:

代码语言:java
复制
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();
		}
	}

}
输入图片说明
输入图片说明

最后

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

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

我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表

输入图片说明
输入图片说明

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 线程安全集合
    • CopyOnWriteArrayList重点
    • CopyOnWriteArraySet
    • ConcurrentHashMap重点
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档