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

简介: CAS 解决ABA问题 对于juc下的几个做类型的理解
📣 📣 📣 📢📢📢
☀️☀️你好啊!小伙伴,我是小冷。是一个兴趣驱动自学练习两年半的的Java工程师。
📒 一位十分喜欢将知识分享出来的Java博主⭐️⭐️⭐️,擅长使用Java技术开发web项目和工具
📒 文章内容丰富:覆盖大部分java必学技术栈,前端,计算机基础,容器等方面的文章
📒 如果你也对Java感兴趣,关注小冷吧,一起探索Java技术的生态与进步,一起讨论Java技术的使用与学习
✏️高质量技术专栏专栏链接: 微服务netty单点登录SSMSpringCloudAlibaba
😝公众号😝想全栈的小冷,分享一些技术上的文章,以及解决问题的经验
当前专栏JUC系列

深入理解CAS

什么是 CAS

CAS compareAndSet 比较并交换

研究底层,才会有所突破

实例代码
    //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

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

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

缺点:

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

unsafe类

image-20220304173602223

CAS ABA问题

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

image-20220304224113617

    //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

代码实例
  //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、公平锁和非公平锁的区别

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

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

    public ReentrantLock() {
        sync = new NonfairSync();
    }

也可以修改成 公平锁

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

2、可重入锁

image-20220305021641302

sync关键字

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

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 锁每个方法是配对一个锁,像下面的例子就是开了两个锁,锁必须配对
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

自己的简易自旋锁
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

死锁测试,如何排查死锁

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

2、 使用==jstack==+进程号 2916

image-20220305025252482

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

  1. 日志 百分之九十
  2. 堆栈 百分之十
相关文章
|
机器学习/深度学习 传感器 自动驾驶
狂风暴雨依旧YOLO | 全新数据集,全新任务,促进极端降雨条件下目标检测的优化和发展
狂风暴雨依旧YOLO | 全新数据集,全新任务,促进极端降雨条件下目标检测的优化和发展
500 0
|
9月前
|
存储 人工智能 Serverless
AI助手测评 | 3步快速构建主动式智能导购AI助手
本文介绍了如何利用阿里云的百炼平台构建主动式智能导购AI助手。在当前经济形势下,企业通过AI技术可以有效降低成本并提升服务质量。主动式智能导购AI助手不仅具备专业知识和耐心,还能24小时不间断服务用户,帮助企业节省夜班客服费用。通过创建API-KEY、部署函数计算应用和集成百炼商品检索应用,企业可以在短短几步内快速构建这一智能系统。此外,文章还提供了详细的部署步骤和测评建议,确保企业在实际应用中能够顺利实施。
|
弹性计算 安全 应用服务中间件
阿里云网络系列之经典网络和专有网络
阿里云面向客户提供的网络类型服务有经典网络和专有网络两种,但这两者有什么区别呢?阿里官网给的解释是: 经典网络:IP地址由阿里云统一分配,配置简便,使用方便,适合对操作易用性要求比较高、需要快速使用 ECS 的用户。
94052 1
|
消息中间件 SQL RocketMQ
【RocketMQ系列五】消息示例-顺序消息&延迟消息&广播消息的实现
【RocketMQ系列五】消息示例-顺序消息&延迟消息&广播消息的实现
630 1
|
10月前
|
SQL 安全 关系型数据库
2024 Mysql基础与进阶操作系列之MySQL触发器详解(20)作者——LJS[你个小黑子这都还学不会嘛?你是真爱粉嘛?真是的 ~;以后请别侮辱我家鸽鸽]
MySQL触发器基础等具体操作详解步骤;精典图示-举例说明、注意点及常见报错问题所对应的解决方法IKUN和1原们你这要是学不会我直接退出江湖;好吧!!!
|
12月前
|
API Windows
[原创]MASM32新手指南
[原创]MASM32新手指南
|
自然语言处理 API Android开发
阿里Qwen2-72B大模型已是开源榜的王者,为什么还要推出其他参数模型,被其他模型打榜?
阿里云的Qwen2-72B模型在Hugging Face上荣登开源模型榜首,展现卓越性能,超越其他包括Meta的Llama-3在内的竞争者。Qwen2有多个参数版本,其中72B版本在自然语言理解、知识、代码等任务上表现出色。较小参数版本如7B模型在某些方面略逊一筹。推出不同参数模型可能是为了降低成本、加速迭代、构建丰富的模型生态。通过提供不同规模的模型,阿里云旨在促进技术研究和全场景应用,类似于微软Windows XP和阿里云OS生态的构建策略。
670 1
|
缓存 NoSQL Java
【二十六】springboot整合jedis和redisson布隆过滤器处理缓存穿透
【二十六】springboot整合jedis和redisson布隆过滤器处理缓存穿透
455 0
|
运维 Docker 微服务
深入浅出:使用Docker容器化部署微服务架构
在当今快速演进的软件开发领域,微服务架构因其灵活性和可扩展性而成为了企业级应用的首选。然而,部署和管理众多微服务的复杂性也随之增加。本文将探讨如何通过Docker容器技术简化微服务架构的部署和运维,实现服务的快速交付和高效管理。我们将从Docker的基本概念入手,逐步深入到如何构建、部署和管理容器化的微服务,最终展示一个简单的微服务应用部署案例,帮助读者理解并掌握使用Docker容器化部署微服务的全过程。
|
传感器 监控 物联网
阿里云IoT HaaS 510:快速实现物联网数据传输的利器
众所周知,物联网(IoT)是近年来日益热门的技术领域之一,它的广泛应用为人们的生活和工作带来了无限可能。在物联网应用中,数据的采集和传输是至关重要的一环。DTU是一种应用于物联网数据传输的终端设备,它可以将各类传感器、数据采集单元等通过串口RS232/485传输到DTU,再由DTU转发到4G网络上传至云端。阿里云IoT HaaS 510是一款开板式DTU产品,能够帮助企业快速搭建物联网平台,并实现数据的采集和传输,那么本文就来简单分享一下。
616 1
阿里云IoT HaaS 510:快速实现物联网数据传输的利器

热门文章

最新文章