java并发编程3.1原子操作CAS

环境:

jdk1.8

摘要说明:

上一大章节主要阐述了常用的并发工具类的的使用:

本章节主要讲述原子操作CAS及常用的原子操作类;

CAS是Compare And Set的缩写,是以一种无锁的方式实现并发控制。在实际情况下,同时操作同一个对象的概率非常小,所以多数加锁操作做的是无用功,CAS以一种乐观锁的方式实现并发控制。

步骤:
1.CAS

之前有写过一个文章《高并发编程之高并发场景:秒杀(无锁、排他锁、乐观锁、redis缓存的逐步演变)》其中我们就提到使用数据库的悲观锁(排他锁)乐观锁来实现高并发;

实际java本身也有对应悲观锁和乐观锁的实现方式:之前的synchronized关键字和后面要学习的显示锁都是悲观锁的一种实现;而CAS就是乐观锁的一种实现;

CAS是Compare And Set的缩写,是以一种无锁的方式实现并发控制。在实际情况下,同时操作同一个对象的概率非常小,所以多数加锁操作做的是无用功,CAS以一种乐观锁的方式实现并发控制。

CAS的具体实现就是给定内存中的期望值和修改后的目标值,如果实际内存中的值等于期望值,则内存值替换为目标值,否则操作失败。该操作具有原子性。

我们也可以把期望值看成版本号,就是如果修改前获取的版本号在修改时没有发生改变就进行替换;

举例:

多线程情况下如何实现count++?
使用悲观锁可以使用synchronized对变量进行加锁;

CAS的操作流程如下:
1.读取内存数据j=count;

2.CAS(j,j++);即比较内存中count数据是否还为j,如果是才进行修改;整个操作具有原子性

3.如果成功,返回;失败则重新执行第一步直到成功,也称之为自旋。

由于第二步成功的概率很大,所以采用CAS的代价很小;当高并发情况下由于CAS采用自旋的方式对CPU会有较大的操作负担,所以可能会损耗部分CPU资源。

2.原子操作类

jdk提供了许多根据CAS思路产生的原子操作类;总结下来可以分成以下几类:

  • 更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference
  • 原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater 

上述原子操作类都是在java.util.concurrent.atomic包下,使用起来也大同小异;首先我们看看AtomicBoolean类的使用;再举例几个典型进行使用,其他可参考api;

注:其中原子更新字段类jdk上说比其他原子类都要弱,这里就不推荐使用,大家可以使用AtomicReference代替;

AtomicBoolean:可以原子更新的布尔值。原子布尔值用于诸如原子更新标志之类的应用程序中,不能用作布尔值的替代品。

构造方法

AtomicBoolean():创建一个初始值为false的新原子布尔值。

AtomicBoolean(boolean initialValue):创建一个指定初始值的新原子布尔值。

常用方法

  • boolean    compareAndSet(boolean expect, boolean update):如果当前值==期望值,则原子化地将该值设置为给定的更新值。
  • boolean    get(): 获取当前值;
  • boolean    getAndSet(boolean newValue):原子地设置为给定值并返回前一个值。
  • void    lazySet(boolean newValue):最终设置为给定的值。
  • void    set(boolean newValue):无条件的设置成给定的值。

举例

package pers.cc.cas;

import java.util.concurrent.atomic.AtomicBoolean;

/*
 * 原子布尔类型实践
 */
public class AtomicBooleanTest {
    /**
     * 定义一个线程安全的Integer并设定初始值为0
     */
    static AtomicBoolean atomicBoolean = new AtomicBoolean(true);

    public static void main(String[] args) throws InterruptedException {
        Thread successThread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 若atomicBoolean为false则自旋直到其他线程修改atomicBoolean为true;
                // 这里要注意weakCompareAndSet本身是不会自旋的,就是一次性操作,修改成功或失败
                while (!atomicBoolean.weakCompareAndSet(true, false)) {
                    System.out.println(System.currentTimeMillis()
                            + "successThread:" + atomicBoolean.get());
                }
                System.out.println(System.currentTimeMillis() + "errorThread:"
                        + atomicBoolean.get());
            }
        });
        Thread errorThread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 若atomicBoolean为false则修改为true
                atomicBoolean.compareAndSet(false, true);
                System.out.println(System.currentTimeMillis() + "errorThread:"
                        + atomicBoolean.get());
            }
        });
        // 修改atomicBoolean为false
        System.out.println(atomicBoolean.getAndSet(false));
        successThread.start();
        Thread.sleep(3);
        errorThread.start();

    }
}

运行结果:

true
1550569160198successThread:false
1550569160198successThread:false
......
1550569160201successThread:false
1550569160201successThread:false
1550569160201successThread:false
1550569160201errorThread:true
1550569160201successThread:false

3.AtomicInteger

AtomicInteger:可以原子更新的int值。AtomicInteger用于原子递增计数器等应用程序中,不能用作整数的替代。但是,这个类确实扩展了Number,允许处理基于数字的类的工具和实用程序进行统一访问。

构造方法

AtomicInteger():创建一个初始值为0的新原子更新的int值。

AtomicInteger(int initialValue):创建一个指定初始值的新原子更新的int值。

常用方法

  • int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction):使用将给定函数应用于当前值和给定值的结果原子地更新当前值,并返回更新后的值。
  • int addAndGet(int delta):原子地将给定值添加到当前值。返回修改后的值
  • boolean  compareAndSet(int expect, int update):如果当前值==期望值,则原子化地将该值设置为给定的更新值。
  • int decrementandget():原子性地将当前值减1。
  • int get():获取当前值。
  • int getandcollect (int x, IntBinaryOperator accumulatorFunction):使用将给定函数应用于当前值和给定值的结果原子地更新当前值,返回前一个值。
  • int getandadd (int delta):原子地将给定值添加到当前值。
  • int incrementandget():原子地将当前值增加1。
  • set(int newValue):设置为给定值。
  • int updateandget(IntUnaryOperator updateFunction):使用应用给定函数的结果原子地更新当前值,返回更新后的值。

实战

package pers.cc.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {
    /**
     * 定义一个线程安全的Integer并设定初始值为0
     */
    static AtomicInteger atomicInteger = new AtomicInteger(0);

    static int i = 0;

    public static void main(String[] args) throws InterruptedException {
        for (int j = 0; j < 100000; j++) {
            Thread addThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    // 原子的增加1
                    atomicInteger.incrementAndGet();
                    i++;
                }
            });
            addThread.start();
        }
        Thread.sleep(2000);
        System.out.println(atomicInteger.get());
        System.out.println(i);
    }
}

 运行结果是不定的i的值会不停变化,由此也可以看出AtomicInteger是一个原子操作且是线程安全的

100000
99998

4.AtomicReference

AtomicReference:可以原子更新的对象引用

构造方法

AtomicReference():创建一个初始值为null的新原子更新的对象。

AtomicReference(V initialValue):创建一个指定初始化的新原子更新的对象。

常用方法

  • boolean compareandset (V expect, V update):如果当前值==期望值,则原子化地将该值设置为给定的更新值。
  • v get ():获取当前值。
  • V getandset (V newValue):原子地设置为给定值并返回旧值。
  • V getandupdate (UnaryOperator < V > updateFunction):使用应用给定函数的结果原子地更新当前值,返回前一个值。
  • set(V newValue):设置为给定值。
package pers.cc.cas;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 可以原子更新的对象引用
 * 
 * @author cc
 *
 */
public class AtomicReferenceTest {
    /**
     * 创建一个原子更新的对象
     */
    static AtomicReference < User > atomicReference = new AtomicReference <>();

    public static void main(String[] args) {
        User cc = new User("cc", 18);
        /**
         * 初始化对象
         */
        atomicReference.set(cc);
        User cccc = new User("cc", 30);
        /**
         * 更换对象属性
         */
        atomicReference.compareAndSet(cc, cccc);
        System.out.println(atomicReference.get().getAge());
        System.out.println(cc.getAge());
    }

    static class User {
        private String name;

        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}

运行结果为:30,18;

可以看出AtomicReference的替换并不会更新初始对象;

5.AtomicStampedReference

AtomicStampedReference:维护一个对象引用和一个整数“戳记”,该“戳记”可以以原子方式更新。

实现注意:此实现通过创建表示“装箱”[引用、整数]对的内部对象来维护带戳记的引用。实际就是带有版本戳的更新

构造方法

AtomicStampedReference(V initialRef, int initialStamp):使用给定的初始值创建一个新的AtomicStampedReference。。

常用方法

  • boolean    attemptStamp(V expectedReference, int newStamp):如果当前引用是预期引用的==,则原子化地将戳记的值设置为给定的更新值。
  • boolean compareandset (V expectedReference, V newReference, int expectedStamp, int newStamp):如果当前引用==期望的引用,并且当前戳等于期望的戳,则原子地将引用和戳记的值设置为给定的更新值。
  • v get(int[]stampHolder):返回引用和戳记的当前值。
  • v getreference():返回引用的当前值。
  • int getstamp():返回戳记的当前值。
  • void set(V newReference, int newStamp):无条件地设置引用和戳记的值。

实战

package pers.cc.cas;

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * AtomicStampedReference维护一个对象引用和一个整数“戳记”,该“戳记”可以以原子方式更新。
 * 实现注意:此实现通过创建表示“装箱”[引用、整数]对的内部对象来维护带戳记的引用。
 * 
 * @author cc
 *
 */
public class AtomicStampedReferenceTest {
    /**
     * 定义一个原子对象,类型为String ,初始版本号为0
     */
    static AtomicStampedReference < String > asr = new AtomicStampedReference <>(
            "cc", 0);

    public static void main(String[] args) throws InterruptedException {
        /**
         * 获取初始版本号
         */
        final int oldStamp = asr.getStamp();
        /**
         * 获取初始值
         */
        final String oldReferenc = asr.getReference();

        System.out.println(oldReferenc + "===========" + oldStamp);

        Thread rightStampThread = new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()
                        + "当前变量值:"
                        + oldReferenc
                        + "当前版本戳:"
                        + oldStamp
                        + "-"
                        + asr.compareAndSet(oldReferenc, oldReferenc + "Java",
                                oldStamp, oldStamp + 1));

            }

        });

        Thread errorStampThread = new Thread(new Runnable() {

            @Override
            public void run() {
                String reference = asr.getReference();
                System.out.println(Thread.currentThread().getName()
                        + "当前变量值:"
                        + reference
                        + "当前版本戳:"
                        + asr.getStamp()
                        + "-"
                        + asr.compareAndSet(reference, reference + "C",
                                oldStamp, oldStamp + 1));

            }

        });

        rightStampThread.start();
        rightStampThread.join();
        errorStampThread.start();
        errorStampThread.join();
        System.out.println(asr.getReference() + "===========" + asr.getStamp());

    }
}

运行结果如下:

cc===========0
Thread-0当前变量值:cc当前版本戳:0-true
Thread-1当前变量值:ccJava当前版本戳:1-false
ccJava===========1

6.AtomicLongArray

AtomicLongArray:一个长数组,其中的元素可以原子地更新。

构造方法

AtomicLongArray(int length):创建给定长度的新AtomicLongArray,所有元素初始值为0。

AtomicLongArray(long[] array):创建一个新的AtomicLongArray,其长度与给定数组相同,并且所有元素都从该数组中复制。

常用方法:

  • long addandget (int i, long delta):原子地将给定的值添加到索引i处的元素。
  • boolean compareandset (int i, long expect, long update):如果当前值==期望值,则原子化地将位于i位置的元素设置为给定的更新值。
  • long  decrementAndGet(int i):原子上,下标i的元素减少了1。
  • long  get(int i):获取位置i处的当前值。
  • long incrementandget (int i):元素在索引i处的原子增量为1。

实战

package pers.cc.cas;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicLongArray;

/**
 * AtomicLongArray:一个长数组,其中的元素可以原子地更新。
 * 
 * @author cc
 *
 */
public class AtomicLongArrayTest {
    static long[] arr = new long[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

    /**
     * 初始化一个可原子操作的数组
     */
    static AtomicLongArray atomicLongArray = new AtomicLongArray(arr);

    public static void main(String[] args) {
        /**
         * 指定游标设值
         */
        atomicLongArray.set(0, 9);
        System.out.println(atomicLongArray.get(0));
        System.out.println(arr[0]);
        /**
         * 指定游标进行比较进行变更
         */
        atomicLongArray.compareAndSet(1, 1, 8);
        /**
         * 指定游标进行减1
         */
        System.out.println(atomicLongArray.decrementAndGet(9));

    }
}

 执行结果为:9,0.,8

7.源码

https://github.com/cc6688211/concurrent-study.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值