《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