Java并发编程:Java原子类

1、线程不安全

当多个线程访问统一资源时,若是没有作线程同步,可能形成线程不安全,致使数据出错。举例:java

@Slf4j public class ThreadUnsafe { // 用于计数的统计变量
    private static int count = 0; // 线程数量
    private static final int Thread_Count = 10; // 线程池
    private static ExecutorService executorService = Executors.newCachedThreadPool(); // 初始化值和线程数一致
    private static CountDownLatch downLatch = new CountDownLatch(Thread_Count); public static void main(String[] args) throws Exception{ for (int i = 0; i < Thread_Count; i++) { executorService.execute(() -> { for (int j = 0; j < 1000; j++) {  // 每一个线程执行1000次++操做 count++; } // 一个线程执行完
 downLatch.countDown(); }); } // 等待全部线程执行完
 downLatch.await(); log.info("count is {}", count); } }

当多个线程对count变量计数,每一个线程加1000次,10个线程理想状态下是加10000次,但实际状况并不是如此。算法

上面的代码执行5次后,打印出来的count值分别为 7130,8290,9370,8790,8132。从测试的结果看出,count的值并非咱们认为的10000次,而都是小于10000次。数据库

之因此出现上面的结果就是由于count++操做并不是原子的。它其实被分红了三步:编程

tp1 = count;  //1
tp2 = tp1 + 1;  //2
count = tp2;  //3

因此 ,若是有两个线程m和n要执行count++操做。若是是理想状态下,m和n线程依次执行,m先执行完后,n再执行,即m1 -> m2 -> m3 -> n1 -> n2 -> n3,那么结果是没问题的。可是若是线程代码的执行顺序是m1 -> n1 -> m2 -> n2 -> m3 -> n3,那么很明显结果就会出错。安全

而上面的测试结果也正是因为没有作线程同步,致使的线程在执行count++时,乱序执行后count的数值就不对了。多线程

2、原子操做

一、使用synchronized实现线程同步并发

对上面的代码作一些改造,对count++操做加入synchronized关键字修饰,实现线程同步,以保证每一个线程在执行count++时,必须执行完成后,另外一个线程才开始执行的。代码以下:性能

@Slf4j public class ThreadUnsafe { // 用于计数的统计变量
    private static int count = 0; // 线程数量
    private static final int Thread_Count = 10; // 线程池
    private static ExecutorService executorService = Executors.newCachedThreadPool(); // 初始化值和线程数一致
    private static CountDownLatch downLatch = new CountDownLatch(Thread_Count); public static void main(String[] args) throws Exception{ for (int i = 0; i < Thread_Count; i++) { executorService.execute(() -> { for (int j = 0; j < 1000; j++) {  // 每一个线程执行1000次++操做
                    synchronized (ThreadUnsafe.class) { count++;  } } // 一个线程执行完
 downLatch.countDown(); }); } // 等待全部线程执行完
 downLatch.await(); log.info("count is {}", count); } }

将线程不安全的测试代码添加synchronized关键字进行线程同步,保证线程在执行count++操做时,是依次执行完后,后面的线程才开始执行的。synchronized关键字能够实现原子性和可见性。测试

将上面的代码执行5次后,打印出来的count值均为10000,已是正确结果了。this

3、原子类

在 JDK1.5 中新增了 java.util.concurrent(J.U.C) 包,它创建在 CAS 之上。而CAS 采用了乐观锁思路,是非阻塞算法的一种常实现,相对于 synchronized 这种阻塞算法,它的性能更好。

一、乐观锁与CAS

在JDK5以前,Java是靠synchronized关键字保证线程同步的,这会致使有锁,锁机制存在如下问题:

在多线程竞争下,加锁和释放锁会致使比较多的上下文切换和调度延时,引发性能问题;

一个线程持有锁后,会致使其余全部等待该锁的线程挂起;

若是一个优先级高的线程等待一个优先级低的线程释放锁会致使线程优先级倒置,引发风险;

独占锁采用的是悲观锁思路。synchronized就是一种独占锁,它会致使其余全部须要锁的线程挂起。而另外一种更加有效的锁就是乐观锁,CAS就是一种乐观锁。乐观锁,严格来讲并非锁。它是经过原子性来保证数据的同步,好比说数据库的乐观锁,经过版本控制来实现,因此CAS不会保证线程同步,只是乐观地认为在数据更新期间没有其余线程参与。

CAS是一种无锁算法。无锁编程,即在不使用锁的状况下实现多线程间的同步,也就是在没有线程被阻塞挂起的状况下实现变量的同步。

CAS算法便是:Compare And Swap,比较并替换。

CAS算法存在着三个参数,内存值V,指望值A,以及须要更新的值B。当且仅当内存值V和指望值A相等的时候,才会将内存值修改成B,不然什么也不作,继续循环检查;

因为CAS是CPU指令,咱们只能经过JNI与操做系统交互,关于CAS的方法都在sun.misc包下Unsafe的类里,java.util.concurrent.atomic包下的原子类等经过CAS来实现原子操做。

CAS特色:

CAS是原子操做,保证并发安全,而不能保证并发同步

CAS是CPU的一个指令(须要JNI调用Native方法,才能调用CPU的指令)

CAS是非阻塞的、轻量级的乐观锁

二、AtomicInteger实现

JDK提供了原子操做类,指的是 java.util.concurrent.atomic 包下,一系列以Atomic开头的包装类。例如AtomicBoolean,AtomicInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操做。如下是AtomicInteger部分源代码:

static { try { //获取value属性值在内存中的地址偏移量
            valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); }

AtomicInteger 的getAndIncrement 调用了Unsafe的getAndInt 方法完成+1原子操做。Unsafe类的getAndInt方法源码以下:

//var1是this指针,var2是地址偏移量,var4是自增值,是自增1仍是自增N
    public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { //获取内存值
            var5 = this.getIntVolatile(var1, var2); //var5是指望值,var5 + var4是要更新的值 //这个操做就是调用CAS的JNI,每一个线程将本身内存里的内存值与var5指望值E做比较,若是相同,就将内存值更新为var5 + var4,不然作自旋操做
            var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }

实现原子操做是基于compareAndSwapInt方法,更新前先取出内存值进行比较,和指望值一致后才更新。 

相关文章
相关标签/搜索