java并发 - 学习CAS

学习CAS

一丶什么是CAS

  CAS(Compare And Swap) 即比较交换, 给定一个期望值, 当将要修改的变量值和期望值一致时(即其他线程没有修改), 就修改对应的变量值, 否则什么也不做, 它允许再次尝试比较交换, 直到成功为止.

二丶CAS算法过程

  CAS(V,E,N). V表示要更新的变量, E表示预期值, N表示将要更新的值.

  仅当V=E时, 才会将V更新为N ,  如果 V ≠ E, 说明有其他线程更新了V, 当前线程什么也不用做. 最后CAS返回当前V的真实值

三丶悲观锁与乐观锁

  对于并发控制而言, 锁是一种悲观策略, 它总是认为多线程操作有可能冲突, 因此总要加锁控制,阻塞等待.

  无锁则是一种乐观策略, 它总是认为多线程操作很少会冲突, 无须加锁, 如果有冲突, 再次重新尝试操作直到没有冲突即可.

  无锁主要是使用CAS实现的, 它是一种"乐观锁"

  

  无锁的优点:

  1. 不会产生死锁的问题

  2. 没有锁竞争带来的系统开销

  3. 没有线程间的频繁调度带来的开销

  4. 性能更优越

  

四丶CPU指令支持

  在硬件层面, 大部分的现代处理器都已经支持原子化的CAS指令.在JDK5.0以后, 虚拟机便可以使用这个指令来实现并发操作和并发数据结构, 并且, 这种操作在虚拟机中可以说是无处不在.

  

五丶unsafe类

  源码入口 AtomicInteger的compareAndSet()方法  (JDK 8)

 /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

  

  unsafe是一个不安全类, jdk不允许开发者直接使用.它的内部方法可以像指针一样直接操作内存. 资料 -- <java并发变成-无锁CAS>

  unsafe的compareAndSwapInt()方法, 内部使用CAS原子指令来完成

public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);

第一个参数o为给定的对象, offset为对象内的偏移量(通过偏移量,可以快速定位获取对应的字段变量), expected表示期望值, x表示要设置的值.如果指定的字段的值等于expected, 那么就会把它设置为x

  

六丶Atomic原子类

  Atomic原子类都是使用CAS操作来实现线程安全操作的, 底层调用unsafe类方法, unsafe则使用了CAS原子指令

  以AtomicInteger为例, 内部记录维护一个使用volatile关键字修饰的int变量, (volatitle只保证线程间的可见性,即一个线程修改该变量, 另一个线程能够知道, 但它并不保证原子性)

  AtomicInteger的线程安全自增方法

/**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

  unsafe中的getAndAddInt()方法

    public final int getAndAddInt(Object o, long offset, int delta) {
        int curr;
        do {
            curr = this.getIntVolatile(o, offset);  // 由于使用了volatile, 修改操作能够被各线程感知
        } while(!this.compareAndSwapInt(o, offset, curr, curr + delta));  //如果cas操作没有成功, 则继续尝试, compareAndSwapInt()方法见第五小节解释

        return curr;
    }

七丶ABA问题

  假设一个变量v初始值为0, t1,t2线程读取的值都为0, 由于线程调度等其他问题, t1并未继续cas操作,  期间t2使用cas操作, 将v加1变成1, 线程t3使用cas操作将v减1变成0, 之后t1获得cpu继续执行, t1使用cas操作时, 发现v值仍为0, 以为v没有变化, 实际已发生变化, 这样在某些场景就会出现问题, 这就是ABA问题

  使用带有时间戳的对象引用AtomicStampedReference可以有效解决该问题, AtomicStampedReference内部除了维护对象值, 还维护了一个时间戳(实际可以是任何一个整数标识来表示状态值, 也可以是版本号). AtomicStampedReference在更新对象值之外,还必须更新时间戳,在更新对象值前, 除了比较对象值之外,还必须比较时间戳, 只有这两个条件都相等的情况下,才会去更新值,否则认为被其他线程修改

八丶示例

public class AddCASRunnable implements Runnable {
    private AtomicInteger data =new AtomicInteger(0);

    public int getResult(){
        return data.get();
    }

    @Override
    public void run() {
        for(int i=0;i < 10000000; i++){
            data.incrementAndGet();
        }
    }
}
public class AddUnsafeRunnable implements Runnable {
    private int data=0;

    public int getResult(){
        return data;
    }

    @Override
    public void run() {
        for(int i=0;i < 10000000; i++){
            data++;
        }
    }
}
public class Client {

    public static void main(String[] args) throws InterruptedException {

        AddCASRunnable addCASRunnable=new AddCASRunnable();
        Thread t1=new Thread(addCASRunnable);
        Thread t2=new Thread(addCASRunnable);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("CAS 结果为: "+addCASRunnable.getResult()); // CAS 结果为: 20000000

        AddUnsafeRunnable addUnsafeRunnable=new AddUnsafeRunnable();
        Thread t3=new Thread(addUnsafeRunnable);
        Thread t4=new Thread(addUnsafeRunnable);
        t3.start();
        t4.start();
        t3.join();
        t4.join();
        System.out.println("unsafe 结果为: "+addUnsafeRunnable.getResult()); // unsafe 结果为: 19994931

    }

}

  源码地址为: https://gitee.com/timfruit189/java-learning/tree/master/src/main/java/com/ttx/java/concurrent/cas

学习资料:

  <java高并发程序设计> 葛一鸣 郭超 编著

  <java并发变成-无锁CAS>

原文地址:https://www.cnblogs.com/timfruit/p/10962986.html

时间: 2024-10-15 10:53:51

java并发 - 学习CAS的相关文章

Java并发学习之五——线程的睡眠和恢复

本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.Thread类的sleep方法,可以使线程睡眠.此方法接收一个整数作为参数,表示线程暂停运行的毫秒数.在调用sleep方法后,当时间结束时,JVM会安排他们CPU时间,线程会继续按指令执行. 另一种可能是使用一个有TimeUnit列举元素的sleep方法,使用线程类的sleep方法让当前线程睡眠,但是它接收的参数单位后会转成毫秒的. 2.当你调用sleep()方法,Thread离开CPU并在一段时间内停止运行.在这段时间内,他是不消耗CP

Java并发学习笔记(九)-原子类AtomicInteger

AtomicInteger能够保证对一个整型的操作是原子性.像i++这个操作不是原子操作,存在竞态条件,所以需要加锁,但是加锁的性能不高,如果仅仅为了对一个整数加1.我们来看下他的实现. private volatile int value; AtomicInteger本身持有一个整型变量,所有的操作都是基于这个变量的.变量由violate修饰,这个变量是保证可见性的,具体可见另一篇博客 Java并发学习笔记(六)-互斥性和内存可见性. 来看一下对value加1的操作 public final

Java并发学习之二——获取和设置线程信息

本文是学习网络上的文章时的总结,感谢大家无私的分享. Thread类的对象中保存了一些属性信息能够帮助我们辨别每一个线程,知道它的一些信息 ID:每个线程的独特标示: Name:线程的名称: Priority:线程对象的优先级.优先级别在1-10之间,1是最低级,10是最高级. Status:线程状态.在java中,线程只有6种状态:new,runnable,blocked,waiting,time waiting 或terminated. 现在写一个程序,将线程的信息保存到文件中方便查看 pa

Java并发学习之七——守护线程

本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.Java有两种Thread:"守护线程Daemon"与"用户线程User".用户线程:Java虚拟机在它所有非守护线程已经离开后自动离开:守护线程:则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去. 2.setDaemon(boolean on)方法可以方便的设置线程的Daemon模式,true为Daemon模式,此方法必须在线程启动之前调用,当线程正在运行时调用

Java并发学习之四——操作线程的中断机制

本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.如果线程实现的是由复杂算法分成的一些方法,或者他的方法有递归调用,那么我们可以用更好的机制来控制线程中断.为了这个Java提供了InterruptedException异常.当你检测到程序的中断并在run()方法内捕获,你可以抛这个异常. 2.InterruptedException异常是由一些与并发API相关的Java方法,如sleep()抛出的. 下面以程序解释 package chapter; import java.io.File

【Todo】Java并发学习 &amp; 示例练习及代码

接上一篇:http://www.cnblogs.com/charlesblc/p/6097111.html <Java并发学习 & Executor学习 & 异常逃逸 & 同步互斥Best Practice & wait/notify, conditon#await/signal> 原文参考:http://www.ciaoshen.com/2016/10/28/tij4-21/ <Thinking in Java 读书笔记:第二十一章 - 并发> 第

Java并发学习之八——在线程中处理不受控制的异常

本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.Java里有2种异常: 检查异常:这些异常必须强制捕获她们或在一个方法里的throws子句中. 未检查异常:这些异常不用强制捕获它们. 2.在一个线程对象的run()方法里抛出一个检查异常,我们必须捕获并处理她们.因为run()方法不接受throws子句.当一个非检查异常抛出,默认的的行为是在控制台写下stack trace并退出程序. package chapter; public class Main8 { /** * <p> *

Java并发学习之六——等待线程的终结

本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.在某些情况下,我们需要等待线程的终结.例如,我们可能会遇到程序在执行前需要初始化资源.在执行剩下的代码之前,我们需要等待线程完成初始化任务.为了达到此目的,我们使用Thread类的join()方法.当前线程调用某个线程的这个方法时,它会暂停当前线程,直到被调用线程执行完成. 2.Java提供2种形式的join()方法: Join(longmilliseconds) Join(long milliseconds,long nanos) 第一

Java并发学习之三——线程的中断

本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.一个多个线程在执行的Java程序,只有当其全部的线程执行结束时(更具体的说,是所有非守护线程结束或者某个线程调用System.exit()方法的时候),它才会结束运行.有时,你需要为了终止程序而结束一个线程,或者当程序的用户想要取消某个Thread对象正在做的任务. 2.Java提供中断机制来通知线程表明我们想要结束它.中断机制的特性是线程需要检查是否被中断,而且还可以决定是否相应结束的请求.所以,线程可以忽略中断请求并且继续运行. 3.