掘金 后端 ( ) • 2024-06-21 10:00

theme: healer-readable highlight: a11y-dark

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云;欢迎大家常来逛逛

今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

在Java开发中,多线程编程是一个重要的领域。随着计算机硬件的不断发展,多核处理器的广泛应用以及多线程编程的需求,Java提供了丰富的并发编程库和工具,以帮助开发人员更好地实现多线程编程。本文将重点介绍Java多线程中的并发集合。

摘要

并发集合是Java中用于处理多线程环境下数据共享和同步的重要工具。它们提供了线程安全的数据结构和算法,确保多个线程可以同时访问和修改共享数据,而不会造成数据不一致或竞态条件。

本文将介绍Java中的几种常用的并发集合,并进行源代码解析。我们还将探讨它们的应用场景案例,并分析它们的优缺点。最后,我们将给出一些具体的Java代码测试用例,以帮助读者更好地理解并发集合的使用方法。

简介

Java并发集合是在Java 5引入的java.util.concurrent包中提供的。它们被设计为线程安全的数据结构,以解决多线程环境下的数据共享和同步问题。这些并发集合提供了一组强大的功能和性能,包括线程安全、高效的并发数据结构和算法。

Java并发集合主要包括以下几种类型:

  1. 并发队列(ConcurrentQueue):如ConcurrentLinkedQueue和LinkedBlockingQueue。
  2. 并发映射(ConcurrentMap):如ConcurrentHashMap。
  3. 并发列表(ConcurrentList):如CopyOnWriteArrayList和ConcurrentSkipListSet。
  4. 并发集合(ConcurrentSet):如ConcurrentSkipListSet和CopyOnWriteArraySet。

下面我们将逐个进行源代码解析和介绍。

源代码解析

并发集合是Java并发包中的一部分,它们提供了比传统集合更好的并发性能和线程安全性。这些集合通常用于多线程环境中,当多个线程需要对共享数据进行读写操作时。

1. 并发队列(ConcurrentQueue)

并发队列是一种特殊的队列数据结构,它允许多个线程同时进行入队和出队操作,而不会造成数据不一致或竞态条件。我们介绍两个常用的并发队列:

  • ConcurrentLinkedQueue:它是一个基于链表实现的无界非阻塞并发队列。它使用CAS(Compare-and-Swap)操作实现线程安全。
  • LinkedBlockingQueue:它是一个基于链表实现的可选界阻塞并发队列。它使用ReentrantLock和Condition实现线程安全。

2. 并发映射(ConcurrentMap)

并发映射是一种提供键值对存储、查找和删除的数据结构,支持高并发访问的线程安全集合。我们介绍一个常用的并发映射:

  • ConcurrentHashMap:它是一个基于散列实现的线程安全的并发映射。它使用分段锁(Segment)来实现高效的并发访问。

3. 并发列表(ConcurrentList)

并发列表是一种支持高并发访问的线程安全列表数据结构。我们介绍两个常用的并发列表:

  • CopyOnWriteArrayList:它是一个基于数组实现的线程安全的并发列表。它使用写时复制(Copy-On-Write)策略,在修改列表时创建一个新的副本来确保线程安全。
  • ConcurrentSkipListSet:它是一个基于跳表实现的线程安全的并发有序集合。它使用CAS操作来实现线程安全。

4. 并发集合(ConcurrentSet)

并发集合是一种支持高并发访问的线程安全集合。我们介绍一个常用的并发集合:

  • ConcurrentSkipListSet:它是一个基于跳表实现的线程安全的并发有序集合。它使用CAS操作来实现线程安全。

5.并发栈(ConcurrentStack)

并发栈是一种支持高并发访问的线程安全栈数据结构。我们介绍一个常用的并发栈:

  • ArrayDeque:它是一个基于数组实现的双端队列,可以用作栈使用。虽然它不是并发包的一部分,但它的性能在单线程环境下非常优秀,并且可以通过外部同步来在多线程环境中使用。

应用场景案例

并发集合在多线程编程中有广泛的应用场景。以下是一些常见的应用场景案例:

  1. 高并发访问数据库:使用并发映射(ConcurrentMap)来缓存数据库查询结果,提高访问性能。
  2. 多线程排序:使用并发集合(ConcurrentSet)来存储排序结果,实现高效的多线程排序。
  3. 并行计算任务:使用并发队列(ConcurrentQueue)来存储待处理的计算任务,多个线程从队列中取任务进行并行计算。

优缺点分析

并发集合在多线程编程中有许多优点和一些缺点。下面是它们的分析:

优点如下:

  • 线程安全:并发集合提供了线程安全的数据结构和算法,确保多线程环境下的数据共享和同步。
  • 高效性能:并发集合使用特定的内部数据结构和算法,以实现高效的并发访问。

并发集合的优点在于它们提供了简化并发编程的抽象,减少了因锁操作和同步问题导致的复杂性。然而,它们也有一些缺点:

  • 性能开销:由于并发集合需要保证线程安全,因此在某些情况下,它们的性能可能不如传统集合。
  • 功能限制:并发集合的某些实现可能没有传统集合那么灵活,例如,它们可能不支持所有的迭代器操作。

类代码方法介绍

1. ConcurrentLinkedQueue

ConcurrentLinkedQueue是一个基于链表实现的无界非阻塞并发队列。它提供以下几个重要的方法:

  • add(E e):将指定的元素添加到队列的尾部。
  • poll():检索并删除队列的头部元素。
  • size():返回队列中的元素个数。

以下是一个示例代码:

import java.util.concurrent.ConcurrentLinkedQueue;

public class ConcurrentLinkedQueueExample {
    public static void main(String[] args) {
        ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();

        queue.add("Java");
        queue.add("Python");
        queue.add("C++");

        System.out.println("Queue size: " + queue.size());

        String element = queue.poll();
        System.out.println("Removed element: " + element);

        System.out.println("Queue size after removal: " + queue.size());
    }
}

2. ConcurrentHashMap

ConcurrentHashMap是一个基于散列实现的线程安全的并发映射。它提供了以下几个重要的方法:

  • put(K key, V value):将指定的键值对添加到映射中。
  • get(Object key):检索指定键的值。
  • remove(Object key):从映射中删除指定的键值对。

以下是一个示例代码:

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

        map.put("Java", 1);
        map.put("Python", 2);
        map.put("C++", 3);

        System.out.println("Value for key 'Java': " + map.get("Java"));

        map.remove("Python");

        System.out.println("Size of map after removal: " + map.size());
    }
}

. CopyOnWriteArrayList

CopyOnWriteArrayList是一个基于数组实现的线程安全的并发列表。它提供以下几个重要的方法:

  • add(E e):将指定的元素添加到列表的尾部。
  • remove(int index):删除指定索引处的元素。
  • get(int index):获取指定索引处的元素。

以下是一个示例代码:

import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        System.out.println("List size: " + list.size());

        String element = list.get(1);
        System.out.println("Element at index 1: " + element);

        list.remove("Banana");

        System.out.println("List after removal: " + list);
    }
}

测试用例

为了测试并发集合的性能和功能,我们可以编写一些基准测试和功能测试用例。以下是一些测试用例的示例:

  1. 并发队列性能测试用例:创建多个生产者和消费者线程,使用ConcurrentLinkedQueue存储和检索对象,测量操作的吞吐量和延迟。
  2. 并发映射功能测试用例:使用ConcurrentHashMap存储和检索键值对,验证线程安全性和数据一致性。
  3. 并发列表并发测试用例:创建多个线程,同时对CopyOnWriteArrayList进行添加和删除操作,验证线程安全性和数据完整性。
  4. 并发集合并发测试用例:使用ConcurrentSkipListSet存储大量元素,从多个线程并发地添加和删除元素,验证线程安全性和排序功能。

通过这些测试用例,我们可以确保并发集合在多线程环境下的正确性和性能。开发者应该根据具体的应用场景和需求,选择合适的并发集合,并进行充分的测试。

为了编写这些测试用例,我们需要使用Java的并发工具和测试框架。以下是每个测试用例的简要描述和代码示例,参考如下:

1. 并发队列性能测试用例

测试代码演示

目标是测量ConcurrentLinkedQueue在高并发环境下的性能。我们将创建多个生产者线程来添加元素,以及多个消费者线程来移除元素。

package com.example.javase.ms.threadDemo.day7;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @Author ms
 * @Date 2024-04-12 23:38
 */
public class ConcurrentQueuePerformanceTest {
    private final ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();

    public void testPerformance() throws InterruptedException {
        int numberOfProducers = 10;
        int numberOfConsumers = 10;
        int iterations = 100000;

        ExecutorService producers = Executors.newFixedThreadPool(numberOfProducers);
        ExecutorService consumers = Executors.newFixedThreadPool(numberOfConsumers);

        long start = System.nanoTime();

        for (int i = 0; i < iterations; i++) {
            int finalI = i;
            producers.submit(() -> queue.add(finalI));
            consumers.submit(() -> queue.poll());
        }

        producers.shutdown();
        consumers.shutdown();

        producers.awaitTermination(1, TimeUnit.HOURS);
        consumers.awaitTermination(1, TimeUnit.HOURS);

        long end = System.nanoTime();
        System.out.println("Total time: " + (end - start) + " nanoseconds");
    }

    public static void main(String[] args) throws InterruptedException {
        new ConcurrentQueuePerformanceTest().testPerformance();
    }
}

测试结果展示

根据如上测试用例,这里我们本地执行一下,结果展示如下:

在这里插入图片描述

测试代码分析

根据如上代码作出解析,以便于同学们更好的理解,分析如下:

该代码是一个并发队列性能测试的示例。主要通过ConcurrentLinkedQueue类实现一个并发队列,使用多线程并发地向队列中添加和移除元素,并统计执行时间。

首先,在testPerformance方法中定义了三个变量:

  • numberOfProducers:生产者线程的数量
  • numberOfConsumers:消费者线程的数量
  • iterations:迭代次数,表示生产者线程和消费者线程执行的总次数

然后,通过Executors提供的newFixedThreadPool方法创建生产者和消费者线程池。

接下来,使用for循环进行iterations次的操作。在每次循环中,使用submit方法向生产者线程池提交任务,任务是向队列中添加元素。使用submit方法向消费者线程池提交任务,任务是从队列中移除元素。然后,调用shutdown方法关闭线程池,阻止新任务的提交,并等待所有任务完成。

最后,使用awaitTermination方法等待线程池中的任务执行完成,并记录执行时间。

在main方法中,创建ConcurrentQueuePerformanceTest对象,并调用testPerformance方法进行性能测试。

总的来说,该代码通过多线程并发地操作ConcurrentLinkedQueue实现一个并发队列,在大量数据操作时可以提高效率。

2. 并发映射功能测试用例

测试代码演示

目标是验证ConcurrentHashMap的线程安全性和数据一致性。我们将从多个线程添加和检索键值对。

package com.example.javase.ms.threadDemo.day7;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author ms
 * @Date 2024-04-12 23:41
 */
public class ConcurrentMapFunctionalTest {
    private final ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();

    public void testFunctional() {
        AtomicInteger successfulOperations = new AtomicInteger(0);

        // 多个线程添加和检索数据
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                int key = ThreadLocalRandom.current().nextInt(1000);
                String value = "Value for " + key;
                map.put(key, value);
                if (map.get(key).equals(value)) {
                    successfulOperations.incrementAndGet();
                }
            }).start();
        }

        // 等待所有线程完成
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }

        System.out.println("Successful operations: " + successfulOperations);
    }

    public static void main(String[] args) {
        new ConcurrentMapFunctionalTest().testFunctional();
    }
}

测试结果展示

根据如上测试用例,这里我们本地执行一下,结果展示如下:

在这里插入图片描述

测试代码分析

根据如上代码作出解析,以便于同学们更好的理解,分析如下:

这段代码演示了使用ConcurrentHashMap实现并发操作的功能。

首先,创建了一个ConcurrentHashMap对象,并定义了一个AtomicInteger对象用于记录成功的操作次数。

然后,通过多个线程并发地进行添加和检索数据操作。每个线程生成一个随机的key,并将其与对应的value放入ConcurrentHashMap中。如果成功添加并且检索到了正确的value,则成功操作次数加一。

最后,通过判断活动线程数是否大于1来等待所有线程完成,并打印出成功操作的次数。

注意:ConcurrentHashMap是线程安全的哈希表,可以用于多线程环境下进行并发操作。它使用了锁分段技术,将数据分成多个段,每个段使用独立的锁来保证并发安全。

3. 并发列表并发测试用例

测试代码演示

目标是验证CopyOnWriteArrayList的线程安全性和数据完整性。我们将创建多个线程来添加和删除元素。

package com.example.javase.ms.threadDemo.day7;
import java.util.concurrent.*;


/**
 * @Author ms
 * @Date 2024-04-12 23:43
 */
public class ConcurrentListConcurrencyTest {
    private final CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();

    public void testConcurrency() throws InterruptedException {
        int numberOfThreads = 10;
        int operations = 10000;

        ExecutorService service = Executors.newFixedThreadPool(numberOfThreads);

        for (int i = 0; i < operations; i++) {
            service.submit(() -> {
                list.add(ThreadLocalRandom.current().nextInt());
                list.remove(ThreadLocalRandom.current().nextInt(list.size()));
            });
        }

        service.shutdown();
        service.awaitTermination(1, TimeUnit.HOURS);

        System.out.println("List size: " + list.size());
    }

    public static void main(String[] args) throws InterruptedException {
        new ConcurrentListConcurrencyTest().testConcurrency();
    }
}

测试结果展示

根据如上测试用例,这里我们本地执行一下,结果展示如下:

在这里插入图片描述

测试代码分析

根据如上代码作出解析,以便于同学们更好的理解,分析如下:这段代码是一个并发测试的示例,使用了CopyOnWriteArrayList来实现线程安全的并发操作。

首先,代码创建了一个CopyOnWriteArrayList对象作为共享数据结构。然后,代码创建了一个线程池ExecutorService,并设置线程数量为10。接下来,代码使用ExecutorService提交了10,000个任务到线程池。每个任务都是一个lambda表达式,会向CopyOnWriteArrayList中添加一个随机数,然后从列表中移除一个随机位置的元素。最后,代码调用ExecutorServiceshutdown方法,关闭线程池,并调用awaitTermination方法等待线程池中的所有任务执行完成。最终,代码输出CopyOnWriteArrayList的大小。

总体来说,这段代码是一个多线程并发测试的示例,使用了CopyOnWriteArrayList来保证共享数据的线程安全性。

4. 并发集合并发测试用例

测试代码演示

目标是验证ConcurrentSkipListSet的线程安全性和排序功能。我们将从多个线程并发地添加和删除元素。

package com.example.javase.ms.threadDemo.day7;
import java.util.concurrent.*;

/**
 * @Author ms
 * @Date 2024-04-12 23:44
 */
public class ConcurrentSetConcurrencyTest {
    private final ConcurrentSkipListSet<Integer> set = new ConcurrentSkipListSet<>();

    public void testConcurrency() throws InterruptedException {
        int numberOfThreads = 10;
        int operations = 10000;

        ExecutorService service = Executors.newFixedThreadPool(numberOfThreads);

        for (int i = 0; i < operations; i++) {
            service.submit(() -> {
                int randomInt = ThreadLocalRandom.current().nextInt(10000);
                set.add(randomInt);
                set.remove(randomInt);
            });
        }

        service.shutdown();
        service.awaitTermination(1, TimeUnit.HOURS);

        System.out.println("Set size: " + set.size());
    }

    public static void main(String[] args) throws InterruptedException {
        new ConcurrentSetConcurrencyTest().testConcurrency();
    }
}

这些测试用例可以帮助我们理解并发集合在不同场景下的行为和性能。在实际应用中,我们可能需要根据具体的业务需求和系统环境调整测试参数,并进行更深入的分析。

测试结果展示

根据如上测试用例,这里我们本地执行一下,结果展示如下:

在这里插入图片描述

测试代码分析

根据如上代码作出解析,以便于同学们更好的理解,分析如下:这段代码演示了使用ConcurrentSkipListSet实现线程安全的并发集合。   在testConcurrency方法中,首先定义了并发操作的线程数numberOfThreads和每个线程的操作次数operations。   然后,创建了一个线程池service,并固定线程数量为numberOfThreads。 接下来,使用for循环提交了operations个任务给线程池,每个任务是一个匿名函数,其中随机生成一个整数randomInt,并将其添加到ConcurrentSkipListSet中,然后再将其移除。   最后,关闭线程池,并等待其所有任务执行完成。   最后,打印出ConcurrentSkipListSet的大小。   在main方法中,创建了ConcurrentSetConcurrencyTest对象,并调用testConcurrency方法来进行测试。

总结

本文详细介绍了Java中并发集合的概念、类型以及它们在多线程编程中的应用。并发集合是Java并发包的一部分,它们提供了线程安全的数据结构,使得在多线程环境下共享数据的访问和修改变得更加安全和高效。我们探讨了并发队列、并发映射、并发列表和并发集合等不同类型的并发集合,并通过源代码示例展示了它们的使用方法。

通过应用场景案例,我们了解了并发集合在实际开发中的多种用途,如数据库查询缓存、多线程排序和并行计算任务。这些应用场景展示了并发集合如何帮助开发者解决多线程编程中的并发问题。

我们还分析了并发集合的优缺点,指出了它们在提供线程安全的同时可能会带来的性能开销和功能限制。这些考量对于选择合适的数据结构和设计高效的并发程序至关重要。

最后,我们提供了一系列的测试用例,用于评估并发集合的性能和功能。这些测试用例不仅验证了并发集合的线程安全性和数据一致性,还帮助我们理解了它们在不同并发级别下的行为表现。

总的来说,Java的并发集合是处理多线程数据共享问题的强大工具。它们简化了并发编程的复杂性,提高了程序的性能和可靠性。开发者应当充分理解并发集合的特点和适用场景,以便在构建多线程应用程序时做出明智的选择。通过不断学习和实践,并发集合将成为解决并发问题的有效工具。

... ...

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

... ...

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。

⭐️若有疑问,就请评论留言告诉我叭。