【牡】丹江二手房:Netty为什么不直接用AtomicXXX,‘而要用’AtomicXXXFieldUpdater去更新变量呢?

admin 2个月前 (04-07) 科技 4 0

『更(多)手艺分享可』关注我

前言

若是仔细阅读过Netty 〖<【『的』】>〗{线程}调剂模子[〖<【『的』】>〗(源码),“或”【者】NIO {线程}<《工具》>及[其{线程}池〖<【『的』】>〗〖建〗立(源码),那么肯定会遇到<类>似“AtomicIntegerFieldUpdater”【〖<【『的』】>〗身影】,【不禁想知道】——Netty(为何)不直接【使】用原子<类><包装>通俗〖<【『的』】>〗好比计数〖<【『的』】>〗“「变量」”?

下面带着这<个>疑问,“深入”Netty“ 以[及”JDK<(源码)去窥探一二>,顺便学习先进〖<【『的』】>〗用法。<原文>:​Netty“为什么不”直接用AtomicXXX,而要用AtomicXXXFieldUpdater去更新“「变量」”呢?

JDK〖<【『的』】>〗Atomic(原子操)作<类>‘实’现机制

《《【在】》》JDK「里」,Atomic 开头〖<【『的』】>〗(原子操)作<类>有许(多),「涉及到」 Java 「常用〖<【『的』】>〗数」字<类>型〖<【『的』】>〗, 基本[都有响应〖<【『的』】>〗 Atomic (原子操)作<类>,〖如下〗图所示:

(原子操)作<类>都是{线程}平『安〖<【『的』】>〗』,编码时可 以[放心大胆〖<【『的』】>〗【使】用。『下面 以[其{中}常用〖<【『的』】>〗』AtomicInteger原子<类>为例子,〖剖析这些原子<类>〗〖<【『的』】>〗底层‘实’现机制,‘辅助明’白Netty为何没有直接【使】用原子<类>。详细【使】用〖<【『的』】>〗demo【就不写了】,{想必}Javaer都若干用过“或”【者】见过,(直接(看))AtomicInteger<类>焦点(源码):

 1 private volatile int value; // 《简》化了部门非焦点(源码)
 2  3 // 初始化,《简》化了部门非焦点(源码)
 4 public AtomicInteger(int initialValue) {
 5     value = initialValue;
 6 }
 7 public final int get() {
 8     return value;
 9 }
10 // 自增 1,{并返回自增}之前〖<【『的』】>〗值    
11 public final int getAndIncrement() {
12     return unsafe.getAndAddInt(this, valueOffset, 1);
13 }
14 // 自减 1,{并返回自增}之前〖<【『的』】>〗值    
15 public final int getAndDecrement() {
16     return unsafe.getAndAddInt(this, valueOffset, -1);
17 }

{ 以[上},AtomicInteger〖可 以[「对」〗int<类>型〖<【『的』】>〗值举行{线程}平『安〖<【『的』】>〗』自增“或”【者】自减{等}{操作}。 从(源码){中}[可 以[(看)到,{线程}平『安〖<【『的』】>〗』{操作}方式底层都是【使】用unsafe{方式实现},这『是一』<个>JDK〖<【『的』】>〗邪术<类>,能实现许(多)贴近底层〖<【『的』】>〗(功效),{ 以[是并不}是Java〖<【『的』】>〗实现〖<【『的』】>〗,“然则能保证底层”〖<【『的』】>〗这些getAndXXX{操作}都是{线程}平『安〖<【『的』】>〗』,「关于」unsafe详细〖<【『的』】>〗用法和细节,‘可’ 以[参考这篇文章Java邪术<类>:Unsafe应用剖析(https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html,可能无法直接打开,《复制黏贴到》浏览【(器)】《即》可)

(题外话):若是AtomicXXX〖<【『的』】>〗<《工具》>是自定义<类>型呢?不要慌,Java 也提供了自定义<类>型〖<【『的』】>〗(原子操)作<类>——AtomicReference,“它操”作〖<【『的』】>〗<《工具》>是<个>泛型<《工具》>,故能支持自定义〖<【『的』】>〗<类>型,其底层是没有自增方式〖<【『的』】>〗,{操作}〖<【『的』】>〗方式可 以[作为函数入参通报,(源码)〖如下〗:

 1 // 「对」 x 《执行》 accumulatorFunction {操作}
 2 // accumulatorFunction 是<个>函数,可 以[自定义想做〖<【『的』】>〗事情
 3 // 「返回老值」
 4 public final V getAndAccumulate(V x,
 5                                 BinaryOperator<V> accumulatorFunction) {
 6     // prev 是老值,next 《是新值》
 7     V prev, next;
 8     // {自旋} + CAS “保证一定可 以[替换老值”
 9     do {
10         prev = get();
11         // 《执行》自定义{操作}
12         next = accumulatorFunction.apply(prev, x);
13     } while (!compareAndSet(prev, next));
14     return prev;
15 }

 

JDK〖<【『的』】>〗AtomicXXXFieldUpdater 原【子更新】【(器)】及其优[势

《《【在】》》Java5{中},JDK就最先提供原子<类>了,固然也包罗原子〖<【『的』】>〗更新【(器)】——《即》后缀为FieldUpdater〖<【『的』】>〗<类>,〖如下〗Integer、Long,另有一<个>自定义<类>型〖<【『的』】>〗原【子更新】【(器)】,共三<类>:

【牡】丹江二手房:Netty为什么不直接用AtomicXXX,‘而要用’AtomicXXXFieldUpdater去更新变量呢? 第1张

这些原【子更新】【(器)】常见于种种优异〖<【『的』】>〗开源框架「里」,而很少被通俗〖<【『的』】>〗营业程序员直接【使】用,实《《【在】》》这些原【子更新】【(器)】也可 以[被用来<包装>共享“「变量」”(〖必须是〗volatile({修饰})〖<【『的』】>〗<《工具》>‘属’ 性[),来为这些共享“「变量」”实现原【子更新】〖<【『的』】>〗(功效)。这些被<包装>〖<【『的』】>〗共享“「变量」”可 以[是原生<类>型,也可 以[是引用<类>型,那么不禁要问:已经有了原子<类>,{为啥还分外提供一套}原【子更新】【(器)】呢? 

简朴〖<【『的』】>〗说有两<个>缘故原‘由’, 以[int“「变量」”为例,「基于」AtomicIntegerFieldUpdater实现〖<【『的』】>〗原子计数【(器)】,比单纯〖<【『的』】>〗直接用AtomicInteger<包装>int“「变量」”〖<【『的』】>〗花销要小,‘由’于前【者】只需要一<个>全局〖<【『的』】>〗静态“「变量」”AtomicIntegerFieldUpdater《即》可<包装>volatile({修饰})〖<【『的』】>〗非静态共享“「变量」”,《然后配合》CAS<就能实现原子更>新,而这样做,使得后续同一<个><类>〖<【『的』】>〗每<个><《工具》>{中}只需要共享这<个>静态〖<【『的』】>〗原【子更新】【(器)】《即》可为<《工具》>计数【(器)】实现原【子更新】,而原子<类>是为同一<个><类>〖<【『的』】>〗每<个><《工具》>{中}都建立了一<个>计数【(器)】 + AtomicInteger<《工具》>,这种开销显然就「对」照大了。

下面(看)一<个>JDK【使】用原【子更新】【(器)】〖<【『的』】>〗例子,《即》JDK〖<【『的』】>〗BufferedInputStream,〖如下〗是(源码)〖<【『的』】>〗片断节选:

 1 public class BufferedInputStream extends FilterInputStream {
 2     private static int DEFAULT_BUFFER_SIZE = 8192;
 3     private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
 4     protected volatile byte buf[];
 5     /**
 6      * Atomic updater to provide compareAndSet for buf. This is
 7      * necessary because closes can be asynchronous. We use nullness
 8      * of buf[] as primary indicator that this stream is closed. (The
 9      * "in" field is also nulled out on close.)
10      */
11     private static final
12         AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
13         AtomicReferenceFieldUpdater.newUpdater
14         (BufferedInputStream.class,  byte[].class, "buf");

可 以[(看)出,每<个>BufferedInputStream<《工具》>都包罗了一<个>buf‘属’ 性[,该‘属’ 性[是<《工具》>‘属’ 性[,且被volition({修饰}),并被原【子更新】【(器)】AtomicReferenceFieldUpdater<包装>,「注重」这<个>引用<类>型〖<【『的』】>〗原【子更新】【(器)】是静态<类>型〖<【『的』】>〗,这意味着岂论用户建立(了若干<个>)BufferedInputStream<《工具》>,《《【在】》》全局都只有这一<个>原【子更新】【(器)】被建立,这「里」之 以[是不用原子<类>AtomicReference直接<包装>buf‘属’ 性[,(是‘由’于)buf『是一』<个>byte“数组”,通常会『是一』<个>「对」照大〖<【『的』】>〗<《工具》>,若是用原子<类>直接<包装>,那么后续每<个>BufferedInputStream<《工具》>都市分外建立一<个>原子<类>〖<【『的』】>〗<《工具》>,会消耗更(多)〖<【『的』】>〗内存, 肩负较重[,因此JDK直接【使】用了原【子更新】【(器)】取代了原子<类>,Netty(源码){中}〖<【『的』】>〗<类>似【使】用也是如出一辙。

另外一<个>主要缘故原‘由’是【使】用原【子更新】【(器)】,不会损坏共享“「变量」”原来〖<【『的』】>〗结构,回到上述JDK〖<【『的』】>〗例子,buf「对」外仍然可 以[保留buf<《工具》>〖<【『的』】>〗原生“数组”‘属’ 性[,只不过(多)了一<个>volatile({修饰}),外界可 以[直接获取到这<个>byte“数组”实现一些营业逻辑,而且《《【在】》》需要〖<【『的』】>〗时刻也能【使】用原【子更新】【(器)】实现原【子更新】,〖可谓两头不延迟〗,(天真 性[较强)!

另有一<个>可能〖<【『的』】>〗疑问点 需要明白[,《即》原【子更新】【(器)】虽然是静态〖<【『的』】>〗,然则其({修饰})〖<【『的』】>〗共享“「变量」”确仍然是<类>〖<【『的』】>〗<《工具》>‘属’ 性[,《即》每<个><类>〖<【『的』】>〗<《工具》>仍然是只包罗自己那独一份〖<【『的』】>〗共享“「变量」”,不会‘由’于原【子更新】【(器)】是静态〖<【『的』】>〗,而受到任何影响。

《结论》:实现原【子更新】最佳〖<【『的』】>〗方式是直接【使】用原【子更新】【(器)】实现。(一方面是更)节约内存, 另一方[面是不损坏原始〖<【『的』】>〗共享“「变量」”,【使】用起来更天真。‘固然’若是是时延要求没有那么高〖<【『的』】>〗场景,那么就不需要这么严苛,直接【使】用原子<类>就OK,究竟原子<类>〖<【『的』】>〗编码简朴,<开>发效率高,{不易失足}。

《品》Netty(源码),学习原【子更新】〖<【『的』】>〗最佳实现方式

前面说了许(多)理论,下面(看)一段Netty(源码),(看)Netty是若何优雅〖<【『的』】>〗【使】用原【子更新】【(器)】〖<【『的』】>〗。{下面是}Netty〖<【『的』】>〗NIO{线程}实现<类>——SingleThreadEventExecutor〖<【『的』】>〗部门(源码),省略了许(多)和本次剖析无关〖<【『的』】>〗代码:

 1 /**
 2  * Abstract base class for {@link OrderedEventExecutor}'s that execute all its submitted tasks in a single thread.
 3  */
 4 public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
 5     private static final int ST_NOT_STARTED = 1;
 6     private static final int ST_STARTED = 2;
 7     private static final int ST_SHUTTING_DOWN = 3;
 8     private static final int ST_SHUTDOWN = 4;
 9     private static final int ST_TERMINATED = 5;
10 11     private static final AtomicIntegerFieldUpdater<SingleThreadEventExecutor> STATE_UPDATER;
12     private static final AtomicReferenceFieldUpdater<SingleThreadEventExecutor, ThreadProperties> PROPERTIES_UPDATER;
13     private static final long SCHEDULE_PURGE_INTERVAL = TimeUnit.SECONDS.toNanos(1);
14 15     static {
16         AtomicIntegerFieldUpdater<SingleThreadEventExecutor> updater =
17                 PlatformDependent.newAtomicIntegerFieldUpdater(SingleThreadEventExecutor.class, "state");
18         if (updater == null) {
19             updater = AtomicIntegerFieldUpdater.newUpdater(SingleThreadEventExecutor.class, "state");
20         }
21         STATE_UPDATER = updater;
22     }
23 24     private final Queue<Runnable> taskQueue;
25     private final Executor executor;
26     private volatile Thread thread;
27     private volatile int state = ST_NOT_STARTED;

{ 以[上}截取了一小片断,并删除了注释,可 以[清晰〖<【『的』】>〗(看)到Netty封装了JDK〖<【『的』】>〗Thread<《工具》>,一些标识{线程}状态〖<【『的』】>〗静态常量,{线程}《执行》【(器)】,‘异步义务行’列,“ 以[及”标识{线程}状态〖<【『的』】>〗‘属’ 性[state{等},其{中}重点关注state,这<个>‘属’ 性[是通俗〖<【『的』】>〗共享“「变量」”,‘由’volatile({修饰}),而且被静态〖<【『的』】>〗原【子更新】【(器)】STATE_UPDATER<包装>。

下面(看)NIO{线程}〖<【『的』】>〗<启动>(源码):

写给小白(看)〖<【『的』】>〗入门级 Java 基[本语法,“强烈推荐”

 1     /**
 2      * NioEventLoop‘线’程<启动>方式, 这「里」会判断本NIO{线程}是否已经<启动>
 3      */
 4     private void startThread() {
 5         if (STATE_UPDATER.get(this) == ST_NOT_STARTED) {
 6             if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
 7                 doStartThread();
 8             }
 9         }
10     }

注释写到了[,<启动>NIO「{线程}之前会做一」次是否已经<启动>〖<【『的』】>〗判断,制止重复<启动>,这<个>判断逻辑就是前面提到〖<【『的』】>〗原【子更新】【(器)】实现〖<【『的』】>〗,当本NIO{线程}实例没有<启动>时,会做一次CAS<盘>算,「注重」CAS「对」应{操作}系统〖<【『的』】>〗一<个>指令,是原子{操作},若是是(多)<个>外部{线程}《《【在】》》<启动>NIO{线程},那么同时只有一<个>外部{线程}能<启动>乐成一次,后续〖<【『的』】>〗{线程}不会重复<启动>这<个>NIO{线程}。保证《《【在】》》NIO{线程}〖<【『的』】>〗一次生命周期内,外部{线程}只能挪用一次doStartThread()方式,这样可 以[实现无锁更新,且没有{自旋}, 性[能较好,这「里」之 以[是不需要{自旋},(是‘由’于)<启动>{线程}就应该是一锤子买卖,<启动>不乐成,就说明是已经<启动>了,<直接跳过>, 无需重试[。

 

《《【在】》》(看)一<个>{自旋}〖<【『的』】>〗用法:

【牡】丹江二手房:Netty为什么不直接用AtomicXXX,‘而要用’AtomicXXXFieldUpdater去更新变量呢? 第2张

《《【在】》》NIO{线程}被优雅(「也可能异常」)『关闭时』,会《《【在】》》死循环「里」,连系CAS算法,原【子更新】当前NIO{线程}〖<【『的』】>〗状态为关闭{中}。。。这「里」有两<个>「注重」事项:

1、和{线程}平『安〖<【『的』】>〗』<启动>NIO{线程}〖<【『的』】>〗逻辑不一样,更新{线程}状态必须乐成,不是一锤子买卖, 以[是需要{自旋}重试,『直到』CAS{操作}乐成

2、需要【使】用局部“「变量」”缓存外部〖<【『的』】>〗共享“「变量」”〖<【『的』】>〗旧值,保证CAS{操作}《执行》时代该共享“「变量」”〖<【『的』】>〗旧值不被外部{线程} 修改[

3、同样〖<【『的』】>〗,每次《执行》CAS{操作}之前,必须判断一次旧值,只有相符更新条件,才真〖<【『的』】>〗《执行》CAS{操作},否则说明已经被外界{线程}更新乐成,无需重复{操作}, 以[提升 性[能。

 

Netty「这样做也侧面反映」Nerty〖<【『的』】>〗(源码)确实很优异,平时〖<【『的』】>〗营业开发,若是有<类>似场景,那么可 以[参考学习这两<类>用法。

 

总结【使】用原【子更新】【(器)】〖<【『的』】>〗「注重」事项:

1、<包装>〖<【『的』】>〗〖必须是〗被volatile({修饰})〖<【『的』】>〗共享“「变量」”

2、<包装>〖<【『的』】>〗〖必须是〗非静态〖<【『的』】>〗共享“「变量」”

3、{必须搭配}CAS〖<【『的』】>〗套路自行实现「对」照并交流〖<【『的』】>〗逻辑

4、自行实现「对」照并交流〖<【『的』】>〗逻辑时需要「注重」:若是是非一锤子买卖〖<【『的』】>〗原【子更新】{操作},那么必须用局部“「变量」”缓存外部〖<【『的』】>〗共享“「变量」”〖<【『的』】>〗旧值,详细缘故原‘由’可 以[参考:Netty〖<【『的』】>〗{线程}调剂模子剖析(10)《(多){线程}环境下,实例“「变量」”转为局部“「变量」”〖<【『的』】>〗程序设计技巧》,且放《《【在】》》一<个>循环「里」{操作}, 以[保证最终一致 性[。

 

<后记>

dashuai〖<【『的』】>〗博客是终身学习践行【者】,大厂程序员,{且}专注于工作经验、学习条记〖<【『的』】>〗分享和一样「平常吐槽」,【包】罗但不限于互联网〖行业〗,“附带分享”一些PDF『电子书』,资料,(协助内推),【迎接拍砖】!

 

【牡】丹江二手房:Netty为什么不直接用AtomicXXX,‘而要用’AtomicXXXFieldUpdater去更新变量呢? 第3张

,

阳光诚信《《【在】》》线官网

阳光诚信《《【在】》》线官网(原诚信《《【在】》》线官网)现已开放阳光《《【在】》》线手机『版』、阳光《《【在】》》线电脑客户端下载。阳光《《【在】》》线娱乐戏公平、{公开}、公正,用实力赢取信誉。

网友评论

  • (*)

最新评论

文章归档

站点信息

  • 文章总数:444
  • 页面总数:0
  • 分类总数:8
  • 标签总数:980
  • 评论总数:105
  • 浏览总数:2752