线程安全2

文章目录

锁的可重入性

直观来看这个代码不能运行

为啥没有出现阻塞?

当前由于是同一个线程,此时的锁对象,就知道了第二次加锁的线程,就是持有锁的线程,第二次操作,就可以直接放行通过,不会出现阻塞

这个特性称为"可重入"

对于可重入锁来说:内部会持有两个信息

1.当前锁是哪个线程持有的

2.加锁次数的计时器

过程描述:

第一次加锁的时候计数器+1(初始情况为0,+1后变成了1,说明当前这个对象被该线程加锁一次)同时记录线程是谁,第二次加锁的时候,发现加锁线程和持有线程是一个线程,第二次应该能够加锁成功,判断当前加锁进程是否持有锁的线程,如果不是同一个线程,阻塞如果是同一个线程,就只是++计数器,即可没有其他操作,此处的计时器(源码是在JVM中,c++代码实现出来的)是关键。

死锁

1.一个线程,一把锁

如果锁是不可重入锁,并且一个线程对这把锁加锁两次就会出现死锁(钥匙锁屋里了·)

2.两个线程,两把锁

线程1获取到锁A

线程2获取到锁B

接下来,1尝试获取B,2尝试获取A,此时会出现死锁

java 复制代码
public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                // sleep 一下, 是给 t2 时间, 让 t2 也能拿到 B
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 尝试获取 B, 并没有释放 A
                synchronized (B) {
                    System.out.println("t1 拿到了两把锁!");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (B) {
                // sleep 一下, 是给 t1 时间, 让 t1 能拿到 A
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 尝试获取 A, 并没有释放 B
                synchronized (A) {
                    System.out.println("t2 拿到了两把锁!");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

3.N个线程M把锁

线程一执行到22行

线程二执行到37行

这些线程安全的类也不是绝对的线程安全,只是自带了锁

内存可见性引起的线程安全

java 复制代码
public class Test3 {
    private  static int flag = 0;
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            while(flag == 0){

            }
            System.out.println("循环结束");
        });
        Thread t2 = new Thread(()->{
            System.out.println("请输入flag的值:");
            Scanner scanner = new Scanner(System.in);
            flag = scanner.nextInt();

        });
        t1.start();
        t2.start();
    }
}

在这个代码中,预期通过t2线程输入的整数,只要输入的不是0,就可以是t1线程结束。

t2要等待用户输入,无论t1先启动还是t2先启动,在等待用户输入的过程中,t1必然循环了很多次了

但是当我们输入非0值,没有输出结果,

原因:内存可见性引起的线程安全,while(flag == 0)由两个指令组成,

1.load读取内存的flag的值到cpu寄存器中

2.拿着寄存器的值与0进行比较

t1线程反复进行1,2操作,由于频繁执行load和条件跳转,load的开销大,并且load的结果又没有变化,此时JVM产生了怀疑,于是JVM就对代码进行了优化,load就被优化掉了,相当于不再重复读内存,直接使用寄存器中之前缓存的值,以提高循环的执行速度,t2修改了内存,但是t1没有看到,就导致了内存可见性问题

为了解决代码优化问题,Java提供了volatile就可以使上述的优化被强制关闭

java 复制代码
private volatile static int flag = 0;
相关推荐
掘金-我是哪吒6 分钟前
分布式微服务系统架构第157集:JavaPlus技术文档平台日更-Java多线程编程技巧
java·分布式·微服务·云原生·架构
飞翔的佩奇14 分钟前
Java项目:基于SSM框架实现的忘忧小区物业管理系统【ssm+B/S架构+源码+数据库+毕业论文+开题报告】
java·数据库·mysql·vue·毕业设计·ssm框架·小区物业管理系统
RainbowSea32 分钟前
跨域问题(Allow CORS)解决(3 种方法)
java·spring boot·后端
掘金-我是哪吒33 分钟前
分布式微服务系统架构第155集:JavaPlus技术文档平台日更-Java线程池实现原理
java·分布式·微服务·云原生·架构
RainbowSea36 分钟前
问题 1:MyBatis-plus-3.5.9 的分页功能修复
java·spring boot·mybatis
前端 贾公子39 分钟前
monorepo + Turborepo --- 开发应用程序
java·前端·javascript
不学会Ⅳ1 小时前
Mac M芯片搭建jdk源码环境(jdk24)
java·开发语言·macos
虫小宝1 小时前
高佣金返利平台监控体系建设:APM、链路追踪与佣金异常预警系统技术实现
java
sniper_fandc2 小时前
SpringBoot系列—入门
java·spring boot·后端
代码的余温3 小时前
Maven引入第三方JAR包实战指南
java·maven·jar