前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JUC系列(九) CAS 与锁的理解

JUC系列(九) CAS 与锁的理解

作者头像
冷环渊
发布2022-12-03 08:57:46
2690
发布2022-12-03 08:57:46
举报

深入理解CAS

什么是 CAS

CAS compareAndSet 比较并交换

研究底层,才会有所突破

实例代码

代码语言:javascript
复制
    //CAS compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        // 两个参数 : 期望 更新
        //   public final boolean compareAndSet(int expectedValue, int newValue)
        // 如果我们的期望值达到了 那么就更新,否则 就不更新 CAS 是 CPU 并发原语
        atomicInteger.compareAndSet(2020, 2021);
        System.out.println(atomicInteger.get());
        atomicInteger.getAndIncrement();
        atomicInteger.compareAndSet(2020, 2021);
        System.out.println(atomicInteger.get());
    }

加一 方法底层原理 为什么效率高

  1. 调用了 unsafe 操作内存的方法
  2. 查看这个getAndAddInt这个方法的参数, var 1 就是原本数字 var 2 就是valueoffset ,var 4 就是要增加多少
  3. var 是获取内存值,之后调用方法 如果 var1 和 var2 的结果是我们想要的 也就是 var5 那么就讲 var5+var4 也就是原本的结果 +1
image-20220304173908384
image-20220304173908384

这个方法是一个典型的自旋锁

CAS:比较当前工作内存中的值,如果这个值是期望的,那么执行操作,如果不是就一直循环

缺点:

  1. 循环会耗时
  2. 一次性只能保证一个共享变量
  3. ABA问题

unsafe类

image-20220304173602223
image-20220304173602223

CAS ABA问题

A:期望是 1 交换成2 ,但是在还没有交换的时候 另一个线程 把 当前的a 改变成了 3 又改回 1 此时 当前A线程依旧可以正常的交换,但是期间的值已经被别人用过了。

image-20220304224113617
image-20220304224113617
代码语言:javascript
复制
    //CAS compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //对于 我们平时都 sql 是如何解决的,乐观锁
        // 两个参数 : 期望 更新
        //   public final boolean compareAndSet(int expectedValue, int newValue)
        // 如果我们的期望值达到了 那么就更新,否则 就不更新 CAS 是 CPU 并发原语
        //=============捣乱的线程====================
        atomicInteger.compareAndSet(2020, 2021);
        System.out.println(atomicInteger.get());
        atomicInteger.compareAndSet(2021, 2020);
        System.out.println(atomicInteger.get());

        //=================期望的线程=============
        atomicInteger.compareAndSet(2020, 2021);
        System.out.println(atomicInteger.get());
    }

原子引用(解决aba问题)

解决ada问题 原子引用 AtomicStampedReference

可以理解为 乐观锁

PS: integer 使用了 对象缓存机制,默认范围是 -128-127 ,推荐使用静态工厂方法 valueof 获取对象实例,而不是new ,因为 value of 使用缓存,而new 一定会创建心的对象分配新的内存空间

image-20220305021114344
image-20220305021114344

代码实例

代码语言:javascript
复制
  //CAS compareAndSet 比较并交换
    public static void main(String[] args) {
        //AtomicInteger atomicStampedReference = new AtomicInteger(2020);
        // 注意 如果 泛型是一个包装类,注意对象的引用问题,
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("a1 =>" + stamp);
            System.out.println("a2 =>" + atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a2 =>" + atomicStampedReference.getStamp());
            System.out.println("a3 =>" + atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a3 =>" + atomicStampedReference.getStamp());
        }, "a").start();
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("b1 =>" + stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("b2 =>" + atomicStampedReference.compareAndSet(1, 6, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("b2 =>" + atomicStampedReference.getStamp());
        }, "b").start();
    }

对于锁的理解

1、公平锁和非公平锁的区别

公平锁 :不能够插队,必须先来后到

非公平锁: 可以插队 锁 默认的都是非公平的

代码语言:javascript
复制
    public ReentrantLock() {
        sync = new NonfairSync();
    }

也可以修改成 公平锁

代码语言:javascript
复制
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

2、可重入锁

image-20220305021641302
image-20220305021641302
sync关键字

这里 是一把锁,每次执行的时候直到方法里逐层向外解锁

代码语言:javascript
复制
public class lockdemo {
    public static void main(String[] args) {
        phone phone = new phone();
        new Thread(() -> {
            phone.sms();
        }, "a").start();
        new Thread(() -> {
            phone.sms();
        }, "b").start();
    }
}

class phone {
    public synchronized void sms() {
        System.out.println(Thread.currentThread().getName() + "=>发短信");
        //这里也有锁
        call();
    }

    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + "=>打电话");
    }
}
Lock
  1. lock 锁每个方法是配对一个锁,像下面的例子就是开了两个锁,锁必须配对
代码语言:javascript
复制
public class lockdemo2 {
    public static void main(String[] args) {
        phone2 phone = new phone2();
        new Thread(() -> {
            phone.sms();
        }, "a").start();
        new Thread(() -> {
            phone.sms();
        }, "b").start();
    }
}

class phone2 {
    //这里就有区别 sync关键字是一个锁,这里使用lock 是两个锁,锁必须配对,否则就会死锁
    Lock lock = new ReentrantLock();

    public synchronized void sms() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "=>发短信");
            //这里也有锁
            call();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public synchronized void call() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "=>打电话");
            //这里也有锁
            call();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

3、自旋锁

这里我们之前查看CAS 的时候 有一个调用自增的方法就是自旋锁

image-20220305022601565
image-20220305022601565

自己的简易自旋锁

代码语言:javascript
复制
public class spinlocks {
    //    int =0
//    thread = null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    //    加锁操作
    public void mylock() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "=> mylock");
        while (!atomicReference.compareAndSet(null, thread)) {
        }
    }

    //    解锁
    public void myunlock() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "=> myUnlock");
        atomicReference.compareAndSet(thread, null);
    }
}

class test {
    public static void main(String[] args) throws InterruptedException {
        //底层使用 CAS 自旋锁
        spinlocks lock = new spinlocks();
        new Thread(() -> {
            lock.mylock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myunlock();
            }
        }, "t1").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            lock.mylock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myunlock();
            }
        }, "t2").start();
    }
}

4、死锁

什么是死锁: 互相争抢锁的过程

image-20220305023700803
image-20220305023700803

死锁测试,如何排查死锁

代码语言:javascript
复制
public class DeadLockDemo {
    public static void main(String[] args) {
        String a = "lockA";
        String b = "lockB";
        new Thread(new mythread(a, b), "t1").start();
        new Thread(new mythread(b, a), "t2").start();
    }
}

class mythread implements Runnable {
    private String a;
    private String b;

    public mythread(String a, String b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        synchronized (a) {
            System.out.println(Thread.currentThread().getName() + "lock=>" + a + "lock=>" + b);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (b) {
                System.out.println(Thread.currentThread().getName() + "lock=>" + b + "lock=>" + a);
            }
        }
    }
}

解决问题

1、使用 jps -l 定位 进程

image-20220305025155109
image-20220305025155109

2、 使用jstack+进程号 2916

image-20220305025252482
image-20220305025252482

面试或者工作中,排查锁的问题:

  1. 日志 百分之九十
  2. 堆栈 百分之十
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022/12/02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 深入理解CAS
    • 什么是 CAS
      • unsafe类
        • CAS ABA问题
          • 原子引用(解决aba问题)
          • 对于锁的理解
            • 1、公平锁和非公平锁的区别
              • 2、可重入锁
                • sync关键字
                • Lock
              • 3、自旋锁
                • 4、死锁
                相关产品与服务
                短信
                腾讯云短信(Short Message Service,SMS)可为广大企业级用户提供稳定可靠,安全合规的短信触达服务。用户可快速接入,调用 API / SDK 或者通过控制台即可发送,支持发送验证码、通知类短信和营销短信。国内验证短信秒级触达,99%到达率;国际/港澳台短信覆盖全球200+国家/地区,全球多服务站点,稳定可靠。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档