JUC的基础介绍

325 阅读5分钟

什么是JUC

JUC(Java.util.concurrent)是 Java 中用于并发编程的工具包,它提供了一系列高效、可靠的并发编程工具,帮助开发者更好地处理多线程并发问题。

JUC中包含的内容

一、线程池(ThreadPoolExecutor)

  1. 概念和作用

    • 线程池是一种管理多线程的机制,它可以重复利用已创建的线程,减少线程创建和销毁的开销。通过预先创建一定数量的线程,当有任务需要执行时,从线程池中获取一个空闲线程来执行任务,任务完成后线程回到池中等待下一个任务,避免了频繁地创建和销毁线程对系统资源的浪费。
    • 提高系统的响应速度和性能,尤其在处理大量短期任务时效果显著。
  2. 核心参数和使用方法

    • corePoolSize(核心线程数):线程池中始终保持运行的线程数量。即使这些线程处于空闲状态,它们也不会被销毁,除非设置了 allowCoreThreadTimeOuttrue

    • maximumPoolSize(最大线程数):线程池允许的最大线程数量。当任务队列已满且核心线程都在忙碌时,会创建新的线程直到达到这个数量。

    • keepAliveTime(线程存活时间):当线程数量超过核心线程数时,多余的空闲线程在多长时间内会被销毁。

    • unit(时间单位):与 keepAliveTime 配合使用,指定时间的单位,如秒、毫秒等。

    • workQueue(任务队列):用于存储等待执行的任务。常见的有 LinkedBlockingQueue(无界队列)、ArrayBlockingQueue(有界队列)等。

    • threadFactory(线程工厂):用于创建新线程,可以设置线程的名称、优先级等属性。

    • RejectedExecutionHandler(拒绝策略):当任务队列已满且线程池无法再创建新线程时,用于处理无法接受的任务。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由提交任务的线程自己执行任务)、DiscardPolicy(默默丢弃任务)、DiscardOldestPolicy(丢弃最老的任务,尝试重新提交当前任务)。

    • 使用方法示例:

    ExecutorService executorService = new ThreadPoolExecutor(
            5, // 核心线程数
            10, // 最大线程数
            60L, TimeUnit.SECONDS, // 线程存活时间和单位
            new LinkedBlockingQueue<>(), // 任务队列
            new ThreadFactoryBuilder().setNameFormat("my-thread-%d").build(), // 线程工厂
            new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略
    
    executorService.execute(() -> {
        // 任务逻辑
    });
    

二、并发容器

  1. ConcurrentHashMap

    • 是一种高效的并发哈希表,允许多个线程同时进行读写操作而不需要外部同步。它通过分段锁(Segment)的方式实现了高效的并发访问,每个分段内部使用传统的哈希表结构,不同分段之间的操作可以并发进行,提高了整体的性能。
    • 相比传统的 HashMap,在多线程环境下更加安全和高效,避免了 HashMap 在并发修改时可能出现的 ConcurrentModificationException
  2. CopyOnWriteArrayListCopyOnWriteArraySet

    • CopyOnWriteArrayList 是一个线程安全的可变数组,它的主要特点是在进行写操作(如添加、删除元素)时,会复制一个新的数组,在新数组上进行修改,然后将原有的引用指向新数组,从而实现了读写分离。这种方式可以避免在迭代过程中被其他线程的修改影响,保证了迭代的线程安全。
    • CopyOnWriteArraySet 内部使用 CopyOnWriteArrayList 实现,是一个线程安全的集合,不允许有重复元素。

三、锁机制

1. ReentrantLock

  • 是一种可重入的互斥锁,与传统的 synchronized 关键字类似,但提供了更多的高级功能。它支持尝试获取锁(tryLock)、可中断地获取锁(lockInterruptibly)以及支持公平锁和非公平锁的配置。
  • 使用示例:
import java.util.concurrent.locks.ReentrantLock;

class MyClass {
    private final ReentrantLock lock = new ReentrantLock();

    public void doSomething() {
        lock.lock();
        try {
            // 临界区代码
        } finally {
            lock.unlock();
        }
    }
}

2. ReadWriteLock

  • 读写锁,允许多个线程同时读取共享资源,但在写操作时需要独占访问。它将锁分为读锁和写锁,提高了并发读的性能。
  • ReentrantReadWriteLock 是 Java 中对读写锁的具体实现。

四、原子类(AtomicInteger、AtomicLong 等)

  1. 概念和作用

    • 原子类提供了对基本数据类型的原子操作,如自增、自减、赋值等。这些操作在多线程环境下是原子性的,不会被其他线程中断,确保了数据的一致性。
    • 避免了使用传统的同步机制(如 synchronized)带来的性能开销,适用于需要频繁进行原子操作的场景。
  2. 使用方法示例

    import java.util.concurrent.atomic.AtomicInteger;
    
    class Counter {
        private AtomicInteger count = new AtomicInteger(0);
    
        public void increment() {
            count.incrementAndGet();
        }
    
        public int getCount() {
            return count.get();
        }
    }
    

五、并发工具类

1. CountDownLatch

  • 一个同步辅助类,允许一个或多个线程等待其他一组线程完成操作后再继续执行。可以用来实现多个线程之间的等待和协调。
  • 使用示例:
import java.util.concurrent.CountDownLatch;

class MyTask implements Runnable {
    private final CountDownLatch latch;

    public MyTask(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        // 任务逻辑
        latch.countDown();
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 5;
        CountDownLatch latch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(new MyTask(latch)).start();
        }
        latch.await();
        System.out.println("All tasks completed.");
    }
}

2. CyclicBarrier

  • 也是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。与 CountDownLatch 不同的是,CyclicBarrier 可以在到达屏障点后重新使用。
  • 使用示例:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

class Worker implements Runnable {
    private final CyclicBarrier barrier;

    public Worker(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        try {
            // 任务逻辑
            barrier.await();
            // 后续任务
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        int threadCount = 5;
        CyclicBarrier barrier = new CyclicBarrier(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(new Worker(barrier)).start();
        }
    }
}

3. Semaphore

  • 信号量,用于控制同时访问某个特定资源的线程数量。可以用来实现资源的有限访问,防止过多的线程同时竞争资源导致性能问题。
  • 使用示例:
import java.util.concurrent.Semaphore;

class Resource {
    private final Semaphore semaphore;

    public Resource(int permits) {
        semaphore = new Semaphore(permits);
    }

    public void useResource() throws InterruptedException {
        semaphore.acquire();
        try {
            // 使用资源
        } finally {
            semaphore.release();
        }
    }
}

JUC 提供的这些工具极大地丰富了 Java 并发编程的手段,使得开发者能够更加高效、可靠地处理多线程并发问题,提高程序的性能和可扩展性。