本文转载自微信公众号《Angela的博客》,作者Angela。转载本文请联系Angela博客公众号。大家好,我是Angela,这是第五期并发节目,完整提纲如下:面试官:你好,请先自我介绍一下。Angela:面试官你好,我是草丛三贱人,最强中单,火球拥有者,不燃烧的,Angela,这是我的简历,请看一下。面试官:听之前的面试官说你对Java并发把握的很好,我们来深入交流一下;Angela:好吧,我们多交流一下面试官:什么是线程安全?安吉拉:这个问题是第一次被问到,但是一个很好的问题。当多个线程访问一个类时,无论运行时环境使用何种调度方法或这些进程将如何交替执行,该类都会正确运行,并且不需要在调用代码中进行任何额外的协调或同步,那么这个类是线程安全的。面试官:线程安全的三大特性是什么?或者是什么导致线程不安全?Angela:【太老套了,能不能来点新的】线程不安全的原因:当前的操作可能不是原子的,执行的时候会被打断。其他线程有能力修改共享变量的值。同时,线程修改的值对其他线程来说不是立即可见的,因为线程有自己的执行空间。还有一点就是存在程序可能会乱序执行。单线程还好,但是多个线程同时执行的话,线程共享的数据就会乱了。上述问题概括了线程安全需要保证的三个特点:原子性提供互斥访问,同时只有一个线程在运行。可见性一个线程对主存的修改可以及时被其他线程看到。顺序是一致的。(你可能会想有没有不一致,是的,因为有指令重排序,为什么会有指令重排序,因为需要性能优化,比如把多次访问主存合并在一起执行计算,访问主存交替访问效率更高),重新排序过程不会影响单线程程序的执行,但会影响多线程并发执行的正确性。面试官:你用过java.util.concurrent.atomic包下的原子性相关的类吗?Angela:是的,Java提供了很多AtomicXXX相关的原子类,如下图:面试官:是的能举个例子说明一下用法吗?Angela:比如有并发和计数的场景。以netty为例。它的线程池工厂类如下:nextId是AtomicInteger类型。每次创建线程并命名线程,代码如下:{if(t.isDaemon()){if(!this.daemon){t.setDaemon(false);}}elseif(this.daemon){t.setDaemon(true);}if(t.getPriority()!=this.priority){t.setPriority(this.priority);}}catch(Exceptionvar4){}returnt;}通过incrementAndGet原子+1。面试官:如果不使用AtomicInteger,使用普通int会有什么后果?Angela:首先,我们知道+1操作不是原子的,它可以分为几条指令:fetch指令,压入操作数栈,执行+1操作,赋值。关于命令,扔个蓝。让我们编译一段Java代码看看。代码和字节码指令为:publicstaticintadd(inta,intb){intc=0;c=a+b;returnc;}指令,也有相应的操作解释,如下:publicstaticintadd(int,int);代码:0:iconst_0//初始化常量0压入操作数栈顶1:istore_2//弹出操作数栈顶元素并保存到局部变量表的第二个位置2:iload_0//复制变量a的值并入栈3:iload_1//复制变量b的值入栈4:iadd//执行加法运算,加法结果入栈5:istore_2//弹出操作数栈顶元素,保存到局部变量表第二个位置6:iload_2//复制局部变量表第二个位置的值入栈7:ireturn//出栈,写这么多返回结果让大家明白a+=1这个操作不是原子的,是由多条指令组成,真的很不容易,点个赞吧,好心的蓝buff面试官:能说说Atomic的实现原理吗?Angela:【该滚了,去Angela最喜欢的源码链接】/***Atomicallyincrementsbyonethecurrentvalue.**@returntheupdatedvalue*/publicfinalintincrementAndGet(){returnunsafe.getAndAddInt(this,valueOffset,1)+1;}代码很简而言之,评论只是一句话,并且原子地增加了当前值。继续探索:publicfinalintgetAndAddInt(Objectvar1,longvar2,intvar4){intvar5;do{var5=this.getIntVolatile(var1,var2);}while(!this.compareAndSwapInt(var1,var2,var5,var5+var4));returnvar5;}输入参数是三个值:var1、var2、var4,我们先来看看这三个值是什么?val1:this,也就是AtomicInteger对象nextIdVal2:valueOffset看代码,我又画了一张图,我们知道一个对象的存储空间是由对象头和成员变量组成的,那么valueOffset就是成员变量值在原子整数对象。初学者可能会问,函数放在哪里?函数放在方法区,因为它属于类,不属于私有对象。privatestaticfinalUnsafeunsafe=Unsafe.getUnsafe();privatestaticfinallongvalueOffset;static{try{valueOffset=unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));}catch(Exceptionex){thrownewError(ex);}}privatevolatileintvalue;Val4:1下面开始详细解释,代码如下:compareAndSwapInt方法:比较val1(AtomicInteger对象)的var2(valueOffset偏移量)的值是否等于var5(原值),如果相等,则将值更新为var5(originalvalue))+val4(1)//val1:nextIdval2:valueOffsetval4:1publicfinalintgetAndAddInt(Objectvar1,longvar2,intvar4){intvar5;//临时变量do{var5=this.getIntVolatile(var1,var2);//这是一个native方法,获取value的值//比较val1(AtomicInteger对象)的var2(valueOffset偏移量)和var5(原始值)的值是否相等。如果它们相等,则将值更新为var5(原始值)+val4(1)}while(!this.compareAndSwapInt(var1,var2,var5,var5+var4));returnvar5;}publicnativeintgetIntVolatile(Objectvar1,longvar2);compareAndSwapInt是Java中非常重要和知名的CAS操作,比较和交换,并发底层框架用到的地方很多。compareAndSwapInt将返回CAS支持状态。如果执行失败,就会循环执行,直到成功。失败的原因一般是其他线程同时修改了这个变量的值,所以比较不相等,下次执行会获取最新的值,执行CAS。....哎,打字好累,先写到这里,我要去吃自助餐了,明天再写能见度和订单。
