JUC专题 - 并发编程带来的安全性挑战之同步锁

1 原子性问题

1.1 提出问题

首先我们看一段代码:

ini 复制代码
public class Demo {
    int i = 0;
    public void incr(){
        i++;
    }
    public static void main(String[] args) {
        Demo demo = new Demo();
        Thread[] threads=new Thread[2];
        for (int j = 0;j<2;j++) {
            threads[j]=new Thread(() -> { // 创建两个线程
                for (int k=0;k<10000;k++) { // 每个线程跑10000次
                    demo.incr();
                }
            });
            threads[j].start();
        }
        try {
            threads[0].join();
            threads[1].join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(demo.i);
    }
}

两个线程同时操作一个共享变量,不考虑线程安全问题应该输出20000,但是实则 每次输出的都不一样,都小于20000,这是为什么呢?

我们来看下这段代码执行底层:

出现上述问题的原因就是该指令的执行没有一气呵成,假如执行过程中切换线程,就会导致线程2对共享变量操作了之后没有感知,从而对源数据进行操作,进而引发线程安全问题

1.2 解决问题

比较简单的方法就是对incr方法进行加锁:

这样就可以保证对i的原子性操作

2 Synchronized

2.1 Synchronized的使用

synchronized有三种方式来加锁,不同的修饰类型,代表锁的控制粒度:

  1. 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
  2. 静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
  3. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

2.2 Synchronized的原理

Synchronized是如何实现锁的,以及锁的信息是存储在哪里? 就拿上面分析的图来说,线程A抢到锁了,线程B怎么知道当前锁被抢占了,这个地方一定会有一个标记来实现,而且这个标记一定是存储在某个地方。

Markword对象头

对象头中和锁相关联的就是:

  • 最后三位标识,可以用来判断是哪一种锁
  • 存储指针的区域,根据情况存储指向轻量级锁和重量级锁的指针

那么当是重量级锁的情况下,指针指向的是什么东西呢? 其实是一个监视器monitor:

monitor主要由三个部分组成:

  • owner 存储当前获取锁的线程
  • EntryList 抢占锁失败进入阻塞状态
  • WaitSet 调用wait方法处于Waiting状态的线程

2.3 锁的升级

Jdk1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

这么设计的目的,其实是为了减少重量级锁带来的性能开销,尽可能的在无锁状态下解决线程并发问题,其中偏向锁和轻量级锁的底层实现是基于自旋锁,它相对于重量级锁来说,算是一种无锁的实现。

  • 默认情况下是偏向锁是开启状态,偏向的线程ID是0,偏向一个Anonymous BiasedLock
  • 如果有线程去抢占锁,那么这个时候线程会先去抢占偏向锁,也就是把markword的线程ID改为当 前抢占锁的线程ID的过程,如果有线程竞争,这个时候会撤销偏向锁,升级到轻量级锁,线程在自己的线程栈帧中会创建一个LockRecord,用CAS操作把markword设置为指向自己这个线程的LR的指针,设置成功后表示抢占到锁。
  • 如果竞争加剧,比如有线程超过10次自旋(-XX:PreBlockSpin参数配置),或者自旋线程数超过 CPU核心数的一般(在1.6之后,加入了自适应自旋Adapative Self Spinning. JVM会根据上次竞争 的情况来自动控制自旋的时间),升级到重量级锁
相关推荐
盖世英雄酱5813616 小时前
Java 组长年终总结:靠 AI 提效 50%,25 年搞副业只赚 4k?
后端·程序员·trae
+VX:Fegn089517 小时前
计算机毕业设计|基于springboot + vue在线音乐播放系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
code bean17 小时前
Flask图片服务在不同网络接口下的路径解析问题及解决方案
后端·python·flask
+VX:Fegn089517 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
努力的小郑17 小时前
2025年度总结:当我在 Cursor 里敲下 Tab 的那一刻,我知道时代变了
前端·后端·ai编程
颜淡慕潇19 小时前
深度解析官方 Spring Boot 稳定版本及 JDK 配套策略
java·后端·架构
Victor35619 小时前
Hibernate(28)Hibernate的级联操作是什么?
后端
Victor35619 小时前
Hibernate(27)Hibernate的查询策略是什么?
后端
superman超哥20 小时前
Rust 内部可变性模式:突破借用规则的受控机制
开发语言·后端·rust·rust内部可变性·借用规则·受控机制
柒.梧.20 小时前
Spring核心知识全解析:从入门实战到进阶
java·后端·spring