并发编程与高并发解决方案(三):线程安全-原子性

多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

原子性:提供了互斥访问,同一时刻只能有一个线程来对它(共享变量)进行操作
可见性:一个线程对主内存的修改可以及时的被其他线程观察到
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。

原子性

JDK中提供的 Atomic 包(使用了CAS完成线程的原子性操作)

使用 的机制来处理线程之间的原子性(synchronized、Lock)
synchronized依赖于JVM去实现

  • 修饰代码块:大括号括起来的代码,作用于 调用 的对象
  • 修饰方法:整个方法,作用于 调用 的对象
    不同对象之间操作互不影响。(交叉执行)
    ———————————————————————–
  • 修饰静态方法:整个静态方法,作用于 所有 对象
  • 修饰类:括号括起来的部分,作用于 所有 对象
    同一时刻只能有一个线程可执行。(顺序执行)
	public class SynchronizedExample {
        public void test(int j){
        //修饰一个代码块,大括号括起来的代码
            synchronized (this){
                for (int i = 0; i < 10; i++) {
                    log.info("test - {} - {}",j,i);
                }
            }
        }
    //修饰一个方法,被修饰的方法称为同步方法,作用的范围是大括号括起来的部分
    public synchronized void test(int j){
        for (int i = 0; i < 10; i++) {
            log.info("test - {} - {}",j,i);
        }
    }
    //---------------------------------------------------------------------------
    //修饰一个静态方法
     public static synchronized void test(int j){
        for (int i = 0; i < 10; i++) {
            log.info("test - {} - {}",j,i);
        }
    }
    //修饰一个类
    public static void test(int j){
        synchronized (SynchronizedExample.class){
            for (int i = 0; i < 10; i++) {
                log.info("test - {}-{}",j,i);
            }
        }
    }

注意:子类继承父类的 被synchronized修饰的方法时,是没有synchronized修饰的,因为synchronized不属于方法声明的一部分
Lock:依赖特殊的CPU指令,代码实现ReentrantLock

临界区

临界区: 我们把需要互斥执行的代码看成为临界区

临界区
线程进入临界区之前,尝试加锁 lock(), 加锁成功,则进入临界区(对共享变量进行修改),持有锁的线程执行完临界区代码后,执行 unlock(),释放锁;(抢占厕所坑位)
共享资源那么多,如何用一把锁保护多个资源? synchronized 修饰类:括号括起来的部分,作用于 所有 对象

Atomic包:

JDK 的 rt.jar 包中的 Unsafe 类提供了 硬件级别 的原子性操作,类中的方法都是 native 修饰的

指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。例如AtomicBoolean,AtomicInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。

//-------------------示例代码(count可以理解为JMM中的工作内存)--------------------
public class CountExample {

    //请求总数
    public static int clientTotal  = 5000;
    //同时并发执行的线程数
    public static int threadTotal = 200;
    //变量声明:计数      用AtomicInteger类保证了线程的原子性
    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        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 (InterruptedException e) {
                    log.error("excption",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();//保证信号量减为0
        executorService.shutdown();//关闭线程池
        log.info("count:{}",count.get());//变量取值
    }

    private static void add(){
        count.incrementAndGet();//变量操作
    }
}

原子操作的底层实现–CAS机制(Compare And Swap比较并替换)

漫画:什么是 CAS 机制?

AtomicInteger

AtomicInteger的定义:

//-----unsafe: 获取并操作内存的数据。(为我们提供了硬件级别的原子操作)
 private static final Unsafe unsafe = Unsafe.getUnsafe();
 
 //-----------valueOffset代表的是AtomicInteger对象value成员变量在内存中的偏移量。
 //-----------我们可以简单地把valueOffset理解为value变量的【内存地址】。
  private static final long valueOffset;
    static {
      try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }
 private volatile int value;//借助volatile关键字保证其在线程间是可见的

 public final boolean compareAndSet(int expect, int update) {
	return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

AtomicInteger.getAndIncrement()、AtomicInteger.incrementAndGet()
的源码:调用了Unsafe类的getAndAddInt(obj, valueOffset, 1)方法。

//incrementAndGet()的源码中调用了一个名为unsafe.getAndAddInt的方法
public final int incrementAndGet() {
     return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
	/**
	*  获取底层当前的值 并且进行加操作
	* @param var1 需要操作的AtomicInteger 对象
	* @param var2 当前的值 
	* @param var4 要增加的值
	*/
	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;
	    }

这段代码是一个无限循环(do-while),也就是CAS的自旋。CAS操作,如果成功则跳出循环,如果失败则重复上述步骤。(用volatile关键字来保证,获得当前内存中的最新值)

/**
	* 本地的CAS方法核心
	* @param var1 需要操作的AtomicInteger 对象
	* @param var2 当前本地变量中的的值 
	* @param var4 当前系统从底层传来的值 this.getIntVolatile(var1, var2)
	* @param var5 要更新后的值==》底层传来的值 this.getIntVolatile(var1, var2)+要增加的值
	* @Return 如果当前本地变量的值(var2)与底层的值(var4)不等,则返回false,否则更新为var5的值并返回True
	*/
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

AtomicLong 与 LongAdder(CAS机制的优化)

AtomicBoolean

包含了一个compareAndSet(),这个方法可以做到的是控制一个boolean变量在一件事情执行之前为false,事情执行之后变为true。或者也可以理解为可以控制某一件事只让一个线程仅且只执行一次。

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);
}
  //是否发生过
    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");
        }
    }

结果:(log只打印一次)
[pool-1-thread-2] INFO com.superboys.concurrency.example.Atomic.AtomicExample6 - execute
[main] INFO com.superboys.concurrency.example.Atomic.AtomicExample6 - isHappened:true

AtomicReferenceFieldUpdater

核心作用是原子性的去更新某一个类的实例的指定的某一个字段的值。
这个字段用volatile修饰且是非static的。

public class AtomicExample5 {

    //原子性更新某一个类的一个实例
    private static AtomicIntegerFieldUpdater<AtomicExample5> updater
            = AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class,"count");

    @Getter
    public volatile int count = 100;//必须要volatile标记,且不能是static

    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());
        }
    }
}

此方法输出的结果为:
[main] INFO com.superboys.concurrency.example.Atomic.AtomicExample5 - update success 1,120
[main] INFO com.superboys.concurrency.example.Atomic.AtomicExample5 - update failed,120

AtomicLongArray

这个类实际上维护了一个Array数组,我们在对数值进行更新的时候,会多一个索引值让我们更新。

CAS的缺点:

  1. ABA问题。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成
    了“1A-2B-3A”。
    JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。compareAndSet()首先 检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。
源码:
private static class Pair<T> {//一个私有的静态类
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
private volatile Pair<V> pair;

private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }
public boolean compareAndSet(V   expectedReference期望的引用,
                                 V   newReference新的引用(当前引用),
                                 int expectedStamp期望的版本号(标志),
                                 int newStamp新的版本号(当前标志) ) {
        Pair<V> current = pair;//volatile修饰的Pair类型变量current,current可以理解为底层数值
       //判断期望的引用和版本号是否与底层的引用和版本号相符
       //并且排除了新的引用和新的版本号与底层的值相同的情况(即不需要修改)的情况(return代码部分3、4行)
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&   //排除新的引用和新的版本号与底层的值相同的情况
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
}

CAS的底层实现和ABA问题和解决方法

  1. 循环时间长开销大。在并发量比较高的情况下,如果许多线程反复尝试更新变量,却又一直更新不成功,会导致其一直自旋,给CPU带来非常大的开销。。(do-while)
  2. 只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。
    Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。

原子性操作对比

synchronized:不可中断锁,适合竞争不激烈,可读性好
Lock:可中断锁,多样化同步,竞争激烈时能维持常态
Atomic:竞争激烈时能维持常态,比Lock性能好,每次只能同步一个值

Synchronized关键字

会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。

尽管Java1.6为Synchronized做了优化,增加了从偏向锁轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值