线程安全性:java
当多个线程访问某个类时,无论运行时环境采用何种调度方式或者这些进程将如何交替执行,而且在主调代码中不须要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。编程
线程安全体如今三个方面:数组
使用AtomicInteger保证该变量操做的原子性安全
public class CountExample2 { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; public static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); add(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("count:{}", count.get()); } private static void add() { count.incrementAndGet(); //至关于++x; // count.getAndIncrement(); //至关于x++ } }
原理:AtomicInteger的incrementAndGet()方法里边用到了一个unsafe的类多线程
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
继续深刻点进去看getAndAddInt的实现:并发
// public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
这里最重要的一个方法是:compareAndSwapInt(),这是java底层的一个方法,它不是经过java实现的:app
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
参数解释:编辑器
Object var1:所操做的对象,好比本次案例中,这个Obect是AtomicInteger count;高并发
long var2:这个对象当前的值;性能
int var4:当前对象要增长的值,好比本次案例中作+1操做,那么var4就是1;
int var5:调用底层获得的一个值,若是没有其余线程过来操做,这个值应该是等于var2
getAndAddInt()方法中compareAndSwapInt()方法执行解释:若是对于var1这个对象,若是var2与从底层获取的值var5是相同的,那么就执行var5 + var4;
进一步解释:count的当前值,是当前线程中的值,属于线程中的工做内存中的值,而底层获取的值是主存中值,只有当工做内存中的值和主存中的值是一致的时候,才能够修改。
AtomicLong、LongAdder
在上边的例子中,把AtomicInteger 替换成AtomicLong,整个方法依然是线程安全的。
第二种方式是使用LongAdder:
public class AtomicExample3 { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; public static LongAdder count = new LongAdder(); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); add(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("count:{}", count); } private static void add() { count.increment(); } }
AtomicLong和LongAdder的对比:
AtomicLong:该类底层实现是在一个死循环内,不断的尝试修改目标值,直到修改为功,在竞争不激烈的状况下,修改为功几率很大,在竞争激烈状况下修改失败的几率较大,这种状况下会有损性能。
LongAdder:因为Long、Double类型的值JVM容许将他们64位的读写操做分拆成32位的读写操做,根据此原理 LongAdder将操做的数值分拆成数组,而后最终获得的是数组的加和,经过分拆均衡操做压力,所以其性能相对较好
使用场景的选择:在高并发计数的情景下优先使用LongAdder,其余状况使用AtomicLong
**AtomicBoolean **
底层实现的方法是:
public final boolean compareAndSet(boolean expect, boolean update) { int e = expect ? 1 : 0; int u = update ? 1 : 0; return unsafe.compareAndSwapInt(this, valueOffset, e, u); }
这个方法是指某个代码块逻辑值执行一次。
使用案例(该案例演示了某一段代码在多线程状况下,只执行了一次):
public class AtomicExample6 { private static AtomicBoolean isHappened = new AtomicBoolean(false); // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); test(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("isHappened:{}", isHappened.get()); } private static void test() { if (isHappened.compareAndSet(false, true)) { log.info("execute"); } } }
AtomicReference、AtomicReferenceFieldUpdater
AtomicReference使用示例:
public class AtomicExample4 { private static AtomicReference<Integer> count = new AtomicReference<>(0); public static void main(String[] args) { count.compareAndSet(0, 2); // 2 count.compareAndSet(0, 1); // no count.compareAndSet(1, 3); // no count.compareAndSet(2, 4); // 4 count.compareAndSet(3, 5); // no log.info("count:{}", count.get()); //4 } }
AtomicReferenceFieldUpdater使用示例:
public class AtomicExample5 { private static AtomicIntegerFieldUpdater<AtomicExample5> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count"); @Getter public volatile int count = 100; public static void main(String[] args) { AtomicExample5 example5 = new AtomicExample5(); if (updater.compareAndSet(example5, 100, 120)) { log.info("update success 1, {}", example5.getCount()); } if (updater.compareAndSet(example5, 100, 120)) { log.info("update success 2, {}", example5.getCount()); } else { log.info("update failed, {}", example5.getCount()); } } }
AtomicStampedReference:解决CAS的ABA问题
ABA问题:在CAS操做的时候,其余线程将变量的值A改为了B,可是又改回了A,本线程使用指望值A与当前变量进行比较的时候,发现变量A没有变,因而CAS将A值进行了交换操做。
解决思路:每次变量更新的时候,把版本号+1
核心类:
AtomicStampedReference
其中的核心方法:compareAndSet()
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
AtomicLongArray
这个类维护的是一个数组
这个类与AtomicLong比较,方法 里多了一个索引值让咱们指定。
synchronized:
修饰代码块:大括号括起来的代码,做用于调用的对象
修饰方法:整个方法,做用于调用的对象
修饰静态方法:整个静态方法,做用于全部对象
修饰类,括号括起来的部分,做用于全部对象
synchronized:不可中断锁,适合竞争不激烈,可读性好
Lock:可中断锁,多样化同步,竞争激烈时能维持常态
Atomic:竞争激烈时能维持常态,比Lock性能好;只能同步一个值
致使共享变量在线程间不可见的缘由
**可见性——**synchronized
JMM关于synchronized的两条规定:
- 线程解锁前,必须把共享变量的最新值刷新到主内存
- 线程加锁时,将清空工做内存中共享变量的值,从而使用共享变量时须要从主内存中从新读取最新的值(注意:加锁和解锁是同一把锁)
**可见性——**volatile
经过假如内存屏障和禁止重排序优化来实现
- 对volatile变量写操做时,会在写操做后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内
- 对volatileb变量读操做时,会在读操做前加入一条load屏障指令,从主内存中读取共享变量
volatile关键字不具备原子性
适合的场景:
所以volatile特别适合状态标记量
java内存模型中,容许编辑器和处理器对指令进行重排序,可是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
一般状况下能够经过如下三个关键字来保证有序性:
happens-before原则
若是两个操做的执行次序没法从happens-before原则推导出来,那么就不能保证他们的有序性,虚拟机就能够对他们随意的进行重排序。
也就是除了下面这些规则规定的场景,其余场景,虚拟机能够对其进行重排序。
程序次序规则:一个线程内,按照代码顺序,书写在前面的操做先行发生于书写在后面的操做
锁定规则:一个unLock操做先行发生于后面对同一个锁的lock操做
volatile变量规则:对一个变量的写操做先行发生于后面对这个变量的读操做
传递规则:若是操做A先行发生于操做B,而操做B又先行发生于操做C,则能够得出操做A先行发生于操做C
线程启动规则:Thread对象的start()方法先行发生于此线程的每个动做
线程中断原则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
线程终结规则:线程中全部的操做都先行发生于线程的终止检测,咱们能够经过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。
对象终结规则:一个对象的初始化完成先行发生于它的finalize()方法的开始