使用Exchanger实现两个线程之间的数据交互

本文介绍了一种简化Jetty EndPoint测试的方法。通过对比传统使用CountDownLatch的方式,引入了Exchanger来优化多线程间的数据交换过程,提高了代码的简洁性和可维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在看Jetty源码中的EndPointTest类,对EndPoint的测试,我的思路是:
1. 建立一个连接(创建ServerSocket实例,一般还会给定一个端口,其实可以bind(null)以让操作系统分配一个可用端口),新启动一个线程,在新线程中监听给定端口(调用accept方法)。
2. 发送客户端请求(创建一个Socket实例,并向该Socket写入请求数据)。
3. 在接收端读取数据,验证写入的请求和接收到的数据相同。

在以上流程实现中,accept方法返回的接收端Socket需要传给主线程,同时要保证使用该Socket是在accept方法返回之后,以我习惯,我会使用一个Lock或CountDownLatch:
private static class SocketHolder {
    Socket socket;
}

@Test
public void levinOldWayTest() throws Exception {
    final ServerSocket server = new ServerSocket(10240);
    
    final CountDownLatch latch = new CountDownLatch(1);
    final SocketHolder socketHolder = new SocketHolder();
    new Thread() {
        public void run() {
            try {
                socketHolder.socket = server.accept();
                latch.countDown();
            } catch(Exception ex) {
                ex.printStackTrace();
            }
        }
    }.start();
    
    Socket socket = new Socket(server.getInetAddress(), server.getLocalPort());
    socket.getOutputStream().write("My Test String".getBytes());
    
    latch.await(5, TimeUnit.SECONDS);
    byte[] receives = new byte[4096];
    int length = socketHolder.socket.getInputStream().read(receives);
    
    assertEquals("My Test String", new String(receives, 0, length));
    
    socket.close();
    socketHolder.socket.close();
    server.close();
}

不知道有多少人也像我一样把这段代码写成这样?这里有两个问题:
1. ServerSocket的监听的端口不一定是可用的,类似测试代码我之前没有写过,我估计自己正真在写的时候应该会想到让操作系统动态分配。
2. 为了在两个线程中传递数据,这里首先创建了一个SocketHolder类,然后使用CountDownLatch,写起来好麻烦。为了简化这段代码,可以使用Exchanger,即当一个生产者线程准备好数据后可以通过Exchanger将数据传递给消费者,而消费者在生产者传递过来数据后就可以消费了,这里的数据就是Socket。

改进后的代码如下:
@Test
public void levinImprovedWayTest() throws Exception {
    final ServerSocket server = new ServerSocket();
    server.bind(null);
    
    final Exchanger<Socket> exchanger = new Exchanger<Socket>();
    new Thread() {
        public void run() {
            try {
                exchanger.exchange(server.accept());
            } catch(Exception ex) {
                ex.printStackTrace();
            }
        }
    }.start();
    
    Socket socket = new Socket(server.getInetAddress(), server.getLocalPort());
    socket.getOutputStream().write("My Test String".getBytes());
    
    Socket receiverSocket = exchanger.exchange(null, 5, TimeUnit.SECONDS);
    byte[] receives = new byte[4096];
    int length = receiverSocket.getInputStream().read(receives);
    
    assertEquals("My Test String", new String(receives, 0, length));
    
    socket.close();
    receiverSocket.close();
    server.close();
}

<think>在并发编程中,一个线程影响另一个线程主要通过以下几种机制: 1. 共享内存:线程之间共享进程的地址空间,可以通过读写共享变量来影响其他线程。 2. 线程间通信(IPC):如使用管道、信号、消息队列、套接字等(虽然这些通常用于进程间,但有些也可用于线程间)。 3. 同步机制:如互斥锁、条件变量、信号量等,用于控制线程的执行顺序和协调对共享资源的访问。 4. 线程协作:如一个线程可以中断另一个线程(不推荐),或者通过设置标志位请求另一个线程停止。 下面我们通过一个具体的例子来说明:两个线程通过共享变量和条件变量进行交互。 场景:一个生产者线程和一个消费者线程,生产者生产数据,消费者消费数据。当生产者生产了一个数据后,通知消费者来消费。 代码示例(使用C++11的线程库): ```cpp #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> std::mutex mtx; std::condition_variable cv; std::queue<int> data_queue; const int MAX_DATA = 10; void producer() { for (int i = 0; i < MAX_DATA; ++i) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产耗时 { std::lock_guard<std::mutex> lock(mtx); data_queue.push(i); std::cout << "Produced: " << i << std::endl; } cv.notify_one(); // 通知等待的消费者 } } void consumer() { while (true) { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return !data_queue.empty(); }); // 等待直到队列不为空 int data = data_queue.front(); data_queue.pop(); std::cout << "Consumed: " << data << std::endl; lock.unlock(); if (data == MAX_DATA - 1) break; // 如果消费了最后一个数据则退出 } } int main() { std::thread t1(producer); std::thread t2(consumer); t1.join(); t2.join(); return 0; } ``` ### 代码解释: 1. **共享数据**:`data_queue`是一个共享的队列,生产者和消费者通过它传递数据。 2. **互斥锁(mutex)**:`mtx`用于保护对共享队列的访问,防止同时读写导致的数据竞争。 3. **条件变量(condition_variable)**:`cv`用于线程间通知。生产者生产数据后调用`cv.notify_one()`唤醒等待的消费者线程。消费者线程调用`cv.wait()`等待通知,并在等待时自动释放锁,允许生产者继续生产。 4. **生产者线程**:循环生产数据(0到9),每次生产后通知消费者。 5. **消费者线程**:循环消费数据,当队列为空时等待,直到被生产者通知。当消费到最后一个数据(9)时退出循环。 ### 其他影响方式: - **原子操作**:使用原子变量(如C++11的`std::atomic`)可以实现无锁同步,一个线程修改原子变量,另一个线程读取该变量。 - **线程中断**:某些语言(如Java)提供了线程中断机制,一个线程可以调用另一个线程的`interrupt()`方法,但被中断的线程需要检查中断标志并决定如何响。 - **信号**:在Unix-like系统中,一个线程可以向同一进程内的另一个线程发送信号(如pthread_kill),但这种方式需要谨慎使用。 ### 注意事项: - 避免竞态条件:使用适当的同步机制。 - 避免死锁:确保锁的获取和释放顺序一致。 - 避免活锁:线程在不断改变状态却无法继续执行。 - 资源管理:确保共享资源的正确释放。 §§相关问题§§: 1. 在多线程编程中,如何避免死锁? 2. 原子操作和锁的区别是什么?在什么情况下使用原子操作? 3. 请解释条件变量与互斥锁通常一起使用的原因。 4. 在Java中,如何实现两个线程之间的通信?</think>在并发编程中,一个线程影响另一个线程主要通过以下几种机制实现。我将详细解释每种机制并提供代码示例: ### 1. 共享内存(最常用) 线程共享进程的地址空间,可以通过共享变量相互影响。 ```java public class SharedMemoryExample { private static volatile boolean flag = false; private static int sharedValue = 0; public static void main(String[] args) { // 线程A:设置标志位和值 new Thread(() -> { try { Thread.sleep(1000); // 模拟工作 sharedValue = 42; flag = true; // 通知线程B System.out.println("Thread A: Set flag and value"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); // 线程B:监听标志位 new Thread(() -> { while (!flag) { // 忙等待(实际用中避免) } System.out.println("Thread B: Received value: " + sharedValue); }).start(); } } ``` **关键点**: - `volatile` 确保可见性(一个线程的修改对其他线程立即可见) - 需要同步机制(如`synchronized`)避免竞态条件 - 共享变量作为通信媒介 ### 2. 线程间通信(Wait/Notify) 使用Java内置的等待/通知机制: ```java public class WaitNotifyExample { private static final Object lock = new Object(); private static boolean dataReady = false; public static void main(String[] args) { // 生产者线程 new Thread(() -> { synchronized (lock) { try { Thread.sleep(2000); // 模拟数据准备 dataReady = true; lock.notifyAll(); // 唤醒等待线程 System.out.println("Producer: Data ready!"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }).start(); // 消费者线程 new Thread(() -> { synchronized (lock) { while (!dataReady) { try { System.out.println("Consumer: Waiting for data..."); lock.wait(); // 释放锁并等待 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.println("Consumer: Processing data!"); } }).start(); } } ``` **执行流程**: 1. 消费者获取锁,检查`dataReady`为false 2. 消费者调用`wait()`释放锁并进入等待 3. 生产者获取锁,准备数据后设置`dataReady=true` 4. 生产者调用`notifyAll()`唤醒消费者 5. 消费者重新获取锁并处理数据 ### 3. 阻塞队列(高级通信) 使用`BlockingQueue`实现线程安全的生产者-消费者模式: ```java import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class BlockingQueueExample { public static void main(String[] args) { BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); // 生产者 new Thread(() -> { try { for (int i = 0; i < 5; i++) { queue.put(i); // 阻塞直到空间可用 System.out.println("Produced: " + i); Thread.sleep(500); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); // 消费者 new Thread(() -> { try { for (int i = 0; i < 5; i++) { int value = queue.take(); // 阻塞直到数据可用 System.out.println("Consumed: " + value); Thread.sleep(1000); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); } } ``` **优势**: - 内置线程安全 - 自动处理阻塞/唤醒 - 解耦生产者和消费者 ### 4. 中断机制 一个线程可以中断另一个线程的执行: ```java public class InterruptExample { public static void main(String[] args) { Thread worker = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { try { System.out.println("Working..."); Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("Interrupted! Exiting..."); Thread.currentThread().interrupt(); // 重新设置中断状态 } } }); worker.start(); // 主线程在3秒后中断worker try { Thread.sleep(3000); worker.interrupt(); // 发送中断信号 } catch (InterruptedException e) { e.printStackTrace(); } } } ``` **中断要点**: - 协作式中断(被中断线程决定如何响) - 检查`isInterrupted()`或捕获`InterruptedException` - 不能强制停止线程,只能请求停止 ### 5. 其他影响方式 - **ThreadLocal**:线程私有变量(反向影响 - 避免影响) - **Phaser/CyclicBarrier**:同步多个线程的执行阶段 - **Exchanger**:线程间交换数据 - **Pipes**:线程间单向数据流 ### 线程安全最佳实践 1. 优先使用不可变对象 2. 使用线程安全集合(`ConcurrentHashMap`, `CopyOnWriteArrayList`) 3. 最小化同步范围(减小锁粒度) 4. 避免嵌套锁(防止死锁) 5. 使用`java.util.concurrent`高级工具类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值