面试官: Exchanger在项目中有使用过吗?说说看(源码剖析)

简介: 面试官: Exchanger在项目中有使用过吗?说说看(源码剖析)

前言

目前正在出一个Java多线程专题长期系列教程,从入门到进阶含源码解读, 篇幅会较多, 喜欢的话,给个关注❤️ ~


Java提供了一些非常好用的并发工具类,不需要我们重复造轮子,本节我们讲解Exchanger,一起来看下吧~


Exchanger

Exchanger类用于两个线程交换数据,支持泛型,下面我们通过例子感受一下:

public class ExchangerTest {
    public static void main(String[] args) throws InterruptedException {
        Exchanger<String> exchanger = new Exchanger<>();
        new Thread(() -> {
            try {
                System.out.println("1:"
                        + exchanger.exchange("这是来自线程2的数据"));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        Thread.sleep(1000);
        new Thread(() -> {
            try {
                System.out.println("2:"
                        + exchanger.exchange("这是来自线程1的数据"));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
复制代码


实际输出:

1:这是来自线程1的数据
2:这是来自线程2的数据
复制代码

可以看出两个线程的数据进行了交换


原理分析

先看下构造函数:

public Exchanger() {
    participant = new Participant();
}
static final class Participant extends ThreadLocal<Node> {
    public Node initialValue() { return new Node(); }
}
复制代码


Participant本质上是一个ThreadLocal,用来保存本地变量Node, 可以理解是交换的信息

static final class Node {
        int index;              // Arena index
        int bound;              // Last recorded value of Exchanger.bound
        int collides;           // Number of CAS failures at current bound
        int hash;               // Pseudo-random for spins
        Object item;            // This thread's current item
        volatile Object match;  // Item provided by releasing thread
        volatile Thread parked; // Set to this thread when parked, else null
    }
复制代码


下面重点看下exchange

public V exchange(V x) throws InterruptedException {
        Object v;
        Object item = (x == null) ? NULL_ITEM : x; // translate null args
        if ((arena != null ||
             (v = slotExchange(item, false, 0L)) == null) &&
            ((Thread.interrupted() || // disambiguates null return
              (v = arenaExchange(item, false, 0L)) == null)))
            throw new InterruptedException();
        return (v == NULL_ITEM) ? null : (V)v;
    }
复制代码


从源码看,数据的交换方式有两种,slotExchangearenaExchange

private volatile Node[] arena;
复制代码


可以看到arena是一个Node数组,arena为空就会进行slotExchange,可以称为单槽交换, arenaExchange可以称为多槽交换。exchange其实主要做了判断处理,在对应情况下使用不同的交换方式


slotExchange

private final Object slotExchange(Object item, boolean timed, long ns) {
        // 获取当前线程的交换节点
        Node p = participant.get();
        Thread t = Thread.currentThread();
        if (t.isInterrupted()) // preserve interrupt status so caller can recheck
            return null;
        // 常规自旋操作
        for (Node q;;) {
            // 如果 q不为空 说明q已经被占了
            if ((q = slot) != null) {
                if (U.compareAndSwapObject(this, SLOT, q, null)) {
                    // 获取交换值
                    Object v = q.item;
                    // 设置交换值
                    q.match = item;
                    Thread w = q.parked;
                    // 唤醒等待线程
                    if (w != null)
                        U.unpark(w);
                    // 交换成功返回结果    
                    return v;
                }
                // CPU核数数多于1个, 且bound为0时创建arena数组,并将bound设置为SEQ大小
                if (NCPU > 1 && bound == 0 &&
                    U.compareAndSwapInt(this, BOUND, 0, SEQ))
                    arena = new Node[(FULL + 2) << ASHIFT];
            }
            // 重定向到多槽交换 arenaExchange
            else if (arena != null)
                return null; // caller must reroute to arenaExchange
            else {
                // 占用slot
                p.item = item;
                if (U.compareAndSwapObject(this, SLOT, null, p))
                    break;
                // 失败设置为null 进入下一次自旋   
                p.item = null;
            }
        }
        // 下面的操作主要是匹配等待接收的线程
        int h = p.hash;
        long end = timed ? System.nanoTime() + ns : 0L;
        // 自旋的次数
        int spins = (NCPU > 1) ? SPINS : 1;
        Object v;
        // 匹配线程未到 就进入自旋
        while ((v = p.match) == null) {
            if (spins > 0) {
                // 优化操作
                h ^= h << 1; h ^= h >>> 3; h ^= h << 10;
                if (h == 0)
                    h = SPINS | (int)t.getId();
                else if (h < 0 && (--spins & ((SPINS >>> 1) - 1)) == 0)
                    Thread.yield();
            }
            // 说明匹配线程已找到 但是还未完全准备好
            else if (slot != p)
                spins = SPINS;
            // 自选时间过长 还未匹配到进入阻塞,常规操作
            else if (!t.isInterrupted() && arena == null &&
                     (!timed || (ns = end - System.nanoTime()) > 0L)) {
                U.putObject(t, BLOCKER, this);
                p.parked = t;
                if (slot == p)
                    U.park(false, ns);
                p.parked = null;
                U.putObject(t, BLOCKER, null);
            }
            // 超时 让出给其它线程
            else if (U.compareAndSwapObject(this, SLOT, p, null)) {
                v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
                break;
            }
        }
        U.putOrderedObject(p, MATCH, null);
        p.item = null;
        p.hash = h;
        return v;
    }
复制代码


arenaExchange

操作与slotExchange类似,但是它更复杂一点,需要通过index来命中,这里就不带大家看了,有兴趣的同学可以自己去看下


结束语

下节给大家讲下CountDownLatch ~

相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
4月前
|
JavaScript 前端开发 应用服务中间件
【Vue面试题三十】、vue项目本地开发完成后部署到服务器后报404是什么原因呢?
这篇文章分析了Vue项目在服务器部署后出现404错误的原因,主要是由于history路由模式下服务器缺少对单页应用的支持,并提供了通过修改nginx配置使用`try_files`指令重定向所有请求到`index.html`的解决方案。
【Vue面试题三十】、vue项目本地开发完成后部署到服务器后报404是什么原因呢?
|
4天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
4月前
|
JavaScript 前端开发
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
这篇文章主要讨论了axios的使用、原理以及源码分析。 文章中首先回顾了axios的基本用法,包括发送请求、请求拦截器和响应拦截器的使用,以及如何取消请求。接着,作者实现了一个简易版的axios,包括构造函数、请求方法、拦截器的实现等。最后,文章对axios的源码进行了分析,包括目录结构、核心文件axios.js的内容,以及axios实例化过程中的配置合并、拦截器的使用等。
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
|
24天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
44 2
|
4月前
|
JavaScript 前端开发
【Vue面试题二十七】、你了解axios的原理吗?有看过它的源码吗?
文章讨论了Vue项目目录结构的设计原则和实践,强调了项目结构清晰的重要性,提出了包括语义一致性、单一入口/出口、就近原则、公共文件的绝对路径引用等原则,并展示了单页面和多页面Vue项目的目录结构示例。
|
3月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
499 37
|
3月前
|
NoSQL Java Redis
面试官:项目中如何实现分布式锁?
面试官:项目中如何实现分布式锁?
104 6
面试官:项目中如何实现分布式锁?
|
2月前
|
JavaScript 前端开发
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
46 0
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
|
4月前
|
JavaScript 安全 前端开发
【Vue面试题二十九】、Vue项目中你是如何解决跨域的呢?
这篇文章介绍了Vue项目中解决跨域问题的方法,包括使用CORS设置HTTP头、通过Proxy代理服务器进行请求转发,以及在vue.config.js中配置代理对象的策略。
【Vue面试题二十九】、Vue项目中你是如何解决跨域的呢?

热门文章

最新文章