此次不讲原理了,主要是一些应用方面的知识,和上几回的JUC并发编程的知识点更容易理解.html
知识回顾:java
上次主要说了Semaphore信号量的使用,就是一个票据的使用,咱们举例了看3D电影拿3D眼镜的例子,还说了内部的抢3D眼镜,和后续排队的源码解析,还有CountDownLatch的使用,咱们是用王者农药来举例的,CyclicBarrier栅栏的使用和CountDownLatch几乎是一致的,Executors用的不多我只是简单的写了一个小示例。上次遗漏了一个CountDownLatch和CyclicBarrier的区别。mysql
CountDownLatch和CyclicBarrier的区别:面试
区别的根本在于有无主线程参与,这样就很容易区别了,CountDownLatch有主线程,CyclicBarrier没有主线程,咱们来举两个例子,CountDownLatch主线程是游戏程序,而咱们开启的10个线程是玩家加载程序,咱们的游戏主程序会等待10个玩家加载完成,线程可能结束,而后主程序游戏程序继续运行。CyclicBarrier没有主线程,可是具备重复性,再举一个例子,年会了,公司团建活动,三人跨栅栏,要求是必须三人所有跨过栅栏之后才能够继续跨下一个栅栏。sql
CountDownLatch和CyclicBarrier都有让多个线程等待同步而后再开始下一步动做的意思,可是CountDownLatch的下一步的动做实施者是主线程,具备不可重复性;而CyclicBarrier的下一步动做实施者仍是“其余线程”自己,具备往复屡次实施动做的特色。编程
本次新知识bootstrap
什么是原子操做?数组
原子(atom)本意是“不能被进一步分割的小粒子”,而原子操做(atomic operation)意为”不可被中断的一个或一系列操做” 。就像是咱们的mysql里面的提到的ACID,原子性,也是不可分割的操做,最小的单位。缓存
咱们之前说的MESI,说到了缓存行,也是上锁的最小单位,原子变动就不作过多解释了,就是把一个变量的值改成另一个值。比较与交换咱们在Semaphore源码里也接触过了,也就是CAS操做须要输入两个数值,一个旧值,一个新值,在将要变动为新值以前,会比较旧值是否已经改变,若是改变了修改失败,若是没有改变,修改为功。安全
Atomic的使用
在Atomic包内一共有12个类,四种原子更新方式,原子更新基本类型,原子更新数组,原子更新字段,Atomic包里的类基本都是基于Unsafe实现的包装类。
基本类型:AtomicInteger,AtomicBoolean,AtomicLong。
引用类型:AtomicReference、AtomicReference的ABA实例、AtomicStampedReference、AtomicMarkableReference。
数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray。
属性原子修改器:AtomicLongFieldUpdater、AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater。
来一个简单的实例,就是开启10个线程而后作一个自加的操做,仍是很好理解的。
public class AtomicIntegerTest { static AtomicInteger atomicInteger = new AtomicInteger(); public static void main(String[] args) { for (int i = 0; i<10; i++){ new Thread(new Runnable() { @Override public void run() { atomicInteger.incrementAndGet(); } }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("自加10次数值:--->"+atomicInteger.get()); } }
ABA问题,ABA这样更能好理解一些,一眼就能够看出来A已经不是原来的A了,虽然值同样,可是里面的属性变成了红色的,先来看一段代码。
package com.xiaocai.main; import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest { static AtomicInteger atomicInteger = new AtomicInteger(1); public static void main(String[] args) { Thread main = new Thread(new Runnable() { @Override public void run() { int a = atomicInteger.get(); System.out.println("操做线程"+Thread.currentThread().getName()+",修改前操做数值:"+a); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } boolean isCasSuccess = atomicInteger.compareAndSet(a,2); if(isCasSuccess){ System.out.println("操做线程"+Thread.currentThread().getName()+",Cas修改后操做数值:"+atomicInteger.get()); }else{ System.out.println("CAS修改失败"); } } },"主线程"); Thread other = new Thread(new Runnable() { @Override public void run() { atomicInteger.incrementAndGet();// 1+1 = 2; System.out.println("操做线程"+Thread.currentThread().getName()+",自加后值:"+atomicInteger.get()); atomicInteger.decrementAndGet();// atomic-1 = 2-1; System.out.println("操做线程"+Thread.currentThread().getName()+",自减后值:"+atomicInteger.get()); } },"干扰线程"); main.start(); other.start(); } }
咱们能够看到主线程设置一个初始值为1,而后进行等待,干扰线程将1修改成2,又将2修改回1,而后主线程继续操做1修改成2,这一系列的动做,主线程并无感知到1已经不是原来的1了。
这样的操做实际上是很危险的,咱们假象,小王是银行的职员,他能够操做每一个帐户的金额(假设啊,具体能不能我也不知道),他将撕葱的帐户转走了1000万用于炒股,股市大涨,小王赚了2000万,还了1千万,本身还剩下2千万,过几天撕葱来查看本身帐户钱并无少,可是钱已经不是那个钱了,有人动过的。因此ABA问题咱们仍是要想办法来处理的。咱们每次转帐汇款的操做都是有一个流水号(回执单)的,也就是每次咱们加一个版本号码就能够了,咱们来改一下代码。
public class AtomicIntegerTest { static AtomicStampedReference atomicInteger = new AtomicStampedReference<>(1,0); public static void main(String[] args) { Thread main = new Thread(new Runnable() { @Override public void run() { int stamp = atomicInteger.getStamp(); //获取当前标识别 System.out.println("操做线程"+Thread.currentThread().getName()+"修改前的版本号为:"+stamp+",修改前操做数值:"+atomicInteger.getReference()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } boolean isCasSuccess = atomicInteger.compareAndSet(1,2,stamp,stamp +1); //此时expectedReference未发生改变,可是stamp已经被修改了,因此CAS失败 if(isCasSuccess){ System.out.println("操做线程"+Thread.currentThread().getName()+",Cas修改后操做数值:"+atomicInteger.getReference()); }else{ System.out.println("CAS修改失败,当前版本为:"+atomicInteger.getStamp()); } } },"主线程"); Thread other = new Thread(new Runnable() { @Override public void run() { int stamp = atomicInteger.getStamp(); atomicInteger.compareAndSet(1,2,atomicInteger.getStamp(),atomicInteger.getStamp()+1); System.out.println("操做线程"+Thread.currentThread().getName()+",版本号为:"+stamp+",修改后的版本号为:"+atomicInteger.getStamp()+",自加后值:"+atomicInteger.getReference()); int newStamp = atomicInteger.getStamp(); atomicInteger.compareAndSet(2,1,atomicInteger.getStamp(),atomicInteger.getStamp()+1); System.out.println("操做线程"+Thread.currentThread().getName()+",版本号为:"+newStamp+",修改后的版本号为:"+atomicInteger.getStamp()+",自减后值:"+atomicInteger.getReference()); } },"干扰线程"); main.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } other.start(); } }
咱们先初始一个主线程,而且设置版本号为0。而后干扰线程进行修改,每次修改时版本号加一,干扰线程结束,而主线程想继续修改时,发现版本不匹配,修改失败。
其他Atomic的类使用都是大同小异的,能够自行尝试一遍。
Unsafe魔术类的使用
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操做的 方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提高Java运行效率、增 强Java语言底层资源操做能力方面起到了很大的做用。但因为Unsafe类使Java语言拥有了 相似C语言指针同样操做内存空间的能力,这无疑也增长了程序发生相关指针问题的风险。 在程序中过分、不正确使用Unsafe类会使得程序出错的几率变大,使得Java这种安全的语 言变得再也不“安全”,所以对Unsafe的使用必定要慎重。
在过去的几篇博客里也说到了Unsafe这个类,咱们须要经过反射来使用它,好比读写屏障、加锁解锁,线程的挂起操做等等。
如何获取Unsafe实例?
一、从getUnsafe方法的使用限制条件出发,经过Java命令行命令-Xbootclasspath/a把 调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被 引导类加载器加载,从而经过Unsafe.getUnsafe方法安全的获取Unsafe实例。 java Xbootclasspath/a:${path} // 其中path为调用Unsafe相关方法的类所在jar包路径。
二、经过反射获取单例对象theUnsafe。
public static Unsafe reflectGetUnsafe() { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe) field.get(null); } catch (Exception e) { e.printStackTrace(); } return null; }
总结:
此次博客彻底没有代码的解析阅读,都是一些简单的使用,咱们开始时候说到了什么是原子操做,接下来咱们说了Atomic类的基本使用,再就是什么是ABA问题,如何用Atomic来解决ABA问题,再就是咱们的魔术类Unsafe类,越过虚拟机直接来操做咱们的系统的一些操做(不是超级熟练别玩这个,玩坏了很差修复)。但愿对你们在工做面试中能有一些帮助。
最进弄了一个公众号,小菜技术,欢迎你们的加入