专栏首页我是攻城师理解Java并发工具类Exchanger

理解Java并发工具类Exchanger

Exchanger类是JDK5中的一个并发工具辅助类,这个类的主要作用是可以用于两个线程之间交换数据,以实际生活中的场景来讲,比如很多小区楼下都有自取的快递柜,如果快递员来了,就把东西直接放快递柜子就行了,然后我们直接从柜子中取走东西即可。这个柜子就起到了媒介的作用。也就说柜子可以是双向信息交换的一个媒介,比如我需要邮寄东西,我可以提前把东西放进柜子,然后快递来了把新买的东西放进去,然后把需要邮寄的东西拿走,这样就完成了交换,但大多数时候,我们可能买东西比较多,邮寄东西比较少。 Exchanger在遗传算法和管道设计比较有用。

下面先来看一个简单的交换例子:

public static void main(String[] args) {

        Exchanger exchanger=new Exchanger();
        ExchangeDemo1 demo1=new ExchangeDemo1();

        Thread t1=new Thread(new Worker(exchanger,"A"));
        Thread t2=new Thread(new Worker(exchanger,"B"));

        t1.start();
        t2.start();

    }


   static class  Worker implements Runnable{

        Exchanger exchanger;
        Object object;

        public Worker(Exchanger exchanger, Object object) {
            this.exchanger = exchanger;
            this.object = object;
        }

        public void run() {

            Object privious=this.object;
            try {
                this.object=this.exchanger.exchange(this.object);
                System.out.println(Thread.currentThread().getName()+" exchange "+privious+"  for "+this.object);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        }
    }

输出结果:

Thread-1 exchange B  for A
Thread-0 exchange A  for B

这种场景可以理解为,快递员把我们买的东西,放进了柜子里面,同时取走了我们要邮寄的快递,这样就完成了交换。

如果我们一直买东西,而不邮寄东西,那么Exchanger类其实就变成了简化版本的生产者和消费者的模型。快递员就是生产者,我们本身就是消费者,而柜子就成为了我们媒介容器,看下面的一个例子:

static Exchanger<DataBuffer<Integer>>  exchanger=new Exchanger<>();
   static DataBuffer<Integer> intialEmptyBuffer=new DataBuffer<>();
   static DataBuffer<Integer> intialFullBuffer=new DataBuffer<>();
   static AtomicInteger countDown=new AtomicInteger(5);


    static class ProducerWorker implements Runnable{
        //每次睡眠时长,单位是秒
        long sleep;

        public ProducerWorker(long sleep) {
            this.sleep = sleep;
        }

        @Override
        public void run() {
            DataBuffer currentBuffer=intialEmptyBuffer;
            while (currentBuffer!=null&&countDown.get()>0){
                try {
                    TimeUnit.SECONDS.sleep(sleep);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                currentBuffer.put(countDown.get());//每次放入数据
                if(currentBuffer.isFull()){
                    try {
                        System.out.println(Thread.currentThread().getName()+"  放入了快递"+countDown.get());
                        currentBuffer=exchanger.exchange(currentBuffer);//交换后得到null
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                countDown.getAndDecrement();

            }

        }


    }

    static class ConsumerWorker implements Runnable{

        //每次睡眠时长,单位是秒
        long sleep;

        public ConsumerWorker(long sleep) {
            this.sleep = sleep;
        }

        @Override
        public void run() {
            DataBuffer<Integer> currentBuffer=intialFullBuffer;

            while (currentBuffer!=null&&countDown.get()>0){
                try {
                    TimeUnit.SECONDS.sleep(sleep);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //如果为空就进行交换
                if(currentBuffer.isEmpyt()){
                    try {
                        currentBuffer=exchanger.exchange(currentBuffer); //交换数据
                        Integer value=currentBuffer.get();//不断的从
                        System.out.println(Thread.currentThread().getName()+"  拿走了快递"+value);
                        System.out.println();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }


            }

        }
    }


    public static void main(String[] args) {
        new Thread(new ProducerWorker(1),"快递员").start(); //启动生产者,每次睡眠1秒,启动顺序不分先后
        new Thread(new ConsumerWorker(3),"我   ").start(); //启动消费者,每次睡眠3秒,启动顺序不分先后


    }

    /***
     * 数据交换类
     * @param <T>
     */
   static class DataBuffer<T>{
        T data;
        public boolean isFull(){
            return data!=null;
        }

        public boolean isEmpyt(){
            return  data==null;
        }

        public T get(){
            T d=data;
            data=null;
            return d;
        }

        public void  put (T data){
            this.data=data;
        }



    }

输出结果:

快递员  放入了快递5
我     拿走了快递5

快递员  放入了快递4
我     拿走了快递4

快递员  放入了快递3
我     拿走了快递3

快递员  放入了快递2
我     拿走了快递2

快递员  放入了快递1
我     拿走了快递1

Exchanger类有两个交换方法:

exchange(V x) //不能超时的交换方法
exchange(V x, long timeout, TimeUnit unit)//可以设置超时的方法

Exchanger类在处理交换时,如果只有一方到达,而另一方没有到达,先到的这一方会等待另一方到达,只有两方都到达,完成交换才能进行下一次的交换,这有点类似约会的场景,但如果另一方一直不来,那么先到的一方可能永远不会停止,一直在等待,正因为如此Exchanger也提供了可超时的等待策略,在指定的时间内如果另一方仍然不能赴约,自己可中断。

底层原理分析:

Exchanger类底层并不是太复杂,关键的技术有:

(1)使用CAS自旋指令完成数据交换

(2)使用LockSupport的park方法使交换线程进入休眠等待, 使用LockSupport的unpark方法唤醒等待线程。

(3)此外还声明了一个Node对象用于存储交换数据。

本文主要介绍了Java里面并发工具类Exchanger,使用Exchanger可以轻松的给两个线程进行数据交换,这里需要注意的是如果超过两个线程操作交换,那么则很有可能出现问题,这里想象一下我们的快递柜的例子就很容易想明白,这一点需要注意,不要使用多个线程操作交换。

文中代码,如果需要可以到我的github上下载运行。

https://github.com/qindongliang/Java-Note

本文分享自微信公众号 - 我是攻城师(woshigcs)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-08-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 使用python3+opencv3实现的识别答题卡的例子(02)

    例子02是ayoungprogrammer博客上参考作者原版C++代码和思路,然后改造成python版本的,先在本地运行成功之后,然后加上自己的理解,给大多数核...

    我是攻城师
  • 20件程序员必须知道的事

    我是攻城师
  • 使用python3+opencv3实现的识别答题卡的例子(02)

    例子02是ayoungprogrammer博客上参考作者原版C++代码和思路,然后改造成python版本的,先在本地运行成功之后,然后加上自己的理解,给大多数核...

    我是攻城师
  • 编辑器之神VIM

    VIM当今世界,文本编辑器种类繁多选择一款优秀的编辑软件至关不仅仅提升工作效率,更能够节省大量的时间。而 VIM 与 emasc 成为了首选之一。

    DataScience
  • 在非Activity中使用startActivity:Calling startActivity() from outside of an Activity context requires the

    activity继承了context重载了startActivity方法,如果使用acitvity中的startActivity,不会有任何限制。

    一个会写诗的程序员
  • AI一分钟 | AI机器人竟混入大学哲学课堂并顺利结业,居然无人察觉!

    一分钟AI AI机器人混入大学哲学课堂,并顺利结业。此事竟无人察觉! 阿里云携手隆平高科、中信云,计划将阿里云ET推进到农业领域, 用于筛选育种,农作物预测和数...

    AI科技大本营
  • vim-command

    zhangheng
  • java_内部类

    在java的开发中,java开发人员建议,尽量少用内部类,要把内部类提出他所处的那个类,单独生成一个类。 直接来代码:

    Hongten
  • [C-C++]你所不知道的C和C++运行库

    周五晚,小雨,少见的未加班。无聊,遂准备写一篇博客,介绍一下C和C++运行库,只因发现工作几年的人对此一知半解的大有人在。 在使用VC构建项目时,经常会遇...

    祥知道
  • linux vim编辑器之常用指令

    以上这些是vim的一些常用指令,会了这些指令你会发现vim非常好使,这些指令还是有些规律的,复制yy,粘贴p,替换r,删除dd,再和数字相结合就会出现多行操作的...

    我是李超人

扫码关注云+社区

领取腾讯云代金券