JUC工具类
JUC工具类:
CountDownLatch
是 Java 中的一个同步辅助类,用于实现线程间的等待/通知机制。它通过一个计数器来控制线程的执行顺序,允许一个或多个线程等待其他线程执行完毕后再继续执行。
CountDownLatch
的主要特点包括:
CountDownLatch
使用一个计数器来进行控制,该计数器可以初始化一个整数值,表示需要等待的线程数量。CountDownLatch
提供了 await()
方法用于等待计数器归零,即等待其他线程执行完毕。线程调用 await()
方法后,如果计数器为零,则立即返回;如果计数器不为零,则线程会被阻塞,直到计数器归零。CountDownLatch
提供了 countDown()
方法用于减少计数器的值,表示某个线程已经执行完毕。每次调用 countDown()
方法,计数器的值会减少1。CountDownLatch
主要用于以下场景:
CountDownLatch
进行线程的等待和通知。CountDownLatch
来控制线程的执行顺序,确保某些线程在其他线程完成特定操作后再执行。CountDownLatch
来控制并发线程的启动和停止,以便对系统的性能进行评估和测试。下面是一个简单的 CountDownLatch
使用案例:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample1 {
public static void main(String[] args) throws InterruptedException {
int numThreads = 3; // 总共需要等待的线程数量
CountDownLatch latch = new CountDownLatch(numThreads);
// 创建并启动多个线程
for (int i = 0; i < numThreads; i++) {
Thread thread = new Thread(new WorkerThread(latch));
thread.start();
}
System.out.println("主线程在等待所有工作线程完成...");
latch.await(); // 主线程等待所有工作线程执行完毕
System.out.println("所有工作线程已完成,主线程继续执行...");
}
static class WorkerThread implements Runnable {
private CountDownLatch latch;
public WorkerThread(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 开始执行");
// 模拟工作线程执行耗时操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 执行完毕");
latch.countDown(); // 每个工作线程执行完毕后减少计数器的值
}
}
}
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int numThreads = 3; // 总共有3个线程需要同步
CountDownLatch latch = new CountDownLatch(numThreads); // 设置计数器初始值为3
// 启动多个线程
for (int i = 1; i <= numThreads; i++) {
Thread thread = new Thread(new WorkerThread("Thread " + i, latch));
thread.start();
}
System.out.println("主线程等待所有线程执行完毕...");
latch.await(); // 主线程等待所有线程执行完毕
System.out.println("所有线程已执行完毕,主线程继续执行...");
}
static class WorkerThread implements Runnable {
private String name;
private CountDownLatch latch;
public WorkerThread(String name, CountDownLatch latch) {
this.name = name;
this.latch = latch;
}
@Override
public void run() {
System.out.println(name + " 开始执行");
// 模拟线程执行耗时操作
try {
Thread.sleep((long) (Math.random() * 5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " 执行完毕");
latch.countDown(); // 每个线程执行完毕后减少计数器的值
}
}
}
在上面的示例中,有3个工作线程(Thread 1、Thread 2、Thread 3)需要执行,主线程通过 CountDownLatch
等待所有工作线程执行完毕。每个工作线程执行完毕后都会调用 countDown()
方法来减少计数器的值,当计数器的值减为0时,主线程被唤醒,继续执行。这样就实现了多个线程之间的同步。
CountDownLatch
可以用于性能测试中,例如模拟多个线程并发执行某个任务,然后统计所有线程完成任务所花费的时间。下面是一个简单的示例,展示了如何使用 CountDownLatch
进行性能测试。
import java.util.concurrent.CountDownLatch;
public class PerformanceTest {
public static void main(String[] args) throws InterruptedException {
int numThreads = 10; // 总共有10个线程并发执行任务
CountDownLatch startLatch = new CountDownLatch(1); // 启动门
CountDownLatch endLatch = new CountDownLatch(numThreads); // 结束门
// 启动多个线程并发执行任务
for (int i = 1; i <= numThreads; i++) {
Thread thread = new Thread(new WorkerThread("Thread " + i, startLatch, endLatch));
thread.start();
}
// 主线程启动所有工作线程
System.out.println("主线程启动所有工作线程...");
long startTime = System.currentTimeMillis();
startLatch.countDown(); // 启动门减少计数器的值,所有工作线程开始执行
// 主线程等待所有工作线程执行完毕
endLatch.await(); // 结束门等待所有工作线程执行完毕
long endTime = System.currentTimeMillis();
System.out.println("所有工作线程执行完毕,总共花费时间: " + (endTime - startTime) + " ms");
}
static class WorkerThread implements Runnable {
private String name;
private CountDownLatch startLatch;
private CountDownLatch endLatch;
public WorkerThread(String name, CountDownLatch startLatch, CountDownLatch endLatch) {
this.name = name;
this.startLatch = startLatch;
this.endLatch = endLatch;
}
@Override
public void run() {
try {
System.out.println(name + " 等待启动...");
startLatch.await(); // 等待启动门打开,所有工作线程同时开始执行
System.out.println(name + " 开始执行");
// 模拟线程执行耗时操作
Thread.sleep((long) (Math.random() * 5000));
System.out.println(name + " 执行完毕");
endLatch.countDown(); // 每个工作线程执行完毕后减少结束门的计数器
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在上面的示例中,有10个工作线程并发执行任务,主线程通过 CountDownLatch
控制所有工作线程的启动和结束。通过统计所有工作线程完成任务所花费的时间,可以进行性能测试和性能优化。
CyclicBarrier
是 Java 中的一个同步工具类,用于实现多个线程之间的同步等待。它的特点是可以在多个线程之间设置一个栅栏(Barrier),当所有线程都到达栅栏时,栅栏会自动打开,所有线程可以继续执行下一步操作。CyclicBarrier
可以用于线程之间的协同工作,特别适用于多个线程需要等待其他线程完成某个任务后再一起继续执行的场景。
CyclicBarrier
的主要特点包括:
下面是 CyclicBarrier
的一些常用方法:
CyclicBarrier(int parties)
:创建一个 CyclicBarrier
实例,设置等待的线程数量。CyclicBarrier(int parties, Runnable barrierAction)
:创建一个 CyclicBarrier
实例,设置等待的线程数量和栅栏打开时执行的回调函数。int await()
:等待所有线程到达栅栏,如果线程数量未满足条件,则当前线程会被阻塞。int await(long timeout, TimeUnit unit)
:等待所有线程到达栅栏,如果线程数量未满足条件,可以设置超时时间。void reset()
:重置栅栏状态,可以继续使用。
CyclicBarrier
可以在以下场景中使用:
CyclicBarrier
。例如,一个大型任务需要被拆分成多个子任务,并且这些子任务需要并行执行,但在进行下一步之前需要等待所有子任务完成,可以使用 CyclicBarrier
来实现线程之间的协同工作。CyclicBarrier
。例如,一个数据处理任务被拆分成多个子任务,每个子任务在独立的线程中执行,然后将计算结果合并后再进行后续处理,可以使用 CyclicBarrier
来实现结果的合并操作。CyclicBarrier
。例如,一个多阶段的流水线任务,每个阶段有不同的线程执行,需要等待上一阶段的线程完成后再进行下一阶段的操作,可以使用 CyclicBarrier
来实现不同阶段之间的同步。CyclicBarrier
。例如,一个并发性能测试,需要同时启动多个线程模拟用户请求,然后等待所有线程完成后再进行性能统计,可以使用 CyclicBarrier
来控制并发测试的同步。CyclicBarrier
来实现并行化的数据处理,提高处理速度和效率。总的来说,CyclicBarrier
适用于多线程之间需要协同工作、同步等待的场景,可以帮助多个线程在满足特定条件时进行同步,从而实现并行计算、任务协同工作等并发编程的需求。
下面是一个简单的使用 CyclicBarrier
的案例,展示了多个线程在到达屏障点后进行同步等待的过程:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
private static final int THREADS = 3;
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(THREADS, () -> {
System.out.println("所有线程已经到达屏障点,开始执行后续操作。");
});
for (int i = 0; i < THREADS; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始执行任务。");
try {
Thread.sleep(2000); // 模拟任务执行耗时
System.out.println(Thread.currentThread().getName() + " 到达屏障点。");
cyclicBarrier.await(); // 等待其他线程到达屏障点
System.out.println(Thread.currentThread().getName() + " 继续执行后续操作。");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
在这个例子中,我们创建了一个 CyclicBarrier
实例,设置屏障点为 3,表示需要等待 3 个线程都到达屏障点后才能继续执行后续操作。然后创建了 3 个线程,每个线程都会执行一段模拟耗时的任务,然后调用 await()
方法等待其他线程到达屏障点。当 3 个线程都到达屏障点后,会触发 CyclicBarrier
中设置的回调方法执行后续操作。
这个案例中演示了 CyclicBarrier
的基本使用方式,通过设置屏障点和调用 await()
方法来实现线程之间的同步等待,以便在所有线程都到达屏障点后再一起继续执行后续操作。
Semaphore
是 Java 中的一种同步工具,用于控制同时访问某资源的线程数量。它基于计数器的方式,可以设置允许同时访问资源的线程数量,当达到限制时,其他线程需要等待。Semaphore
提供了两种操作:acquire()
用于申请资源,release()
用于释放资源。
Semaphore
类的主要构造方法如下:
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
其中,permits
参数表示允许同时访问资源的线程数量,fair
参数表示是否使用公平性策略,如果设置为 true
,则线程会按照申请的顺序获得许可,遵循先进先出(FIFO)的规则;如果设置为 false
,则线程获得许可的顺序不受保证。
Semaphore
类的常用方法如下:
void acquire()
: 申请一个许可,如果没有许可可用,则线程会阻塞,直到获得许可。void acquire(int permits)
: 申请指定数量的许可,如果没有足够的许可可用,则线程会阻塞,直到获得足够的许可。void release()
: 释放一个许可。void release(int permits)
: 释放指定数量的许可。int availablePermits()
: 获取当前可用的许可数量。int getQueueLength()
: 获取等待许可的线程数量。下面是一个简单的使用 Semaphore
的案例,展示了如何使用 Semaphore
控制同时访问某资源的线程数量:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final int THREADS = 5;
private static final int MAX_CONCURRENT = 2;
private static Semaphore semaphore = new Semaphore(MAX_CONCURRENT);
public static void main(String[] args) {
for (int i = 0; i < THREADS; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 正在执行任务。");
semaphore.acquire(); // 申请许可
System.out.println(Thread.currentThread().getName() + " 获得许可,开始访问资源。");
// 访问资源的代码
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
System.out.println(Thread.currentThread().getName() + " 释放许可,结束任务。");
}
}).start();
}
}
}
Semaphore
是一种用于控制并发访问资源数量的同步工具,可以用于多种场景。以下是一些常见的 Semaphore
的使用场景:
Semaphore
来限制同时访问某个共享资源的线程数量,从而控制并发访问的并发度。例如,可以用于限制同时访问数据库连接池、线程池、网络连接等资源的线程数量,以避免资源过度竞争导致性能下降。Semaphore
可以用于控制对有限资源的分配。例如,可以使用 Semaphore
来控制对某个有限数量的许可证的分配,用于控制某个服务的并发调用数量、限制某个资源的消耗速率等。Semaphore
可以用于实现互斥锁的功能,通过设置许可数量为1,确保只有一个线程可以获得许可执行临界区代码,从而实现互斥的效果。Semaphore
可以用于同步多个线程的执行,例如在多个线程之间达成某种协议或条件。可以通过设置不同的许可数量来控制线程的执行顺序、数量、等待时间等。Semaphore
可以用于控制流量的大小,例如限制请求的并发数、控制消息队列的大小、限制文件的并发写操作等。需要注意的是,Semaphore
并不是适用于所有情况的同步工具,使用时需要仔细考虑业务场景和并发需求,确保正确使用。同时,Semaphore
的使用应遵循线程安全的编程原则,避免出现竞态条件和死锁等问题。
下面是一个使用 Semaphore
来限制并发访问数量的案例,假设有一个共享资源,例如数据库连接池,需要限制同时访问的线程数量为固定值:
import java.util.concurrent.Semaphore;
public class DatabaseConnectionPool {
private final int MAX_CONNECTIONS = 10; // 最大连接数
private final Semaphore semaphore = new Semaphore(MAX_CONNECTIONS); // 使用Semaphore限制并发访问数量
private final Connection[] connections; // 假设连接池中的连接是Connection类型的对象数组
public DatabaseConnectionPool() {
connections = new Connection[MAX_CONNECTIONS];
// 初始化连接池
for (int i = 0; i < MAX_CONNECTIONS; i++) {
connections[i] = new Connection(); // 假设Connection是一个自定义的数据库连接类
}
}
public Connection getConnection() throws InterruptedException {
semaphore.acquire(); // 获取许可证,如果没有可用的许可证,则阻塞等待
return getAvailableConnection();
}
public void releaseConnection(Connection connection) {
releaseConnection(connection.getId()); // 释放连接
}
public void releaseConnection(int connectionId) {
// 释放连接,将许可证归还给Semaphore
semaphore.release();
}
private Connection getAvailableConnection() {
// 获取一个可用的连接
// 省略实现细节
return null;
}
private static class Connection {
// 自定义的数据库连接类
// 省略实现细节
private int id; // 假设有一个连接ID用于标识连接
public int getId() {
return id;
}
}
}
在上面的案例中,Semaphore
被用于限制同时访问数据库连接池的线程数量,最大限制为 MAX_CONNECTIONS
。当获取连接时,如果没有可用的连接,线程将被阻塞等待直到有可用的连接并获取到许可证。当释放连接时,许可证将归还给 Semaphore
,从而允许其他线程获取连接。这样可以有效地控制并发访问数据库连接池的线程数量,防止过度竞争导致性能下降。
下面是一个使用 Semaphore
来控制资源分配的案例,假设有一个固定数量的资源(如打印机),多个线程需要申请资源并使用,但同时只能有限定数量的线程能够获取到资源:
import java.util.concurrent.Semaphore;
public class PrinterPool {
private final int MAX_PRINTERS = 3; // 最大打印机数量
private final Semaphore semaphore = new Semaphore(MAX_PRINTERS); // 使用Semaphore限制资源分配
private final Printer[] printers; // 假设打印机资源是Printer类型的对象数组
public PrinterPool() {
printers = new Printer[MAX_PRINTERS];
// 初始化打印机资源
for (int i = 0; i < MAX_PRINTERS; i++) {
printers[i] = new Printer(i); // 假设Printer是一个自定义的打印机类
}
}
public Printer getPrinter() throws InterruptedException {
semaphore.acquire(); // 获取许可证,如果没有可用的许可证,则阻塞等待
return getAvailablePrinter();
}
public void releasePrinter(Printer printer) {
releasePrinter(printer.getId()); // 释放打印机资源
}
public void releasePrinter(int printerId) {
// 释放打印机资源,将许可证归还给Semaphore
semaphore.release();
}
private Printer getAvailablePrinter() {
// 获取一个可用的打印机
// 省略实现细节
return null;
}
private static class Printer {
// 自定义的打印机类
// 省略实现细节
private int id; // 假设有一个打印机ID用于标识打印机
public Printer(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
}
在上面的案例中,Semaphore
被用于控制资源分配,最大限制为 MAX_PRINTERS
,即同时最多允许 MAX_PRINTERS
个线程获取到资源。当申请资源时,如果没有可用的资源,线程将被阻塞等待直到有可用的资源并获取到许可证。当释放资源时,许可证将归还给 Semaphore
,从而允许其他线程获取资源。这样可以有效地控制资源的分配,防止资源过度竞争导致资源耗尽或性能下降。
Semaphore
可以用于同步多个线程的执行,以下是一个示例:
import java.util.concurrent.Semaphore;
public class MultiThreadExecutor {
private final Semaphore semaphore = new Semaphore(0); // 初始化Semaphore,设置许可证数量为0
private final int THREAD_COUNT = 5; // 线程数量
private final Runnable task = () -> {
try {
System.out.println("Thread " + Thread.currentThread().getId() + " is running.");
Thread.sleep(1000); // 模拟线程执行任务
System.out.println("Thread " + Thread.currentThread().getId() + " finished.");
semaphore.release(); // 任务完成后释放一个许可证
} catch (InterruptedException e) {
e.printStackTrace();
}
};
public void execute() throws InterruptedException {
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(task).start(); // 启动多个线程执行任务
}
semaphore.acquire(THREAD_COUNT); // 等待所有线程任务完成,一共需要获取 THREAD_COUNT 个许可证
System.out.println("All threads finished.");
}
public static void main(String[] args) {
MultiThreadExecutor executor = new MultiThreadExecutor();
try {
executor.execute(); // 启动线程池执行任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在上面的例子中,Semaphore
被用于同步多个线程的执行。线程池中的每个线程执行任务时,会获取一个许可证,表示任务正在执行。当线程完成任务后,会释放一个许可证。在 execute()
方法中,我们使用 semaphore.acquire(THREAD_COUNT)
来等待所有线程任务完成,需要获取 THREAD_COUNT
个许可证,表示所有线程都已经完成任务。一旦所有线程都完成了任务,semaphore.acquire(THREAD_COUNT)
将返回,然后主线程继续执行后续操作。这样可以确保多个线程的任务都完成后再继续执行主线程的后续逻辑。
Semaphore
可以用于控制流量,限制系统中某一资源的并发访问数量。以下是一个示例:
import java.util.concurrent.Semaphore;
public class TrafficController {
private final Semaphore semaphore = new Semaphore(3); // 设置许可证数量为3,表示最多允许3辆车同时通过
public void enterIntersection(String carName) throws InterruptedException {
System.out.println(carName + " is waiting to enter the intersection.");
semaphore.acquire(); // 获取一个许可证,表示车辆进入了路口
System.out.println(carName + " enters the intersection.");
Thread.sleep(1000); // 模拟车辆通过路口的时间
semaphore.release(); // 释放一个许可证,表示车辆离开了路口
System.out.println(carName + " leaves the intersection.");
}
public static void main(String[] args) {
TrafficController trafficController = new TrafficController();
// 启动多个线程,模拟多辆车同时访问路口
for (int i = 1; i <= 5; i++) {
final String carName = "Car " + i;
new Thread(() -> {
try {
trafficController.enterIntersection(carName);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
在上面的例子中,Semaphore
用于控制同时通过路口的车辆数量,设置许可证数量为3,表示最多允许3辆车同时通过。每个车辆线程在进入路口时先尝试获取一个许可证,如果成功获取到许可证,则表示可以进入路口。车辆线程通过路口后,释放许可证,表示离开了路口,从而允许其他车辆线程继续获取许可证进入路口。这样就实现了对流量的控制,限制了同时通过路口的车辆数量。
Phaser
是 Java 并发包中的一个同步器,用于协调多个线程的执行。它提供了一种更灵活、更高级的同步机制,用于控制线程的阶段性执行。
Phaser
提供了类似于 CyclicBarrier
的功能,但它的使用更加灵活,可以适应更复杂的同步场景。Phaser
可以用于控制一组线程在到达某个共同的同步点之前等待,然后一起继续执行;也可以用于控制一组线程在某个共同的同步点之后继续执行。Phaser
还可以动态地注册和注销线程,灵活地调整同步参与者的数量。
Phaser
的核心概念是 “phase”(阶段)和 “party”(参与者)。一个 Phaser
可以有多个阶段,每个阶段都有多个参与者。线程在到达阶段同步点时可以等待其他线程到达,然后一起继续执行;在下一个阶段开始时,线程可以继续执行或者等待其他线程执行完毕。
以下是一个简单的 Phaser
使用案例:
import java.util.concurrent.Phaser;
public class PhaserExample {
public static void main(String[] args) {
Phaser phaser = new Phaser(3); // 创建一个 Phaser,设置参与者数量为3
for (int i = 0; i < 3; i++) {
new Thread(new Worker(phaser)).start(); // 创建3个线程,并启动它们
}
}
static class Worker implements Runnable {
private final Phaser phaser;
public Worker(Phaser phaser) {
this.phaser = phaser;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " starts working.");
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " starts phase " + (i + 1));
phaser.arriveAndAwaitAdvance(); // 到达阶段同步点并等待其他线程到达
System.out.println(Thread.currentThread().getName() + " continues working in phase " + (i + 1));
}
System.out.println(Thread.currentThread().getName() + " finishes working.");
}
}
}
Phaser
是 Java 并发包中的一个同步器,用于协调多个线程的执行。它提供了一种更灵活、更高级的同步机制,用于控制线程的阶段性执行。
Phaser
提供了类似于 CyclicBarrier
的功能,但它的使用更加灵活,可以适应更复杂的同步场景。Phaser
可以用于控制一组线程在到达某个共同的同步点之前等待,然后一起继续执行;也可以用于控制一组线程在某个共同的同步点之后继续执行。Phaser
还可以动态地注册和注销线程,灵活地调整同步参与者的数量。
Phaser
的核心概念是 “phase”(阶段)和 “party”(参与者)。一个 Phaser
可以有多个阶段,每个阶段都有多个参与者。线程在到达阶段同步点时可以等待其他线程到达,然后一起继续执行;在下一个阶段开始时,线程可以继续执行或者等待其他线程执行完毕。
以下是一个简单的 Phaser
使用案例:
java Copy codeimport java.util.concurrent.Phaser; public class PhaserExample { public static void main(String[] args) { Phaser phaser = new Phaser(3); // 创建一个 Phaser,设置参与者数量为3 for (int i = 0; i < 3; i++) { new Thread(new Worker(phaser)).start(); // 创建3个线程,并启动它们 } } static class Worker implements Runnable { private final Phaser phaser; public Worker(Phaser phaser) { this.phaser = phaser; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " starts working."); for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + " starts phase " + (i + 1)); phaser.arriveAndAwaitAdvance(); // 到达阶段同步点并等待其他线程到达 System.out.println(Thread.currentThread().getName() + " continues working in phase " + (i + 1)); } System.out.println(Thread.currentThread().getName() + " finishes working."); } } }
在上面的例子中,创建了一个 Phaser
对象,设置参与者数量为3,表示有3个线程参与同步。每个线程在运行时通过 phaser.arriveAndAwaitAdvance()
方法到达阶段同步点,并等待其他线程到达。当所有线程都到达阶段同步点后,它们一起继续执行,然后进入下一个阶段,直到完成所有阶段的执行。
Phaser
可以在以下场景中使用:
Phaser
可以用于协调一组线程在完成某个阶段的任务后,一起进入下一个阶段并继续执行。这在一些需要分阶段进行任务的并行计算中很有用,例如大规模数据处理、图像处理、模拟等场景。Phaser
允许线程在任何时候动态地注册和注销,从而可以灵活地调整参与同步的线程数量。这在一些需要根据实际情况动态控制线程数量的场景中非常有用,例如线程池的动态调整、动态任务分配等。Phaser
提供了更灵活和高级的同步机制,可以在复杂的场景中实现多线程的协作。例如,在一些多阶段、多任务、多条件的场景中,Phaser
可以用于更加灵活和高效地管理线程的同步和协作。Phaser
具有公平性控制的特性,可以通过 Phaser
的构造函数参数来设置公平性。这在一些需要公平性控制的场景中很有用,例如任务调度、资源分配等。Phaser
提供了可以扩展的同步器接口,可以通过继承和扩展 Phaser
来实现自定义的同步逻辑,从而满足特定的同步需求。总的来说,Phaser
在复杂的多线程协作场景中非常有用,提供了灵活、高级和可扩展的同步机制,用于协调多个线程的执行,并可以根据实际需求进行动态的线程管理。
Exchanger
是 Java 中的一个同步工具类,用于在两个线程之间进行数据交换。Exchanger
提供了一个同步点,当两个线程都到达这个同步点时,它们可以交换数据。
Exchanger
的主要特点包括:
Exchanger
允许两个线程之间进行数据的交换,其中一个线程将数据放入 Exchanger
,而另一个线程从 Exchanger
中取出数据,从而实现线程间的数据传递。Exchanger
提供了一个同步点,当两个线程都到达这个同步点时,它们可以交换数据。如果一个线程先到达同步点,它将等待另一个线程到达,直到两个线程都到达同步点,数据才会被交换。Exchanger
是线程安全的,可以被多个线程同时使用。Exchanger
允许控制数据传递的方式,可以选择是一对一的传递还是多对一的传递。Exchanger
的使用场景包括但不限于以下情况:
Exchanger
可以用于两个线程之间交换数据,例如线程 A 生成数据,线程 B 处理数据,通过 Exchanger
可以实现数据的传递和交换。Exchanger
可以用于控制数据的流量,例如限制某个线程生成数据的速度,只有当另一个线程处理完数据后才能继续生成数据。Exchanger
可以用于线程间的协作,例如一个线程在某个条件满足时将数据传递给另一个线程,从而实现线程间的协作和同步。
以下是一个简单的 Exchanger
使用案例,展示了两个线程之间的数据交换:
import java.util.concurrent.Exchanger;
public class ExchangerExample {
public static void main(String[] args) {
Exchanger exchanger = new Exchanger<>();
Thread thread1 = new Thread(() -> {
try {
String data1 = "Data from Thread 1";
System.out.println("Thread 1 sending data: " + data1);
String data2 = exchanger.exchange(data1);
System.out.println("Thread 1 received data: " + data2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
String data1 = "Data from Thread 2";
System.out.println("Thread 2 sending data: " + data1);
String data2 = exchanger.exchange(data1);
System.out.println("Thread 2 received data: " + data2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在这个例子中,我们创建了一个 Exchanger
对象,并通过两个线程 thread1
和 thread2
进行数据交换。thread1
将字符串 “Data from Thread 1” 放入 Exchanger
,而 thread2
将字符串 “Data from Thread 2” 放入 Exchanger
。当两个线程都到达 Exchanger
的同步点时,它们会交换数据,thread1
取得了 thread2
放入的数据 “Data from Thread 2”,而 thread2
取得了 thread1
放入的数据 “Data from Thread 1”。这样,两个线程之间成功地进行了数据交换。注意,在 exchange()
方法中,如果一个线程先到达同步点,它将等待另一个线程到达,直到两个线程都到达同步点,数据才会被交换。
Exchanger
是 Java 并发包中的一个工具类,用于在两个线程之间进行数据交换。它可以在多线程环境下实现线程之间的数据传递和同步,适用于以下一些场景:
Exchanger
可以用于两个线程之间进行数据交换,例如一个线程生成数据,另一个线程处理数据,通过 Exchanger
可以实现数据的传递和同步。Exchanger
进行数据校对,确保数据的一致性和准确性。Exchanger
可以用于实现多线程之间的数据缓冲区,例如一个线程从外部读取数据放入缓冲区,另一个线程从缓冲区取出数据进行处理。Exchanger
可以用于控制多个线程之间的数据处理流程,例如一个线程负责数据的生成,另一个线程负责数据的处理,通过 Exchanger
进行数据的交换和同步,从而实现数据处理流程的控制和协调。需要注意的是,Exchanger
在使用时需要谨慎,特别是在高并发环境下,避免出现死锁或其他线程同步问题。使用 Exchanger
时应注意合理设计线程的执行顺序和数据交换方式,确保线程之间能够正确地交换数据并保持同步。
ThreadLocal
是 Java 中的一个线程局部变量类,它允许每个线程都有自己的变量副本,互不干扰。ThreadLocal
提供了一种简单的机制,可以在多线程环境下实现线程间的数据隔离,每个线程都可以访问和修改自己的线程局部变量,而不会影响其他线程的变量。
ThreadLocal
主要有以下几个特点:
ThreadLocal
变量副本,互不干扰,线程间的数据访问不会相互影响。ThreadLocal
提供了线程安全的方式来存储线程局部变量,避免了多线程并发访问的问题。ThreadLocal
使用了线程的 ThreadLocalMap 来存储数据,具有较高的访问速度。ThreadLocal
主要用于在多线程环境下实现线程间的数据隔离,常见的使用场景包括:
ThreadLocal
将请求的上下文信息(如用户信息、请求参数等)保存在线程局部变量中,以便在整个请求处理过程中方便地访问。ThreadLocal
来保存每个线程的连接或者缓存对象,避免线程间的竞争和同步开销。ThreadLocal
将这些参数保存在线程局部变量中,从而避免传递参数的复杂性。需要注意的是,虽然 ThreadLocal
提供了一种简单的线程间数据隔离的方式,但在使用时需要注意谨慎,避免滥用。应该合理使用 ThreadLocal
,并在使用后及时清理线程局部变量,以避免可能的内存泄漏问题。此外,由于 ThreadLocal
的使用涉及到线程间的数据共享和隔离,因此在使用时需要仔细考虑线程安全和并发性问题,确保多线程环境下的正确性和稳定性。
在 Java 中,使用 ThreadLocal
主要涉及以下三个步骤:
ThreadLocal
对象:可以通过直接实例化 ThreadLocal
类或者使用其子类 InheritableThreadLocal
创建一个 ThreadLocal
对象。例如:ThreadLocal threadLocal = new ThreadLocal<>();
ThreadLocal
对象的 set
方法来存储线程局部变量的值,通过 get
方法来获取线程局部变量的值。例如:threadLocal.set(42); // 存储线程局部变量的值
Integer value = threadLocal.get(); // 获取线程局部变量的值
ThreadLocal
使用了线程的 ThreadLocalMap 来存储数据,它会伴随线程的生命周期一直存在,可能导致内存泄漏。因此,在不再使用线程局部变量时,应该及时清理,可以通过 remove
方法来清理线程局部变量的值。例如:threadLocal.remove(); // 清理线程局部变量的值
以下是一个简单的示例,演示了如何使用 ThreadLocal
存储和获取线程局部变量的值:
public class ThreadLocalExample {
private static ThreadLocal threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
// 创建两个线程,并分别设置线程局部变量的值
Thread thread1 = new Thread(() -> {
threadLocal.set(1);
System.out.println("Thread 1 - ThreadLocal value: " + threadLocal.get());
});
Thread thread2 = new Thread(() -> {
threadLocal.set(2);
System.out.println("Thread 2 - ThreadLocal value: " + threadLocal.get());
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
// 获取主线程中的线程局部变量的值(应为 null)
System.out.println("Main Thread - ThreadLocal value: " + threadLocal.get());
}
}
运行以上示例,输出结果应为:
Thread 1 - ThreadLocal value: 1
Thread 2 - ThreadLocal value: 2
Main Thread - ThreadLocal value: null
从输出结果可以看出,每个线程都可以通过 ThreadLocal
获取自己的线程局部变量的值,互不干扰,而主线程中的线程局部变量的值为 null
,说明在主线程中并没有设置该线程局部变量的值。这说明了 ThreadLocal
的线程隔离性。
除了基本的用法外,ThreadLocal
还可以用于一些高级的使用场景,包括但不限于以下几种:
ThreadLocal
可以用于在多个方法或组件之间传递线程上下文信息,避免显式传递参数或全局变量的方式。例如,在一个复杂的业务处理流程中,可以将某些需要在多个方法中共享的上下文信息,如请求信息、用户信息等,存储在 ThreadLocal
中,各个方法可以直接从 ThreadLocal
中获取这些信息,无需显式传递参数,从而简化了方法之间的调用。public class ThreadContext {
private static ThreadLocal userThreadLocal = new ThreadLocal<>();
public static void setUser(User user) {
userThreadLocal.set(user);
}
public static User getUser() {
return userThreadLocal.get();
}
}
public class UserService {
public void processRequest(Request request) {
// 从请求中获取用户信息
User user = extractUserFromRequest(request);
// 存储用户信息到ThreadLocal
ThreadContext.setUser(user);
// 执行业务处理
// ...
// 从ThreadLocal中获取用户信息
User currentUser = ThreadContext.getUser();
// ...
}
}
ThreadLocal
可以用于线程内的资源管理,例如数据库连接、线程池、缓存等。通过 ThreadLocal
可以在线程内维护一个线程独享的资源对象,避免资源对象的创建和销毁在多个线程之间的频繁切换,从而提高性能和减少资源竞争。public class ConnectionManager {
private static ThreadLocal connectionThreadLocal = new ThreadLocal<>();
public static Connection getConnection() {
Connection connection = connectionThreadLocal.get();
if (connection == null) {
// 创建数据库连接
connection = createConnection();
// 存储到ThreadLocal
connectionThreadLocal.set(connection);
}
return connection;
}
public static void releaseConnection() {
Connection connection = connectionThreadLocal.get();
if (connection != null) {
// 关闭数据库连接
closeConnection(connection);
// 从ThreadLocal中移除
connectionThreadLocal.remove();
}
}
}
ThreadLocal
可以用于避免全局变量的使用,从而减少全局变量带来的潜在风险,如线程安全性、可维护性等。通过将线程特定的状态存储在 ThreadLocal
中,避免了使用全局变量来共享状态的方式,从而简化了代码逻辑,降低了代码的复杂度。下面是一个使用 ThreadLocal
避免全局变量的案例,该案例模拟了一个简单的请求处理流程,使用 ThreadLocal
存储请求信息,避免了全局变量的使用:
public class Request {
private String requestId;
private String requestContent;
// 省略构造方法和getter/setter
}
public class RequestContext {
private static ThreadLocal requestThreadLocal = new ThreadLocal<>();
public static void setRequest(Request request) {
requestThreadLocal.set(request);
}
public static Request getRequest() {
return requestThreadLocal.get();
}
public static void clear() {
requestThreadLocal.remove();
}
}
public class RequestProcessor {
public void process(Request request) {
// 将请求信息存储到ThreadLocal
RequestContext.setRequest(request);
// 执行请求处理逻辑
// ...
// 从ThreadLocal中获取请求信息
Request currentRequest = RequestContext.getRequest();
// ...
// 清除ThreadLocal中的请求信息
RequestContext.clear();
}
}
public class Main {
public static void main(String[] args) {
// 模拟多个请求处理线程
for (int i = 0; i < 5; i++) {
new Thread(() -> {
Request request = new Request();
// 设置请求信息
// ...
RequestProcessor processor = new RequestProcessor();
processor.process(request);
}).start();
}
}
}
在这个案例中,RequestContext
类使用了 ThreadLocal
存储了请求信息,每个线程都可以通过 RequestContext.getRequest()
方法获取到自己线程内的请求信息,避免了使用全局变量的方式,从而简化了代码逻辑,降低了全局变量带来的潜在风险。同时,通过在每个请求处理流程中清除 ThreadLocal
中的请求信息,保证了线程之间的隔离性和线程安全性。
ThreadLocal
是 Java 中的一个线程本地变量工具类,它提供了一种在多线程环境下,为每个线程独立维护变量副本的机制,从而实现了线程间的隔离。其原理主要涉及到 ThreadLocal 实例、Thread 对象以及线程之间的数据传递。
在 Java 中,每个线程都有一个 Thread 对象与之对应,Thread 对象中包含了一个 ThreadLocalMap 类型的成员变量 threadLocals,这个 threadLocals 是一个 ThreadLocal 实例的集合,其中 ThreadLocal 是一个弱引用(WeakReference)的键,而实际的变量副本则作为值存储在 ThreadLocalMap 中。ThreadLocalMap 是 ThreadLocal 的内部类,它实现了一个类似于 HashMap 的键值对结构,用于存储每个线程的 ThreadLocal 变量副本。
当我们调用 ThreadLocal 的 set
方法时,实际上是在当前线程的 Thread 对象的 threadLocals 中设置一个键值对,其中键为 ThreadLocal 实例,值为我们传入的变量。当我们调用 ThreadLocal 的 get
方法时,实际上是从当前线程的 Thread 对象的 threadLocals 中获取与当前 ThreadLocal 实例对应的变量副本。
需要注意的是,由于 ThreadLocal 使用了弱引用作为键,当线程结束时,Thread 对象被垃圾回收时,其 threadLocals 中对应的 ThreadLocal 实例也可能被回收,从而避免了内存泄漏的风险。
另外,ThreadLocal 还提供了 initialValue
方法,它可以被子类重写,用于指定在第一次访问 ThreadLocal 变量时,如果没有为该线程设置过值,应该返回的默认值。这个方法在每个线程第一次访问 ThreadLocal 变量时被调用,从而实现了每个线程在第一次访问 ThreadLocal 变量时都可以获取到一个初始化的值。
需要注意的是,虽然 ThreadLocal 可以解决多线程环境下的线程间隔离问题,但也需要小心使用,避免滥用。在使用 ThreadLocal 时,应该合理管理线程与变量之间的生命周期,避免潜在的内存泄漏问题,并确保在多线程环境下不会出现意外的数据共享或线程安全问题。
在使用 ThreadLocal 时,需要注意以下几个事项:
ThreadLocal.withInitial()
方法来设置 ThreadLocal 的初始值,并且避免在使用 ThreadLocal 时出现空指针异常。总之,使用 ThreadLocal 时需要仔细考虑内存泄漏风险、线程安全性、生命周期管理和并发性能,并根据实际情况选择合适的应用场景,从而确保线程间的数据隔离和程序的正确性。
ThreadLocal 的高级使用案例有以下几种:
public class DatabaseConnectionManager {
private static ThreadLocal connectionHolder = new ThreadLocal<>();
public static Connection getConnection() {
Connection connection = connectionHolder.get();
if (connection == null) {
// 创建数据库连接
connection = createConnection();
connectionHolder.set(connection);
}
return connection;
}
public static void releaseConnection() {
Connection connection = connectionHolder.get();
if (connection != null) {
// 关闭数据库连接
closeConnection(connection);
connectionHolder.remove();
}
}
}
public class UserContext {
private static ThreadLocal userHolder = new ThreadLocal<>();
public static void setUser(User user) {
userHolder.set(user);
}
public static User getUser() {
return userHolder.get();
}
public static void clearUser() {
userHolder.remove();
}
}
public class RequestContext {
private static ThreadLocal requestHolder = new ThreadLocal<>();
public static void setRequest(HttpServletRequest request) {
requestHolder.set(request);
}
public static HttpServletRequest getRequest() {
return requestHolder.get();
}
public static void clearRequest() {
requestHolder.remove();
}
}
下面是一个使用 ThreadLocal 实现线程级别的缓存的简单案例,假设我们需要在多个线程间缓存用户信息。
public class UserCache {
private static ThreadLocal
在上面的例子中,UserCache
类使用了 ThreadLocal
来实现线程级别的用户信息缓存。每个线程都有自己的缓存,通过 cacheHolder
这个 ThreadLocal
对象来存储和获取缓存数据,从而避免了线程间的缓存共享和竞争条件。每个线程可以通过 UserCache
类的静态方法来缓存和获取用户信息,而不会影响其他线程的缓存数据。在不需要使用缓存时,可以通过 clearCache()
方法来清空当前线程的缓存数据,从而释放资源。