解析自旋锁CAS

前置知识Java 内存模型中的可见性、原子性和有序性

可见性:

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。

在 Java 中 volatile、synchronized 和 final 实现可见性。

原子性:

原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++;这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等,JDK1.8之后的LonAddr,DoubleAddr提供的分片操作等。

在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。

有序性:

Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。

当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU 中。经典的i++问题不是线程安全,

而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读。

在这里插入图片描述

volatile
保证线程可见性
– MESI

– 缓存一致性协议

禁止指令重排序(CPU)
– DCL单例

– Double Check Lock

-- loadfence 原语指令

-- storefence 原语指令

单例模式-饿汉式

 /**
 * 饿汉式
 * 类加载到内存后,就实例化一个单例,JVM保证线程安全
 * 缺点:不管用到与否,类加载时就完成实例化,不用的类,加载了干啥
 */
public class VolatileSingle {
    private static final  VolatileSingle instance = new VolatileSingle();

    private VolatileSingle(){

    }

    private static VolatileSingle getInstance(){
        return instance;
    }

    public static void main(String[] args) {
        VolatileSingle single1 = VolatileSingle.getInstance();
        VolatileSingle single2 = VolatileSingle.getInstance();
        System.out.println(single1 == single2);
    }
}

懒汉式

/*
 * 懒汉式
 * 默认不会实例化,什么时候用什么时候
 * 虽然达到了按需初始化的目的,但也带来了线程不安全的问题
 */
public class VolatileSingle1 {
    private static VolatileSingle1 instance;

    private VolatileSingle1(){

    }

    private static VolatileSingle1 getInstance(){
        if (instance == null){
            try {
                Thread.sleep(1);
            }catch (InterruptedException interruptedException){
                interruptedException.printStackTrace();
            }
            instance = new VolatileSingle1();
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0 ; i < 100 ; i++){
            new Thread(() ->{
                System.out.println(VolatileSingle1.getInstance().hashCode());
            }).start();
        }
    }
}

输出结果:

2068895579
48238505
120111935
875048040
2077358326
95324150
2077358326
1419457248
1641419305
1419457248
1419457248
1944532174
1316704413
2007045553
1641419305
1557134983
1557134983
993945018
176261066
1955884916
2048417961
1526685889
1788262330
786680875
2077358326
2077358326
2077358326

懒汉式 — 方法加锁

 /*
 * 懒汉式 --方法加锁,
 * 默认不会实例化,什么时候用什么时候
 * 虽然达到了按需初始化的目的,但也带来了线程不安全的问题
 * 虽然可以通过synchronized方法上锁解决,但也带来了效率下降
 */
public class VolatileSingle2 {
    private static VolatileSingle2 instance;

    private VolatileSingle2(){

    }

    private static synchronized VolatileSingle2 getInstance(){
        if (instance == null){
            try {
                Thread.sleep(1);
            }catch (InterruptedException interruptedException){
                interruptedException.printStackTrace();
            }
            instance = new VolatileSingle2();
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0 ; i < 100 ; i++){
            new Thread(() ->{
                System.out.println(VolatileSingle2.getInstance().hashCode());
            }).start();
        }
    }
}

懒汉式—代码块加锁/**

 /* 
 * 懒汉式 --代码块加锁,
 * 默认不会实例化,什么时候用什么时候
 * 虽然达到了按需初始化的目的,但也带来了线程不安全的问题
 * 虽然可以通过synchronized方法上锁解决,但也带来了效率下降
 */
public class VolatileSingle3 {
    private static VolatileSingle3 instance;

    private VolatileSingle3(){

    }

    private static synchronized VolatileSingle3 getInstance(){
        if (instance == null){
            //通过同步代码块的方式来提高效率
            //存在问题:第一个线程过来判断为空,还未执行,线程二来也判断为空,第一个线程上锁,初始化完返回,线程二此时也是空的,也会初始化一遍再返回
            synchronized (VolatileSingle3.class){
                try {
                    Thread.sleep(1);
                }catch (InterruptedException interruptedException){
                    interruptedException.printStackTrace();
                }
                instance = new VolatileSingle3();
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0 ; i < 1000 ; i++){
            new Thread(() ->{
                System.out.println(VolatileSingle3.getInstance().hashCode());
            }).start();
        }
    }
}

DCL— Double Check Lock(双重校验锁)

/**
 * 懒汉式 --双重锁校验,
 * 默认不会实例化,什么时候用什么时候
 * 虽然达到了按需初始化的目的,但也带来了线程不安全的问题
 * 虽然可以通过synchronized方法上锁解决,但也带来了效率下降
 */
public class VolatileSingle4 {
    //关于是否需要加上volatile,不加volatile,问题会出现在指令重排序上
    //new 一个对象会分成3步:1、申请内存;2、更新值;3赋值给对象;
    //若不加volatile,在超高超高的并发情况下,会产生对象在半初始化的时候就指令有可能被打断而重新排序,线程1初始化一半时,线程2到来,取到了半初始化的值
    // synchronized是保证了可见性,但不能阻止重排序
    private static volatile VolatileSingle4 instance;

    private VolatileSingle4(){

    }

    private static synchronized VolatileSingle4 getInstance(){
        if (instance == null){
            //双重检查
            synchronized (VolatileSingle4.class){
                //上锁之后再次进行加锁
                if (instance == null){
                    try {
                        Thread.sleep(1);
                    }catch (InterruptedException interruptedException){
                        interruptedException.printStackTrace();
                    }
                    instance = new VolatileSingle4();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0 ; i < 1000 ; i++){
            new Thread(() ->{
                System.out.println(VolatileSingle4.getInstance().hashCode());
            }).start();
        }
    }
}

volatile能否替代synchronized

/**
 * volatile 并不能保证多线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
 * count++线程不安全,非原子性操作
 */
public class T {
    volatile int count = 0;


    //操作时需要加上锁
    /**synchronized*/ void m() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }
    public static void main(String[] args) {
        T t = new T();
        List<Thread> threadList = new ArrayList<>();

        for (int j = 0; j < 10; j++) {
            threadList.add(new Thread(t::m,"thread-" + j));
        }

        threadList.forEach((o) -> o.start());

        threadList.forEach((o) ->{
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(t.count);
    }
}

锁优化

 /**
 * synchronized优化
 * 同步代码块中的语句越少越好-----锁细化
 * 锁争用的时候,可以考虑锁粗化
 * 比较m1和m2
 */
public class FineCoarseLock {
  
   int count = 0;

   synchronized void m1() {
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
      count ++;
      
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
  
   void m2() {
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
      //采用细粒度的锁,可以使线程争用时间变短,从而提高效率
      synchronized(this) {
         count ++;
      }
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}

CAS(无锁化,自旋)

–Compare And Set
–cas(V, Expected, NewValue)

-if V == E 若所期望的值相同

V = NEW      则设置成新的值

try again or fail   反之这重试或者直接失败
  • CPU 原语支持(执行过程中不能被打断)
  • ABA问题 采用加version解决(基础类型–可以不用考虑,引用类型—会通俗来讲就是你大爷还是你大爷,你大妈已经不是你大妈了_

java.util.concurrent.atomic.目录下以Atomic开头的都是自旋锁,JDK1.8之后提拱了分片区自旋锁,LongAddr、DoubleAddr等

/**
 * 解决同样的问题的更高效的方法,使用AtomXXX类
 * AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的
 */
public class T_AtomicInteger {  
   AtomicInteger count = new AtomicInteger(0);

   /*synchronized*/ void m() {
      for (int i = 0; i < 10000; i++)
         count.incrementAndGet();
   }

   public static void main(String[] args) {
      T_AtomicInteger t = new T_AtomicInteger();

      List<Thread> threads = new ArrayList<Thread>();

      for (int i = 0; i < 10; i++) {
         threads.add(new Thread(t::m, "thread-" + i));
      }

      threads.forEach((o) -> o.start());

      threads.forEach((o) -> {
         try {
            o.join();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      });
      System.out.println(t.count);
   }
}

Unsafe

在这里插入图片描述
在这里插入图片描述
随手点个关注~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值