《Java 并发编程的艺术》实验02-3 JUC 并发工具类的使用
JUC 并发工具类的使用
CountDownLatch
简介
CountDownLatch 是Java并发包中的一个基本类,它可以用来在多个线程间同步操作。
其主要功能是允许一个或多个线程等待,直到其他线程完成它们的操作后再继续执行。
CountDownLatch 是通过一个计数器实现的,计数器的初始值可以设置为任意整数,当一个线程调用 countDown()
方法时,计数器的值将减 1,当计数器的值为0时,那些等待的线程将被唤醒。
实验
实验目的
通过实验了解 CountDownLatch 的使用,掌握其基本用法和原理。
实验内容
某游戏中,只有队伍中有 5 个人,才能开启一场比赛,请利用 CountDownLatch 实现等待机制
实验过程
- 创建 CountDownLatch
- 实现计数业务逻辑
- 等待计数器归零
- 统一同步业务执行
示例代码
下面是一个简单的示例代码,演示了CountDownLatch的基本用法:
public class CountDownLatchDemo {
public static void main(String[] args) {
int N = 5;
CountDownLatch latch = new CountDownLatch(N);
//计数逻辑实现
for (int i = 0; i < N; i++) {
final int j = i;
new Thread(() -> {
try {
Thread.sleep((long) (Math.random() * 100));
System.out.println("Player-" + j + " enters the game");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
try {
//等待所有玩家进入后,主线程继续统一执行同步业务
latch.await();
System.out.println("All players entered, game starts");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Player-2 enters the game
Player-4 enters the game
Player-1 enters the game
Player-3 enters the game
Player-0 enters the game
All players entered, game starts
CyclicBarrier
简介
CyclicBarrier 是 Java 中的一个同步工具类,它可以让一个或多个线程等待其他线程达到一个共同的屏障点(barrier point)再继续执行。
在 CyclicBarrier 中,我们可以指定一个计数器,当计数器的值达到指定值时,所有等待的线程会被释放,并继续执行。而每个线程在等待时,可以选择执行自己的逻辑或等待直到所有线程达到屏障点。一旦所有线程达到屏障点,CyclicBarrier 可以选择执行指定的回调方法(barrier action)。
CyclicBarrier 的特点包括:
- 可重用:一旦所有线程达到屏障点并被释放,CyclicBarrier 的计数器会被重置,可以再次使用。
- 线程安全:CyclicBarrier 内部使用了内部锁来保证线程安全。
- 可以设置回调方法:在所有线程到达屏障点时,CyclicBarrier 可以选择执行指定的回调方法。
实验
实验目的
掌握 CyclicBarrier 类的基本使用,了解其在多线程编程中的应用场景。
实验内容
某游戏中,只有队伍中有 5 个人,才能开启一场比赛,请利用 CyclicBarrier 实现等待机制
实验过程
- 创建 CyclicBarrier
- 实现等待业务逻辑
- 等待屏障条件满足
- 统一同步业务执行
- 并行同步业务执行
示例代码
public class CyclicBarrierDemo {
public static void main(String[] args) {
int N = 5;
CyclicBarrier barrier = new CyclicBarrier(N, () -> {
//等待所有玩家进入后,主线程继续统一执行同步业务
System.out.println("All players entered, game starts");
});
for (int i = 0; i < N; i++) {
final int j = i;
new Thread(() -> {
try {
Thread.sleep((long) (Math.random() * 100));
System.out.println("Player-" + j + " enters the game");
barrier.await();
System.out.println("Player-" + j + " starts playing the game");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Player-3 enters the game
Player-1 enters the game
Player-2 enters the game
Player-0 enters the game
Player-4 enters the game
All players entered, game starts
Player-4 starts playing the game
Player-3 starts playing the game
Player-1 starts playing the game
Player-2 starts playing the game
Player-0 starts playing the game
Semaphoere
简介
Semaphore 是一种用于控制并发访问资源的同步机制。它通过一个计数器来管理一定数量的许可(permits)。
每当一个线程需要访问某个共享资源时,它必须首先获得一个许可,它会尝试减少许可的数量。当许可数量为 0 时,其他线程就无法再获得许可,只能等待。当持有许可的线程离开临界区时,它会将许可的数量加 1,这样其他等待的线程就可获得许可。
实验
实验目的
掌握 Semaphore(信号量)类的基本使用,了解其在多线程编程中的应用场景。
实验内容
假设有一个商场,最多容纳 capacity
个顾客。商场设有入口门和出口门,每个顾客在进入商场时需要获得一个计数的许可,而离开商场时释放这个许可。
实验过程
- 利用 Semaphore 封装限流资源,包括资源数和加解锁过程
- 将限流资源作为属性,封装限流资源的使用者,实现限流资源相关逻辑,包括限流资源的使用和放回过程
- 声明限流资源
- 声明资源使用者,向资源使用者传入共享的限流资源
示例代码
public class SemaphoreDemo {
//客流量有上限的商场
static class Mall {
private final Semaphore semaphore;
public Mall(int capacity) {
this.semaphore = new Semaphore(capacity);
}
public void enter() throws InterruptedException {
semaphore.acquire();
}
public void exit() {
semaphore.release();
}
}
static class Customer implements Runnable {
Mall mall;
int id;
public Customer(Mall mall, int id) {
this.mall = mall;
this.id = id;
}
@Override
public void run() {
try {
System.out.println("Customer-" + id + " enters the mall");
mall.enter();
System.out.println("Customer-" + id + " is shopping");
Thread.sleep((long) (Math.random() * 1000));
System.out.println("Customer-" + id + " finishes shopping");
mall.exit();
System.out.println("Customer-" + id + " exits the mall");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
int capacity = 2;
int amount = 3;
Mall mall = new Mall(capacity);
Thread[] threads = new Thread[amount];
for (int i = 0; i < amount; i++) {
Customer customer = new Customer(mall, i);
threads[i] = new Thread(customer);
threads[i].start();
}
for (int i = 0; i < amount; i++) {
threads[i].join();
}
}
}
Customer-1 enters the mall
Customer-2 enters the mall
Customer-0 enters the mall
Customer-2 is shopping
Customer-1 is shopping
Customer-1 finishes shopping
Customer-1 exits the mall
Customer-0 is shopping
Customer-2 finishes shopping
Customer-2 exits the mall
Customer-0 finishes shopping
Customer-0 exits the mall
Exchange
简介
JUC Exchange(Java Util Concurrent Exchange)是Java并发工具包中的一个类,它用于在两个线程之间进行数据交换。
在多线程编程中,有时候我们需要将数据在不同的线程之间传递,常见的做法是使用共享变量或者通过消息队列等机制进行数据交换。而 JUC Exchange 类提供了一种更简单、更高效的方式来实现线程间的数据交换。
JUC Exchange 类的主要特点如下:
- 它是一个泛型类,可以传递任意类型的数据。
- 它提供了两种交换数据的方法:
exchange(V x)
和exchange(V x, long timeout, TimeUnit unit)
。V exchange(V x)
:将参数 x 与另一个线程通过交换数据的方式进行数据交换,并返回另一个线程传递过来的数据。如果当前线程没有其他线程等待交换数据,那么当前线程会被阻塞,直到有其他线程调用了相同的exchange
方法。V exchange(V x, long timeout, TimeUnit unit)
:与上面的方法类似,但是如果在指定的时间内还没有其他线程调用了相同的exchange
方法,那么当前线程会被唤醒,并返回一个特定的标识(可以自定义)。
- 它使用的是同步机制,即当没有线程在等待交换数据时,调用
exchange
方法的线程会被阻塞,直到有另一个线程调用了相同的exchange
方法为止。 - 它可以用于实现一些特定的同步场景,比如生产者-消费者模型、线程间通信等。
实验
实验目的
掌握 Exchange 类的基本使用,了解其在多线程编程中的应用场景。
实验内容
系统中有多位交易员(Trader),他们在进行交易时需要进行商品的交换。为了确保交易的公平性,所有交易员需要进行配对,并将各自的商品交换给对方。
实验过程
- 封装交易员
- 利用 Exchange 封装交易逻辑
- 测试交易逻辑
示例代码
package juc;
import java.util.concurrent.Exchanger;
public class ExchangeDemo {
static class Trader {
private final String name;
private String product;
public Trader(String name, String product) {
this.name = name;
this.product = product;
}
public String getName() {
return name;
}
public String getProduct() {
return product;
}
public void setProduct(String product) {
this.product = product;
}
}
static class ExchangeService {
private static final Exchanger<String> exchanger = new Exchanger<>();
public static void exchange(Trader trader1, Trader trader2) {
trade(trader1);
trade(trader2);
}
private static void trade(Trader trader) {
System.out.println("交换前 " + trader.getName() + " 拥有 " + trader.getProduct());
new Thread(() -> {
try {
System.out.println(trader.getName() + " 正在交换商品");
String product = exchanger.exchange(trader.getProduct());
System.out.println(trader.getName() + " 交换到了 " + product);
trader.setProduct(product);
System.out.println("交换后 " + trader.getName() + " 拥有 " + trader.getProduct());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
public static void main(String[] args) {
Trader trader1 = new Trader("Trader1", "Apple");
Trader trader2 = new Trader("Trader2", "Banana");
ExchangeService.exchange(trader1, trader2);
}
}
交换前 Trader1 拥有 Apple
交换前 Trader2 拥有 Banana
Trader1 正在交换商品
Trader2 正在交换商品
Trader2 交换到了 Apple
Trader1 交换到了 Banana
交换后 Trader1 拥有 Banana
交换后 Trader2 拥有 Apple
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏