《Java 并发编程的艺术》实验02-3 JUC 并发工具类的使用

发布时间 2023-10-31 16:31:10作者: Ba11ooner

JUC 并发工具类的使用

CountDownLatch

简介

CountDownLatch 是Java并发包中的一个基本类,它可以用来在多个线程间同步操作。

其主要功能是允许一个或多个线程等待,直到其他线程完成它们的操作后再继续执行。

CountDownLatch 是通过一个计数器实现的,计数器的初始值可以设置为任意整数,当一个线程调用 countDown() 方法时,计数器的值将减 1,当计数器的值为0时,那些等待的线程将被唤醒。

实验
实验目的

通过实验了解 CountDownLatch 的使用,掌握其基本用法和原理。

实验内容

某游戏中,只有队伍中有 5 个人,才能开启一场比赛,请利用 CountDownLatch 实现等待机制

实验过程
  1. 创建 CountDownLatch
  2. 实现计数业务逻辑
  3. 等待计数器归零
  4. 统一同步业务执行
示例代码

下面是一个简单的示例代码,演示了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 实现等待机制

实验过程
  1. 创建 CyclicBarrier
  2. 实现等待业务逻辑
  3. 等待屏障条件满足
  4. 统一同步业务执行
  5. 并行同步业务执行
示例代码
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 个顾客。商场设有入口门和出口门,每个顾客在进入商场时需要获得一个计数的许可,而离开商场时释放这个许可。

实验过程
  1. 利用 Semaphore 封装限流资源,包括资源数和加解锁过程
  2. 将限流资源作为属性,封装限流资源的使用者,实现限流资源相关逻辑,包括限流资源的使用和放回过程
  3. 声明限流资源
  4. 声明资源使用者,向资源使用者传入共享的限流资源
示例代码
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),他们在进行交易时需要进行商品的交换。为了确保交易的公平性,所有交易员需要进行配对,并将各自的商品交换给对方。

实验过程
  1. 封装交易员
  2. 利用 Exchange 封装交易逻辑
  3. 测试交易逻辑
示例代码
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