Java并发编程之代码实现两玩家交换装备

目录
  • 1 Exchanger 是什么
  • 2 Exchanger 详解
  • 3 Exchanger 应用
  • 总结

1 Exchanger 是什么

JDK 1.5 开始 JUC 包下提供的 Exchanger 类可用于两个线程之间交换信息。Exchanger 对象可理解为一个包含2个格子的容器,通过调用 exchanger 方法向其中的格子填充信息,当两个格子中的均被填充信息时,自动交换两个格子中的信息,然后将交换的信息返回给调用线程,从而实现两个线程的信息交换。

功能看似简单,但这在某些场景下是很有用处的,例如游戏中两个玩家交换装备;交友软件男女心仪对象匹配。

下面简单模拟下两个玩家交换装备的场景。

package com.chenpi;
import java.util.concurrent.Exchanger;
/**
 * @Description
 * @Author 陈皮
 * @Date 2021/7/11
 * @Version 1.0
 */
public class ChenPiMain {
  public static void main(String[] args) throws InterruptedException {
    Exchanger<String> exchanger = new Exchanger<>();
    new Thread(() -> {
      String str = null;
      try {
        str = exchanger.exchange("屠龙刀");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("交易成功," + Thread.currentThread().getName() + "获得" + str);
    }, "周芷若").start();
    new Thread(() -> {
      String str = null;
      try {
        str = exchanger.exchange("倚天剑");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("交易成功," + Thread.currentThread().getName() + "获得" + str);
    }, "张无忌").start();
  }
}

// 输出结果如下
交易成功,张无忌获得屠龙刀
交易成功,周芷若获得倚天剑

2 Exchanger 详解

Exchager 类可用于两个线程之间交换信息,如果一个线程调用了 Exchanger 对象的 exchange 方法之后,会一直阻塞直到另一个线程来和它交换信息,交换之后的信息返回给调用线程,从而实现两个线程的信息交换。

Exchager 底层也是使用到了自旋和 cas 机制。

注意,如果超过两个线程调用同一个 Exchanger 对象 exchange 方法时,结果是不可预计的,只要有2个线程满足条件了,就认为匹配成功并交换信息。而剩下的未能得到配对的线程,则会被阻塞一直等待直到有另一个线程能与它匹配与之配对。

package com.chenpi;
import java.util.concurrent.Exchanger;
/**
 * @Description
 * @Author 陈皮
 * @Date 2021/7/11
 * @Version 1.0
 */
public class ChenPiMain {
  public static void main(String[] args) {
    Exchanger<String> exchanger = new Exchanger<>();
    new Thread(() -> {
      String str = null;
      try {
        str = exchanger.exchange("屠龙刀");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("交易成功," + Thread.currentThread().getName() + "获得" + str);
    }, "周芷若").start();
    new Thread(() -> {
      String str = null;
      try {
        str = exchanger.exchange("倚天剑");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("交易成功," + Thread.currentThread().getName() + "获得" + str);
    }, "张无忌").start();
    new Thread(() -> {
      String str = null;
      try {
        str = exchanger.exchange("假的倚天剑");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("交易成功," + Thread.currentThread().getName() + "获得" + str);
    }, "成昆").start();
  }
}

// 输出结果如下
交易成功,周芷若获得假的倚天剑
交易成功,成昆获得屠龙刀

当然,在等待交换信息的线程是可以被中断的,就比如玩家在等待交易过程中,突然玩家下线了,那就应该中断线程等待。

package com.chenpi;
import java.lang.Thread.State;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Exchanger;
/**
 * @Description
 * @Author 陈皮
 * @Date 2021/7/11
 * @Version 1.0
 */
public class ChenPiMain {
  public static void main(String[] args) throws InterruptedException {
    Exchanger<String> exchanger = new Exchanger<>();
    List<Thread> threads = new ArrayList<>(3);
    Thread thread1 = new Thread(() -> {
      String str = null;
      try {
        str = exchanger.exchange("屠龙刀");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("交易成功," + Thread.currentThread().getName() + "获得" + str);
    }, "周芷若");
    threads.add(thread1);
    Thread thread2 = new Thread(() -> {
      String str = null;
      try {
        str = exchanger.exchange("倚天剑");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("交易成功," + Thread.currentThread().getName() + "获得" + str);
    }, "张无忌");
    threads.add(thread2);
    Thread thread3 = new Thread(() -> {
      String str = null;
      try {
        str = exchanger.exchange("假的屠龙刀");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("交易成功," + Thread.currentThread().getName() + "获得" + str);
    }, "成昆");
    threads.add(thread3);
    for (Thread thread : threads) {
      thread.start();
    }
    // 等待5秒
    Thread.sleep(5000);
    for (Thread thread : threads) {
      System.out.println(thread.getName() + ":" + thread.getState());
      // 如果还在阻塞等待则中断线程
      if (thread.getState() == State.WAITING) {
        thread.interrupt();
      }
    }
  }
}

// 输出结果如下
交易成功,张无忌获得屠龙刀
交易成功,周芷若获得倚天剑
周芷若:TERMINATED
张无忌:TERMINATED
成昆:WAITING
交易成功,成昆获得null
java.lang.InterruptedException
at java.util.concurrent.Exchanger.exchange(Exchanger.java:568)
at com.chenpi.ChenPiMain.lambda$main$2(ChenPiMain.java:47)
at java.lang.Thread.run(Thread.java:748)

上面演示的是线程如果等不到另一个线程和它交换信息,则会一直等待下去。其实 Exchanger 还可以设置等待指定时间。比如系统设置玩家交换装备匹配时间为60秒,如果超出时间则终止交易。

package com.chenpi;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
 * @Description
 * @Author 陈皮
 * @Date 2021/7/11
 * @Version 1.0
 */
public class ChenPiMain {
  public static void main(String[] args) {
    Exchanger<String> exchanger = new Exchanger<>();
    new Thread(() -> {
      try {
        // 超时时间设置为5秒
        String str = exchanger.exchange("屠龙刀", 5, TimeUnit.SECONDS);
        System.out.println("交易成功," + Thread.currentThread().getName() + "获得" + str);
      } catch (TimeoutException e) {
        System.out.println("交易超时!");
        e.printStackTrace();
      } catch (InterruptedException e) {
        System.out.println("交易异常终止");
        e.printStackTrace();
      }
    }, "周芷若").start();
  }
}

// 输出结果如下
交易超时!
java.util.concurrent.TimeoutException
at java.util.concurrent.Exchanger.exchange(Exchanger.java:626)
at com.chenpi.ChenPiMain.lambda$main$0(ChenPiMain.java:22)
at java.lang.Thread.run(Thread.java:748)

3 Exchanger 应用

Exchager 在遗传算法和管道设计等应用中是非常有用的。比如两个线程之间交换缓冲区,填充缓冲区的线程在需要时从另一个线程获得一个刚清空的缓冲区,并将填充的缓冲区传递给清空缓冲区的线程。

package com.chenpi;
import java.awt.image.DataBuffer;
import java.util.concurrent.Exchanger;
/**
 * @Description
 * @Author 陈皮
 * @Date 2021/7/11
 * @Version 1.0
 */
public class ChenPiMain {
  Exchanger<DataBuffer> exchanger = new Exchanger<DataBuffer>();
  DataBuffer initialEmptyBuffer = ... a made-up type
  DataBuffer initialFullBuffer = ...
  class FillingLoop implements Runnable {
    public void run() {
      DataBuffer currentBuffer = initialEmptyBuffer;
      try {
        while (currentBuffer != null) {
          addToBuffer(currentBuffer);
          if (currentBuffer.isFull()) {
            currentBuffer = exchanger.exchange(currentBuffer);
          }
        }
      } catch (InterruptedException ex) { ...handle ...}
    }
  }
  class EmptyingLoop implements Runnable {
    public void run() {
      DataBuffer currentBuffer = initialFullBuffer;
      try {
        while (currentBuffer != null) {
          takeFromBuffer(currentBuffer);
          if (currentBuffer.isEmpty()) {
            currentBuffer = exchanger.exchange(currentBuffer);
          }
        }
      } catch (InterruptedException ex) { ...handle ...}
    }
  }
  void start() {
    new Thread(new FillingLoop()).start();
    new Thread(new EmptyingLoop()).start();
  }
}

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • Java并发编程Callable与Future的应用实例代码

    本文主要探究的是java并发编程callable与future的使用,分享了相关实例代码,具体介绍如下. 我们都知道实现多线程有2种方式,一种是继承Thread,一种是实现Runnable,但这2种方式都有一个缺陷,在任务完成后无法获取返回结果.要想获得返回结果,就得使用Callable,Callable任务可以有返回值,但是没法直接从Callable任务里获取返回值:想要获取Callabel任务的返回值,需要用到Future.所以Callable任务和Future模式,通常结合起来使用. 试想

  • Java编程一道多线程问题实例代码

    前面几篇博文基本上总结了一下java并发里的一些内容,这篇博文主要从一个问题入手,看看都能用到前面总结的哪些并发技术去解决. 题目描述: 模拟一个场景:处理16条日志记录,每条日志记录打印时间需要1秒,正常情况下如果将这16条记录去部打完需要16秒,现在为了提高效率,准备开启4个线程去打印,4秒钟打印完,实现这个demo. 先来分析一下这个题目,关于这16条日志记录,我们可以在主线程中产生出来,这没用什么难度,关键是开启4个线程去执行,现在有两种思路:一种是日志的产生和打印日志的线程在逻辑上分开

  • Java并发编程之ReentrantLock可重入锁的实例代码

    目录 1.ReentrantLock可重入锁概述2.可重入3.可打断4.锁超时5.公平锁6.条件变量 Condition 1.ReentrantLock可重入锁概述 相对于 synchronized 它具备如下特点 可中断 synchronized锁加上去不能中断,a线程应用锁,b线程不能取消掉它 可以设置超时时间 synchronized它去获取锁时,如果对方持有锁,那么它就会进入entryList一直等待下去.而可重入锁可以设置超时时间,规定时间内如果获取不到锁,就放弃锁 可以设置为公平锁

  • Java编程多线程之共享数据代码详解

    本文主要总结线程共享数据的相关知识,主要包括两方面:一是某个线程内如何共享数据,保证各个线程的数据不交叉:一是多个线程间如何共享数据,保证数据的一致性. 线程范围内共享数据 自己实现的话,是定义一个Map,线程为键,数据为值,表中的每一项即是为每个线程准备的数据,这样在一个线程中数据是一致的. 例子 package com.iot.thread; import java.util.HashMap; import java.util.Map; import java.util.Random; /*

  • java并发编程之同步器代码示例

    同步器是一些使线程能够等待另一个线程的对象,允许它们协调动作.最常用的同步器是CountDownLatch和Semaphore,不常用的是Barrier和Exchanger 队列同步器AbstractQueuedSynchronizer是用来构建锁或者其他同步组件的基础框架,它内部使用了一个volatiole修饰的int类型的成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作. 同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽

  • Java编程数组中最大子矩阵简便解法实现代码

    本文研究的主要是Java编程数组中最大子矩阵的相关内容,具体介绍如下. 遇到一个好人,可以改变一生:遇到一本好书,又何尝不是呢? 最近在翻阅 左程云先生的<程序员代码面试指南–IT名企算法与数据结构题目最优解>时就非常的有感悟.建议有这方面爱好的博友,也去观摩观摩. 书中讲解的基于栈的数组的最大矩阵的算法很经典,但是博主能力有限,没能彻底的领悟该算法的精髓,但是根据这个思想,博主想出了一种简易的应对该类问题的算法,现概述如下. 核心思想 先来看一张图吧,我们就可以大致的理解了. 如图,每一个轮

  • Java编程基于快速排序的三个算法题实例代码

    快速排序原理简介 快速排序是我们之前学习的冒泡排序的升级,他们都属于交换类排序,都是采用不断的比较和移动来实现排序的.快速排序是一种非常高效的排序算法,它的实现,增大了记录的比较和移动的距离,将关键字较大的记录从前面直接移动到后面,关键字较小的记录从后面直接移动到前面,从而减少了总的比较次数和移动次数.同时采用"分而治之"的思想,把大的拆分为小的,小的拆分为更小的,其原理如下:对于给定的一组记录,选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,

  • Java并发编程之代码实现两玩家交换装备

    目录 1 Exchanger 是什么 2 Exchanger 详解 3 Exchanger 应用 总结 1 Exchanger 是什么 JDK 1.5 开始 JUC 包下提供的 Exchanger 类可用于两个线程之间交换信息.Exchanger 对象可理解为一个包含2个格子的容器,通过调用 exchanger 方法向其中的格子填充信息,当两个格子中的均被填充信息时,自动交换两个格子中的信息,然后将交换的信息返回给调用线程,从而实现两个线程的信息交换. 功能看似简单,但这在某些场景下是很有用处的

  • Java并发编程(CyclicBarrier)实例详解

    Java并发编程(CyclicBarrier)实例详解 前言: 使用JAVA编写并发程序的时候,我们需要仔细去思考一下并发流程的控制,如何让各个线程之间协作完成某项工作.有时候,我们启动N个线程去做一件事情,只有当这N个线程都达到某一个临界点的时候,我们才能继续下面的工作,就是说如果这N个线程中的某一个线程先到达预先定义好的临界点,它必须等待其他N-1线程也到达这个临界点,接下来的工作才能继续,只要这N个线程中有1个线程没有到达所谓的临界点,其他线程就算抢先到达了临界点,也只能等待,只有所有这N

  • Java并发编程Semaphore计数信号量详解

    Semaphore 是一个计数信号量,它的本质是一个共享锁.信号量维护了一个信号量许可集.线程可以通过调用acquire()来获取信号量的许可:当信号量中有可用的许可时,线程能获取该许可:否则线程必须等待,直到有可用的许可为止. 线程可以通过release()来释放它所持有的信号量许可(用完信号量之后必须释放,不然其他线程可能会无法获取信号量). 简单示例: package me.socketthread; import java.util.concurrent.ExecutorService;

  • Java 并发编程:volatile的使用及其原理解析

    Java并发编程系列[未完]: •Java 并发编程:核心理论 •Java并发编程:Synchronized及其实现原理 •Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) •Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) •Java 并发编程:volatile的使用及其原理 一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchroniz

  • Java 并发编程学习笔记之Synchronized简介

    一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题.从语法上讲,Synchronized总共有三种用法: (1)修饰普通方法 (2)修饰静态方法 (3)修饰代码块 接下来我就通过几个例子程序来说明一下这三种使用方式(为了便于比较,三段代码除了Synchronized的使用方式不同以外,

  • Java 并发编程之线程挂起、恢复与终止

    挂起和恢复线程 Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已经被淘汰,因为它们是不安全的,不稳定的.如果在不合适的时候挂起线程(比如,锁定共享资源时),此时便可能会发生死锁条件--其他线程在等待该线程释放锁,但该线程却被挂起了,便会发生死锁.另外,在长时间计算期间挂起线程也可能导致问题. 下面的代码演示了通过休眠来延缓运行,模拟长时间运行的情况,使线程更可能在不适当的时候被挂起: public class DeprecatedSuspendResume

  • Java 并发编程学习笔记之核心理论基础

    并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密,这样才能写出高效.安全.可靠的多线程并发程序.本系列会从线程间协调的方式(wait.notify.notifyAll).Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制.在此基础上,我们会进一步分析java.util.concurrent包的工具类,包括其使用方式.实现源码及其背后的原理.本

  • 深入分析java并发编程中volatile的实现原理

    引言 在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的"可见性".可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值.它在某些情况下比synchronized的开销更小,本文将深入分析在硬件层面上Inter处理器是如何实现Volatile的,通过深入分析能帮助我们正确的使用Volatile变量. 术语定义 术语 英文单词 描述 共享变量 在多个线

随机推荐