【Java并发编程】JUC(java.util.concurrent) 包中的常见类的使用以及线程安全集合类

Callable 接口

在Java中,Callable接口是一个函数式接口,主要用于表示可以返回结果的任务。它和Runnable接口类似,但Runnablerun方法没有返回值,而Callablecall方法可以返回一个结果,且可以抛出异常。

主要特点:

  1. 泛型Callable接口是一个泛型接口,需要指定返回值的类型。例如,Callable<Integer>表示一个可以返回Integer类型结果的任务。

  2. call 方法Callable接口中定义了的方法是call(),该方法执行任务并返回一个结果。它的签名如下:

    java 复制代码
    V call() throws Exception;
  3. 异常处理 :与Runnable接口不同,call()方法可以抛出异常,这使得在执行任务时能够捕获和处理可能出现的错误。

  4. 与Executor框架结合使用Callable通常与ExecutorService结合使用,可以通过Future类获取任务的结果。使用submit()方法提交一个Callable任务后,返回一个Future对象,允许你在之后的某个时间点获取结果或检查任务是否已完成。

示例代码:

以下是一个使用Callable接口的简单示例:

java 复制代码
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableExample {
    public static void main(String[] args) {
        // 创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        
        // 创建一个Callable任务
        Callable<Integer> task = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                // 模拟一些计算
                return 123;
            }
        };

        // 提交任务并获取Future对象
        Future<Integer> future = executorService.submit(task);

        try {
            // 获取任务的结果
            Integer result = future.get();
            System.out.println("Task result: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

在这个示例中,我们创建了一个实现了Callable接口的任务,并将其提交到一个线程池中执行。最后,通过Future对象获取计算结果。

总之,Callable接口是Java中实现并发编程的重要组成部分,广泛用于需要返回结果或处理异常的任务。

ReentrantLock 可重入互斥锁

ReentrantLock是Java中的一个重要的同步工具类,提供了比内置的同步机制(如synchronized关键字)更灵活的锁机制。它位于java.util.concurrent.locks包中,主要用于在多线程环境中控制对共享资源的访问。

主要特点:

  1. 可重入性:锁是可重入的,这意味着同一个线程可以多次获得同一个锁而不会发生死锁。每次锁的获取都会增加锁的计数,释放时相应地减少。

  2. 公平性ReentrantLock可以选择是否使用公平策略。在公平模式下,线程会按照请求锁的顺序来获得锁;而在非公平模式下,线程可以插队获取锁。公平性在构造ReentrantLock时指定:

    java 复制代码
    ReentrantLock lock = new ReentrantLock(true); // 公平锁
  3. 灵活的锁定和释放 :与synchronized不同,ReentrantLock允许在代码的任何位置手动锁定和释放,这为控制锁的范围提供了更大的灵活性。

  4. 条件变量ReentrantLock提供了条件变量(Condition对象),允许线程在某些条件下等待并被其他线程唤醒。这使得在复杂的线程协调中更加强大。

  5. 中断支持ReentrantLock的锁获取可以响应中断,可以通过调用lockInterruptibly()方法来实现,使得线程可以在尝试获取锁时响应中断。

示例代码:

以下是一个使用ReentrantLock的简单示例:

java 复制代码
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private static ReentrantLock lock = new ReentrantLock();
    private static int sharedResource = 0;

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Task());
        Thread thread2 = new Thread(new Task());

        thread1.start();
        thread2.start();
    }

    static class Task implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                lock.lock();  // 获取锁
                try {
                    // 访问共享资源
                    sharedResource++;
                    System.out.println(Thread.currentThread().getName() + " : " + sharedResource);
                } finally {
                    lock.unlock();  // 释放锁
                }
            }
        }
    }
}

在这个示例中,我们创建了一个ReentrantLock实例来保护对共享资源sharedResource的访问。两个线程同时尝试增加该资源的值。lock.lock()lock.unlock()负责获取和释放锁,确保线程安全。

总结:

ReentrantLock是Java并发编程中一个强大且灵活的工具,提供了比内置锁更丰富的特性。它允许开发者根据具体需求选择锁的行为,并更好地控制多线程之间的资源竞争和协调。

信号量 Semaphore

Semaphore类是Java中的一个同步工具,用于控制对某个资源的访问。它位于java.util.concurrent包中,能够限制同时访问某个特定资源的线程数量,从而实现更精细的线程控制。

主要特点:

  1. 许可证(Permit)Semaphore维护一组许可证(permits),每个许可证表示可以同时被一个线程占用的资源。线程在获取许可证之前,需要检查是否有可用的许可证。

  2. 公平性Semaphore可以选择是否使用公平策略。在公平模式下,线程会按照请求许可证的顺序来获得许可证;而在非公平模式下,线程可以插队抢占许可证。公平性在Semaphore构造时指定:

    java 复制代码
    Semaphore semaphore = new Semaphore(3, true); // 公平信号量
  3. 可重入Semaphore允许同一个线程多次获得许可证,而在释放时则需要相应地多次释放。

  4. 限制资源访问 :使用Semaphore可以控制对共享资源的访问,例如限制同时访问数据库连接、文件、网络连接等的线程数。

  5. 阻塞和非阻塞Semaphore提供了阻塞和非阻塞的获取许可证的方法。acquire()方法是阻塞式的,线程会在没有可用许可证时等待,而tryAcquire()方法则是非阻塞式的,立即返回,允许线程根据许可证的可用性做进一步处理。

示例代码:

以下是一个使用Semaphore的简单示例:

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private static final int MAX_CONCURRENT_THREADS = 3;
    private static Semaphore semaphore = new Semaphore(MAX_CONCURRENT_THREADS);

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            executorService.submit(new Task(i));
        }

        executorService.shutdown();
    }

    static class Task implements Runnable {
        private final int taskId;

        public Task(int taskId) {
            this.taskId = taskId;
        }

        @Override
        public void run() {
            try {
                // 请求许可证
                semaphore.acquire();
                System.out.println("Task " + taskId + " is executing...");

                // 模拟任务执行
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                // 释放许可证
                semaphore.release();
                System.out.println("Task " + taskId + " has finished.");
            }
        }
    }
}

在这个示例中,我们创建一个Semaphore实例,允许最多3个线程同时执行Task。我们使用semaphore.acquire()请求许可证,如果许可证不可用,线程会等待。模拟任务执行时,每个任务会输出其ID,表示正在执行。任务结束后,使用semaphore.release()释放许可证,以便其他线程能够获取。

总结:

Semaphore是用于控制对共享资源访问的强大工具,尤其适合于限制同时访问资源的线程数量。通过合理利用Semaphore,可以有效地防止资源过载,提高系统性能与稳定性。

CountDownLatch

CountDownLatch是Java中的一个同步辅助类,位于java.util.concurrent包中。它允许一个或多个线程等待直到一组操作完成。其核心功能是通过倒计时来控制线程的执行,常用于多线程协调。

主要特点:

  1. 计数器CountDownLatch维护一个计数器,在线程之间协调工作。当计数器的值减为0时,表示所有线程都已完成它们的任务。

  2. 构造方法CountDownLatch的构造方法需要一个整数参数,表示初始计数值。这个值通常是要等待的事件数量。

  3. await()方法 :调用该方法的线程会被阻塞,直到计数器的值达到0。无论是主线程还是工作线程都可以调用await()

  4. countDown()方法 :当一个线程完成了某个任务时,可以调用这个方法使得计数器减1。每次调用countDown()都会减少计数器的值。

  5. 一次性使用 :一旦计数器减到0,CountDownLatch不能重用。如果需要重用,必须创建一个新的实例。

使用场景:

CountDownLatch非常适合于以下场景:

  • 等待多个线程完成它们的任务,例如在多线程环境中主线程需要等待所有工作线程完成后再继续执行。
  • 在某个初始条件满足之前,不允许某些线程开始执行。

示例代码:

以下是一个使用CountDownLatch的简单示例:

java 复制代码
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    private static final int THREAD_COUNT = 3;

    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);

        // 创建并启动多个线程
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(new Worker(latch, i)).start();
        }

        try {
            // 等待计数器减到0
            latch.await();
            System.out.println("All threads have finished, main thread is continuing...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class Worker implements Runnable {
        private final CountDownLatch latch;
        private final int threadId;

        public Worker(CountDownLatch latch, int threadId) {
            this.latch = latch;
            this.threadId = threadId;
        }

        @Override
        public void run() {
            try {
                // 模拟任务执行
                System.out.println("Thread " + threadId + " is working...");
                Thread.sleep((long) (Math.random() * 1000)); // 随机睡眠时间
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                // 完成任务后调用countDown()
                latch.countDown();
                System.out.println("Thread " + threadId + " has finished.");
            }
        }
    }
}

在这个示例中,我们创建了一个CountDownLatch实例,其初始计数值为线程的数量。每个工作线程在执行完成后调用countDown(),主线程在调用latch.await()时会被阻塞,直到所有工作线程完成任务计数器减到0,主线程之后再继续执行。

总结:

CountDownLatch是一个非常有用的工具,尤其是在需要等待多个线程完成某项任务时。通过合理地使用CountDownLatch,可以简化多线程程序的控制逻辑,从而提高程序的可读性和维护性。

线程安全集合类

Java提供了一系列线程安全的集合类,主要用于在多线程环境中安全地处理数据。这些集合类通常是在java.util.concurrent包中实现的,也有一些是在java.util包中的类具有同步(synchronized)功能。以下是一些常用的线程安全集合类:

  1. ConcurrentHashMap
  • 描述:一个高效的哈希表实现,支持高并发的访问。
  • 特点:支持分段锁定,能在多个线程同时读取和写入时保持高性能。
  1. CopyOnWriteArrayList
  • 描述:线程安全的ArrayList实现,主要用于读多写少的场景。
  • 特点:每次修改(添加、删除)时都会复制整个底层数组,因此在读取时不会出现并发修改的问题。
  1. CopyOnWriteArraySet
  • 描述 :线程安全的Set实现,底层使用CopyOnWriteArrayList
  • 特点 :提供与CopyOnWriteArrayList类似的特性,但去除重复元素。
  1. BlockingQueue接口及其实现
  • 描述BlockingQueue是一个支持阻塞操作的队列接口,有多个实现。
  • 典型实现
    • ArrayBlockingQueue:基于数组实现的有界阻塞队列。
    • LinkedBlockingQueue:基于链表实现的可选界限队列。
    • PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
    • DelayQueue:一个基于时间的阻塞队列,元素会在指定时间后可用。
  1. ConcurrentSkipListMap
  • 描述:一个高效的并发跳表实现,具有给定顺序的Map结构。
  • 特点:可以使用并发地进行插入、删除、查询等操作,且具有排序特性。
  1. ConcurrentSkipListSet
  • 描述 :一个基于ConcurrentSkipListMap的线程安全Set实现。
  • 特点:具备Set的所有特性,并支持高并发。
  1. Collections.synchronizedList()
  • 描述:返回一个线程安全的List包装。
  • 使用方法 :例如,使用Collections.synchronizedList(new ArrayList<>())可以将ArrayList包装成线程安全的List。
  1. Collections.synchronizedMap()
  • 描述:返回一个线程安全的Map包装。
  • 使用方法 :例如,使用Collections.synchronizedMap(new HashMap<>())可以将HashMap包装成线程安全的Map。
  1. Collections.synchronizedSet()
  • 描述:返回一个线程安全的Set包装。
  • 使用方法 :例如,使用Collections.synchronizedSet(new HashSet<>())可以将HashSet包装成线程安全的Set。

总结

这些线程安全的集合类允许开发者在多线程环境中安全地管理数据,避免了并发操作导致的数据不一致和其他问题。在选择使用哪种集合类时,可以根据具体的使用场景和性能要求进行权衡。

相关推荐
芊寻(嵌入式)8 分钟前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
WaaTong9 分钟前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
m0_743048449 分钟前
初识Java EE和Spring Boot
java·java-ee
AskHarries11 分钟前
Java字节码增强库ByteBuddy
java·后端
一颗松鼠17 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
有梦想的咸鱼_18 分钟前
go实现并发安全hashtable 拉链法
开发语言·golang·哈希算法
海阔天空_201324 分钟前
Python pyautogui库:自动化操作的强大工具
运维·开发语言·python·青少年编程·自动化
天下皆白_唯我独黑31 分钟前
php 使用qrcode制作二维码图片
开发语言·php
小灰灰__31 分钟前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
夜雨翦春韭35 分钟前
Java中的动态代理
java·开发语言·aop·动态代理