3)volatile可见性与CAS

目录

 

volatile实现可见性

volatile与synchronized比较

volatile的非原子性

CAS

ABA问题

实现CAS的原子类

例:AtomicLong类

例:AtomicIntegerArray类

例:AtomicIntegerFieldUpdater类

例:AtomicReference类

例:AtomicStampedReference


volatile实现可见性

volatile关键字的作用使变量在多个线程之间可见,满足可见性

volatile 的作用可以 强制线程从公共内存中读取变量的值 ,而不是从工作内存中读取
案例:下列代码在某些电脑会存在数据可风性问题,导致while()一直循环。(但是在while()有语名就不会一直执行)
public class Test02 {
    public static void main(String[] args) {
        //创建 PrintString 对象
        PrintString printString = new PrintString();
        //开启子线程,让子线程执行 printString 对象的
        // printStringMethod()方法
        new Thread(new Runnable() { @Override public void run() { printString.printStringMethod(); } }).start(); 
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }//main 线程睡眠 1000 毫秒
        System.out.println("在 main 线程中修改打印标志");
        printString.setContinuePrint(false); //程序运行,查看在 main 线程中修改了打印标志之后 ,子线程打印是否可以结束打 印 //程序运行后, 可能会出现死循环情况 //分析原因: main 线程修改了 printString 对象的打印标志后, 子线程读不到 //解决办法: 使用 volatile 关键字修饰 printString 对象的打印标志. // volatile 的作用可以强制线程从公共内存中读取变量的值,而不是从工作内 存中读取
    }
    static class PrintString {
        private boolean continuePrint = true;

        public PrintString setContinuePrint(boolean continuePrint) {
            this.continuePrint = continuePrint;
            return this;
        }

        public void printStringMethod() {
            System.out.println(Thread.currentThread().getName() + "开始....");
            while (continuePrint) {
            }
            System.out.println(Thread.currentThread().getName() + "结束++++++++++++++");
        }
    }
}

volatile与synchronized比较

  1. volatile性能优于synchronized,volatile只修饰变量,而synchronized可以修饰方法,代码块
  2. 多线程访问volatile不会阻塞,而synchronized可能会阻塞(volatile就像一个从内存取值,而synchronized修饰的代码块可能有很多的操作)
  3. volatile能保证数据的可见性,但是不能保证原子性;而synchronized可以保存原子性与可见性(synchronized含有隐形的刷新数据功能 )

volatile的非原子性

volatile的作用是强制线程从公共内存中读取变量的值,而不是从工作内存中读取。没有关于原子性的功能,只是可见性。

如  a++ 使用分为   1)取出a值  2)计算a+1  3)存放新的a值

volatile只保存了第一步,如果在多线程中可以使用原子类进行自增自减操作

CAS

原理:在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样就更新,否则重新执行

如下:在修改value的时候,取出当前的value与 操作起始时读取的值比较,一样时才执行写入

public class CASTest {
    public static void main(String[] args) {
        CASCounter casCounter = new CASCounter();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    casCounter.incrementAndGet();
                }
            }).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.err.println(casCounter.getValue());

    }
}
class CASCounter{
    volatile private long value = 0;

    public long getValue() {
        return value;
    }
    //定义 comare and swap方法
    private boolean compareAndSwap(long exepectedValue,long newValue){
        synchronized (this){
            if(value == exepectedValue){
                value = newValue;
                return true;
            }else {
                System.err.println("冲突了");
                return false;
            }
        }
    }
    public long incrementAndGet(){

        long oldvalue;
        long newValue;
        do {
            oldvalue = value;
            newValue = oldvalue+1;
        }while (!compareAndSwap(oldvalue,newValue));
        return value;
    }
}

ABA问题

如果出现以下情况:

CAS 实现原子操作背后有一个假设“ 共享变量的当前值与当前线程提供的期望值相同, 就认为这个变量没有被其他线程修改过
实际上这种假设不一定总是成立. 如有共享变量 count = 0
  • A 线程对 count 值修改为 10
  • B 线程对 count 值修改为 20
  • C 线程对 count 值修改为 0
当前线程看到 count 变量的值现在是 0, 现在是否认为 count 变 量的值没有被其他线程更新呢? 这种结果是否能够接受 ??
这就是 CAS 中的 ABA 问题 , 即共享变量经历了 A->B->A 的更新
如果要规避ABA问题,可以为共享变量引入一个修订号(时间戳)来表示。过程为 : [A,0] ->[B,1]->[A,2]。这时虽然共享值没有变化,但是修订号已经发生了改变
 

实现CAS的原子类

原子变量类基于CAS实现,原子类有12个:如

例:AtomicLong类

相当于Long的原子类,
get  获取当前值
set  设置当前值
lazySet
getAndSet  先获取当前值,再设置当前值
compareAndSet
weakCompareAndSet
getAndIncrement  == i++
getAndDecrement  == i--
getAndAdd(n)     先获取当前值 i,再 i += n;
incrementAndGet  == ++i
decrementAndGet  == --i
addAndGet(n)     先 i+=n 再获取i
getAndUpdate
updateAndGet
getAndAccumulate
accumulateAndGet
toString
intValue
longValue
floatValue
doubleValue
serialVersionUID
unsafe
valueOffset
VM_SUPPORTS_LONG_CAS
value
        int i = 0;
        AtomicLong atomicLong = new AtomicLong(0);
//        atomicLong.addAndGet(1); //+n
        //相当于  --i
        System.err.println(atomicLong.decrementAndGet());
        //相当于  i--
        System.err.println(atomicLong.getAndIncrement());
        //相当于  i+=10 后返回i
        System.err.println(atomicLong.addAndGet(10));
        atomicLong.set(0);
        System.err.println(atomicLong.addAndGet(10));
        System.err.println(atomicLong.getAndSet(10));

例:AtomicIntegerArray类

相当于Integer[] 的源子类

        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
        //1)返回指定位置的元素
        System.out.println(atomicIntegerArray.get(0)); //返回下标为0的元素
        //2)设置指定位置的元素
        atomicIntegerArray.set(0,10); //将下标为0的元素设置为10
        //在设置数组元素的同时,返回元素的旧值
        System.out.println(atomicIntegerArray.getAndSet(0,20));
        //3)修改元素的值,把数组元素加上某个值
        System.out.println(atomicIntegerArray.getAndSet(0,10));//下标为0元素加10,返回旧值
        System.out.println(atomicIntegerArray.addAndGet(0,10));//下标为0元素加10,返回新值
        //4)CAS操作
        //如果数组中下标为0的元素,值为10,则将其修改为222,返回 boolean
        System.out.println(atomicIntegerArray.compareAndSet(0,10,222));
        //5)自增/自减操作
        System.out.println(atomicIntegerArray.decrementAndGet(0)); //相当于 --i[0]
        System.out.println(atomicIntegerArray.incrementAndGet(0)); //相当于 ++i[0]
public class Test02 {
    static AtomicIntegerArray integerArray = new AtomicIntegerArray(10);
    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new th();
        }
        for (Thread thread : threads) {
            thread.start();
        }
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(integerArray);

    }
    static class th extends Thread{
        @Override
        public void run() {
            for (int i = 0; i< 1000; i++){
                for (int j = 0 ; j < 10; j++){
                    integerArray.incrementAndGet(j);
                }
            }
        }
    }

}

例:AtomicIntegerFieldUpdater类

可以对原子整数字段进行更新(对一个对象的整数属性更新),要求:

1)字符必须使用volatile修饰,使线程之间可见

2)只能是实例变量,不能是静态变量,也不能使用final修饰

public class AtomicIntegerFieldUpdaterTest {
    public static void main(String[] args) {
        User user = new User();
        AtomicIntegerFieldUpdater<User> updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age"); //age是一个用volatile修饰的整数字段
        System.out.println(updater.addAndGet(user,1));  //age属性直接操作是不能保证原子性的
    }
}
class User{
    volatile int age;
    String name;

}

例:AtomicReference类

可以原子读一个对象

static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
    public static void main(String[] args) {
        for (int i = 0;i < 100; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if(atomicReference.compareAndSet("abc","def")){
                        System.out.println(Thread.currentThread().getName()+"把字符串abc改为def");
                    }
                }
            }).start();
        }

        for (int i = 0;i < 100; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if(atomicReference.compareAndSet("def","abc")){
                        System.out.println(Thread.currentThread().getName()+"还原字符串");
                    }
                }
            }).start();
        }
    }

例:AtomicStampedReference

和AtomicReference类似,它通过版本号解决了ABA问题

static AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("abc",0);
    public static void main(String[] args) {
        //获取当前版本号
        int j = stampedReference.getStamp();
        //如果当前值是abc且版本号为j,则将其值修改为cge,版本号j+1
        stampedReference.compareAndSet("abc","cge",j,j+1);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
VolatileCAS都是Java中用于实现多线程并发编程的技术。 Volatile是一种轻量级的同步机制,用于保证可见和禁止指令重排序。当一个变量被声明为volatile时,每次对该变量的读写操作都会直接操作主内存,而不是从缓存中读取或写入。这样可以确保不同线程之间对该变量的操作是可见的,从而避免了由于线程间内存可见问题而带来的线程安全问题。 CAS(compare and swap)是一种无锁的原子操作,它通过比较预期值和实际值来判断是否需要更新主内存中的值。当多个线程同时执行CAS操作时,只有一个线程能够成功,其他线程需要重试直到成功为止。CAS操作是通过硬件的原子指令来实现的,因此具有很高的效率。CAS操作可以确保线程之间对共享变量的更新是原子的,从而避免了由于线程竞争而引起的数据不一致的问题。 VolatileCAS可以结合使用,来实现线程安全的操作。Volatile保证了可见,确保了对共享变量的读写操作是立即可见的;而CAS保证了原子,通过比较交换的方式保证了多个线程对共享变量的更新是安全的。当多个线程同时访问共享变量时,可以使用Volatile保证可见,使用CAS保证原子,从而实现线程安全。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java多线程(Synchronized+Volatile+JUC 并发工具原理+线程状态+CAS+线程池)](https://download.csdn.net/download/weixin_43516258/87937931)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [volatile + CAS](https://blog.csdn.net/weixin_51207423/article/details/123085930)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值