java线程之List集合并发安全问题及解决方案

简介: java线程之List集合并发安全问题及解决方案

一、问题代码

任务:执行10轮次,使用多线程,给list集合添加元素,查看每次执行的结果。

public static void main(String[] args) throws InterruptedException {
        int loop = 0;
        int threadNum = 10;
        while (loop < 10) {
            //创建一个计数器
            CountDownLatch countDownLatch = new CountDownLatch(threadNum);
            //创建集合
            List<String> list = new ArrayList<>();
            for (int i = 0; i < threadNum; i++) {
                new Thread(() -> {
                    //集合添加内容
                    list.add(UUID.randomUUID().toString().substring(0, 8));
                    //线程操作完成一个减一
                    countDownLatch.countDown();
                }, String.valueOf(i)).start();
            }
            //等待所有线程执行完成
            countDownLatch.await();
            System.out.println("---集合长度:" + list.size() + "---loop:" + loop + "---");
            System.out.println(list);
            loop++;
        }
    }

loop:6时候,只添加了9条。

---集合长度:10---loop:0---
[955419b4, cd30b81c, 8bee554b, ff91be9b, 841408f7, 8a132002, 4384a15c, df8c561e, 4897166a, 80714bf0]
---集合长度:10---loop:1---
[d29700a9, f512d3d8, c9bc5faf, 749e0e91, 25dc21d5, 570b7473, 49161838, a2c28a34, 8e138a7c, 77d887cb]
---集合长度:10---loop:2---
[1e9ca70b, a07560ad, cb4c76ba, b2d1893c, 45e0f262, 68e25ac2, 09d2fe4f, 281765b4, 9877edc4, 284b852c]
---集合长度:10---loop:3---
[b17ba08f, 68f289dc, 47182e97, 7bdb5210, 7917e4fe, 04a0b440, b733d289, e137c32b, 4e5c920c, 10a9acdb]
---集合长度:10---loop:4---
[6bd8be8e, e07b569f, e8b37d23, c724a1f4, 2fa0b4fa, a0359dd5, 441dec81, 47f55116, 7642ab44, 4619b854]
---集合长度:10---loop:5---
[345dbd60, cac7fbb0, 4e54acb8, 6f6a4a82, 941579a3, 9f7242db, 4c1aa6ee, 81eb7196, faa589b9, e35f2f3b]
---集合长度:9---loop:6---
[5fcaa369, a0ced8b5, a05d0c74, 8415e2d9, 72ab401b, 809fd7a0, 51442894, 52bcf2a3, d477d1c3]
---集合长度:10---loop:7---
[85c82ca3, 074e8881, fb798fb4, 99a9fabe, ec4b6b5a, 964f0b63, 87aa845e, bab5e35b, f40bc936, dc284c89]
---集合长度:10---loop:8---
[98e6c1f2, 37a4370b, 6b7360af, a51b0051, 6322a817, 055b62d2, 78201d93, bee8e60d, 537ac6c7, 876b8428]
---集合长度:10---loop:9---
[e706c947, cc068833, 01c34aea, 870a294a, effd91ea, 8cf00cab, 8ae2aa4c, b0441ffa, 1ccf31bc, 91b143f8]

 loop:4的时候,只添加了9条;

---集合长度:10---loop:0---
[627e8354, ce0eaff6, 7097d1d7, 9e5b07d5, 8b4f2e0c, e7ddf456, 59364b06, 3b2f5370, b9f2ea33, 8174b138]
---集合长度:10---loop:1---
[06701acb, 8af28cc8, 8ca4f803, 707722cb, cb277df8, 72a63097, e6b33f7e, 71bae0f6, d51ebeae, 779ef7fc]
---集合长度:10---loop:2---
[95f05da1, 5c068fd5, 1493ad15, 91561550, 17af73fd, c99c6616, 3e1ee3c2, 09c2ede0, ef9367fa, 65808dcd]
---集合长度:10---loop:3---
[ffc77c44, 36ef14d8, 7d0a36a4, 12bb2f90, 065b3dc5, 891a45f6, 8a6a4a1f, 4264090b, d585f760, 2c282e1b]
---集合长度:9---loop:4---
[17dda97c, 7aa517b3, 96984409, ad74eb02, d8e5c893, dff7a694, f7a72f69, 49457871, 791a0695]
---集合长度:10---loop:5---
[5346072d, d3050d5d, e87a942f, 0e8e8061, 3189c7f7, 4ee541cd, de0a0100, 370f0465, e03d71a7, 3f2572fe]
---集合长度:10---loop:6---
[f1ae5275, f62ec85f, b6c564c3, 04e999c0, d54d0863, 5e5c4e69, c12cc9e9, 74f6dc15, 87632932, 79c98321]
---集合长度:10---loop:7---
[c55d1196, b9e18d20, 24f23a0e, 8c54c083, 61cdf212, e60cc532, a5fc1d27, 79dc4bf2, ea6be1fe, 9df13359]
---集合长度:10---loop:8---
[6a40ef06, 22a69e75, 0d448a08, 6af1c353, c091190e, ef670d5b, ad790da0, 52b9f59d, 2c287738, f96176ce]
---集合长度:10---loop:9---
[13a0bdb1, fe98ce3f, 45aac986, b7b940bf, f41f8c8f, 62bf82ea, 8a123b4b, 85bb41a1, 776cfdd1, 149a8212]

发生原因:ArrayList的add方法不是同步方法,有可能n个线程同时进入,拿到的size是同一个值,那么n个线程同时只添加了一个元素。

二、解决方案

1、使用List的线程安全子类Vector

 public static void main(String[] args) throws InterruptedException {
        int loop = 0;
        int threadNum = 10;
        while (loop < 10) {
            //创建一个计数器
            CountDownLatch countDownLatch = new CountDownLatch(threadNum);
            //创建集合
            List<String> list = new Vector<>();
            for (int i = 0; i < threadNum; i++) {
                new Thread(() -> {
                    //集合添加内容
                    list.add(UUID.randomUUID().toString().substring(0, 8));
                    //线程操作完成一个减一
                    countDownLatch.countDown();
                }, String.valueOf(i)).start();
            }
            //等待所有线程执行完成
            countDownLatch.await();
            System.out.println("---集合长度:" + list.size() + "---loop:" + loop + "---");
            System.out.println(list);
            loop++;
        }
 
    }
---集合长度:10---loop:0---
[391b7947, cc171749, 17326cbe, 696340e8, 6932ede4, 57769aeb, 445a93c6, 314bee8a, ee14af4c, 3b886645]
---集合长度:10---loop:1---
[4806f7e2, eccfa620, e19ee85e, 3a0bd870, c9f0f4bc, 6ed89d16, 3ea92668, 7237ddeb, eedea3e1, d13753f0]
---集合长度:10---loop:2---
[558e3ee2, 592d8534, ffbd8e1e, 2bd467e8, 3fc60170, 382a2460, 32ae670b, 5dd26920, 1acaf2a3, 7261d7e3]
---集合长度:10---loop:3---
[a520df0f, abe540ed, 223feeab, ccb285a5, 1283fce8, e2bd426a, d23c4b82, dd425f23, 03c683fd, 9650489a]
---集合长度:10---loop:4---
[fe667d22, f726c6ee, 88ad82e0, 0104cf48, 4c6dd4da, 9c50cd5e, 28a22a43, 20038d20, fd6a12c2, b4a2b869]
---集合长度:10---loop:5---
[653dfdef, 31818490, 56291645, c9bf42c6, f49cf28b, b8612671, 9ef9956c, af4c048f, 612a2aa8, 00488f64]
---集合长度:10---loop:6---
[54892a2c, 63eff31d, 50d67c43, c4984687, 89f86418, 5cd079f3, 93b8cdc7, 7f6e857e, 5eeec1a8, ef024516]
---集合长度:10---loop:7---
[a1c94d7b, f304ec2a, d9c32237, 2dc75056, 5df3701d, 34e1b122, ac2b3b41, a9d44e50, bbf95afa, 914b8d33]
---集合长度:10---loop:8---
[92350948, 3f691746, d7a64cdd, d31bef58, d23fb841, b594b477, 4c9494f8, 71bc27b8, 8293f055, fb0331b1]
---集合长度:10---loop:9---
[54b8d072, 9abbdbeb, daeca0a6, 0566c89f, cc9c4b56, 25fd1787, 7b3cb590, 292cff8a, 3295be2b, 7fea15ba]

2、使用 Collections.synchronizedList()

使用同步方法操作

 public static void main(String[] args) throws InterruptedException {
        int loop = 0;
        int threadNum = 10;
        while (loop < 10) {
            //创建一个计数器
            CountDownLatch countDownLatch = new CountDownLatch(threadNum);
            //创建集合
            List<String> list =Collections.synchronizedList(new ArrayList<>());
            for (int i = 0; i < threadNum; i++) {
                new Thread(() -> {
                    //集合添加内容
                    list.add(UUID.randomUUID().toString().substring(0, 8));
                    //线程操作完成一个减一
                    countDownLatch.countDown();
                }, String.valueOf(i)).start();
            }
            //等待所有线程执行完成
            countDownLatch.await();
            System.out.println("---集合长度:" + list.size() + "---loop:" + loop + "---");
            System.out.println(list);
            loop++;
        }
    }
---集合长度:10---loop:0---
[2a59eb58, c04f64de, 5d2f6924, c3772c5f, 440b529c, 997ac389, f95717d7, da233e53, 05894c56, 26da6f22]
---集合长度:10---loop:1---
[19dc29cc, 7eb82b00, d8715162, f7b82a61, f8106ccf, 81b4a4d1, cc07a96f, 6b405055, 2074db7e, 87b2a8b8]
---集合长度:10---loop:2---
[eea3f5f0, 2f555a94, 7ce5bcc2, 99547f75, 7b2b3c41, 64718afe, 3d1e3350, 86420d89, e1b7c452, 353a8843]
---集合长度:10---loop:3---
[40621d07, 200f11f9, 815e6613, d84e2943, 84760222, fd8385d6, 5c928382, 6dc9c863, a1c49665, f0e0472a]
---集合长度:10---loop:4---
[cb800a5c, 5f81fc14, a2d64da5, f5a67780, ee845c8d, b631e9b1, 5961daf6, 7ba11e93, 24f0513c, 786eb7bf]
---集合长度:10---loop:5---
[0db1a50e, 43b374ca, 768c363f, 4baf5e9f, 65ee9ebc, c0d2c24b, 532a29fd, ff5682d6, d3250085, 590d3eb2]
---集合长度:10---loop:6---
[9223a887, ce7f3d69, 551c3199, 5c092523, 2af16ab2, e401f80b, b2db6e9d, dbb48564, fa6076a9, 9bfcb824]
---集合长度:10---loop:7---
[134a9b7c, 3e9ba896, d01847c1, d8263425, d63002ff, 5d19ed22, 60704521, 42065456, 8c9501d8, 36224a1f]
---集合长度:10---loop:8---
[b14957b4, 33a51813, 0c5a992c, 9a5a8fe6, 1df1187c, a6cd0e73, 3dd89d6a, 3d0be681, 071e0d5d, 20d8a7d5]
---集合长度:10---loop:9---
[57f4bf4a, 8ef7282b, 5fbee16c, 4ceb41d3, a6afd3d7, 08c3a07c, 4d17d47e, 48616ce7, dbe45214, 76b332d5]

3、使用CopyOnWriteArrayList(推荐)

Java CopyOnWriteArrayList详解 - 简书 (jianshu.com)

public static void main(String[] args) throws InterruptedException {
        int loop = 0;
        int threadNum = 10;
        while (loop < 10) {
            //创建一个计数器
            CountDownLatch countDownLatch = new CountDownLatch(threadNum);
            //创建集合
            List<String> list = new CopyOnWriteArrayList<>();
            for (int i = 0; i < threadNum; i++) {
                new Thread(() -> {
                    //集合添加内容
                    list.add(UUID.randomUUID().toString().substring(0, 8));
                    //线程操作完成一个减一
                    countDownLatch.countDown();
                }, String.valueOf(i)).start();
            }
            //等待所有线程执行完成
            countDownLatch.await();
            System.out.println("---集合长度:" + list.size() + "---loop:" + loop + "---");
            System.out.println(list);
            loop++;
        }
 
    }
---集合长度:10---loop:0---
[89e6a3e4, b24efa55, 26dfde63, 344fdd63, f17d9ff4, 00dcc8ec, 3550b167, de14127a, cad5aa6e, 81c025db]
---集合长度:10---loop:1---
[b785dc56, edf42136, 46722ecf, 93d37d13, d0de78df, d4e8d3ba, 481c40f1, 57bec525, a68d51c4, 006c8406]
---集合长度:10---loop:2---
[651cdab5, a1d6a5d2, f90052ec, 1594f2e6, c1cc4d69, 46bf3d47, a0050804, bb7e7068, ec850dad, 9da3c35d]
---集合长度:10---loop:3---
[2bcfc2fc, 3b16ae9f, 10666ded, 096d7904, f65702be, 5af12eb4, 53a4edb8, 465a7907, 2497082a, 66c9db76]
---集合长度:10---loop:4---
[646ce345, f4a8ca8e, cfded69f, 185d394f, f42cadeb, 3e926b34, 729f71f6, 5916a8d7, 806f331b, 0566775a]
---集合长度:10---loop:5---
[df47784e, 8f7dbe4e, ba5e3f00, 72520d47, 86af1ddf, edba78eb, 2670bdd3, 3663b234, ea8930d4, c00b2f7e]
---集合长度:10---loop:6---
[e4399e5b, 412405c7, b547bebe, 70da2c66, d85fb42a, 32fe82b9, ed1ffea9, 5a1e0f8b, e77683c9, b1ed414c]
---集合长度:10---loop:7---
[75b2ff0a, e22f6eef, 249fe313, a1771cb3, cf8d33c2, a2aeb0ad, 4562bbf2, c09abd16, 92489b03, e68e2ced]
---集合长度:10---loop:8---
[687a916d, 6acea36d, 27bfad3c, 94ba873c, 39626ce0, bb2836fc, 8f7f88e7, 42ea11cc, c82bd4c7, 6de280ff]
---集合长度:10---loop:9---
[43d88928, adc1476c, 465f9e1c, ade2bf6c, c90c2e16, 2deb25ec, b59c62c7, 13abb545, 098bac48, 1c6213b5]

相关文章
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
17天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
22 9
|
7天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
4天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
7天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
21 3
|
8天前
|
SQL 安全 Java
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
21 4
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
7天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
17 1
|
7天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。