synchronized底层原理、volatile关键字分析

synchronized锁

synchronized锁用法

// synchronized锁底层原理
public class SynchronizedTest03 {
  //修饰静态方法(同步方法)
  //代码块0(锁定的是当前类)
  public synchronized static void access0(){
    try {
      TimeUnit.*MINUTES*.sleep(1);
      System.*out*.println(Thread.currentThread().getName()+" is running");
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  //修饰非静态方法(同步方法)
  //代码块1(锁定的是当前对象的调用者)
  public synchronized void access1(){
    try {
      TimeUnit.MINUTES.sleep(1);
      System.out.println(Thread.currentThread().getName()+" is running");
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  //修饰非静态方法(同步代码块)
  //代码块2(锁定的是对象)this指的是当前对象
  public void access2(){
    synchronized(this){
      try {
        synchronized (this){
          TimeUnit.*MINUTES*.sleep(1);
        }
        System.*out*.println(Thread.currentThread().getName()+" is running");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  //代码块3(锁定的是CLASS类)(同步代码块)
  public void access3(){
    synchronized(SynchronizedTest03.class){//ClassLoader  class  -->  “堆”区生成一个Class对象:所有的对象
      //有Class对象的所有的对象都共同使用这一个锁
      try {
        TimeUnit.MINUTES.sleep(1);
        System.out.println(Thread.currentThread().getName()+" is running");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  public static void main(String[] args) {
    SynchronizedTest03 demo = new SynchronizedTest03();
    for (int i = 0; i < 5; i++) {
      new Thread(demo::access2).start();
    }
  }
}

锁的分类:

1、对象锁

synchronized(this|object) {} 修饰非静态方法

在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就***是 Java 对象的锁***,通常会被***称为“内置锁”或“对象锁”***。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。

***monitor对象(监视器)***:

​ A、某一线程占有这个对象的时候,先看monitor 的计数器是不是0,如果是0还没有线程占有,这个时候线程占有这个对象,并且对这个对象的monitor+1;如果不为0,表示这个线程已经被其他线程占有,这个线程等待。当线程释放占有权的时候,monitor-1;

​ B、同一线程可以对同一对象进行多次加锁,加一次锁就monitor+1,可重入性。

2、类锁

synchronized(类.class) {} 修饰静态方法

在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即***类的 Class 对象锁***。每个类只有一个 Class 对象,所以每个类只有一个类锁。

synchronized原理分析

1、线程堆栈分析(Jconsole、Jstack pid)

上面的程序我们启动5个线程,每个线程睡眠1分钟,我们使用Jconsole软件进行查看线程。

image-20220210162920845

image-20220210162935305

我们可以看到有5个线程,一个线程正在等待,其他4个线程都是阻塞状态。

image-20220210163012530

使用Jstack pid查看线程:

image-20220210163030048

image-20220210163048315

2、JVM指令分析

对当前类进行反编译:javap -v SynchronizedTest03

找到我们写的access0、access1、access2、access3方法:其中access0、access1原理相同(锁的方法),access2、access3原理相同(锁的代码块)。

A、锁的代码块(用monitor进行加锁解锁)

image-20220210163104672

【问题】为什么会有两个monitorexit?

答:第一个是正常出口,第二个是异常出口。

B、锁的方法(用标记进行加锁、ACC_SYNCHRONIZED)

image-20220210163121963

3、使用synchronized注意的问题

(1)与moniter关联的对象不能为空

(2)synchronized作用域太大

(3)不同的monitor企图锁相同的方法

(4)多个锁的交叉导致死锁

Java虚拟机对synchronized的优化

对锁优化加入:偏向锁、轻量级锁、重量级锁(等待时间长)

每一个monitor存放在一个实例对象里面,每一个对象都和monitor进行关联,主要与对象头进行关联,关联主要根据锁的状态进行。

image-20220210163140564

image-20220210163151810

无锁状态:没有加锁

偏向锁:在对象第一次被某一线程占有的时候,是否偏向锁置1,锁表01,写入线程号,当其他的线 程访问的时候,会去竞争,竞争失败(升级到轻量级锁)、很多次被第一次占有它的线程获取次数多–竞争成功

轻量级锁:线程有交替适用,互斥性不是很强,CAS失败,00

重量级锁:强互斥,10,等待时间长

自旋锁:竞争失败的时候,不是马上转化级别,而是执行几次空循环

锁消除:JIT在编译的时候吧不必要的锁去掉

volatile关键字

机器硬件CPU与JMM模型

(1)CPU Cache模型

img

image-20220210163300128

多核CPU的情况下:

image-20220210163322102

(2)CPU缓存的一致性问题

每一个CPU到内存的cache互不干涉,容易导致数据不一致问题

每一个CPU修改内存数据的步骤:

1、从内存中把数据读到Cache中

2、在Cache中更新数据

3、把更新的结果刷新到内存

解决方案:

A)总线加锁(粒度太大)

B)MESI(缓存一致性协议)

a.读操作:不做任何事情,把Cache中的数据读到寄存器

b.写操作:发出信号通知其他的CPU讲改变量的Cache line置为无效,其他的CPU要访问这个变量的时候,只能从内存中获取。(高速缓存其实就是一组称之为缓存行(cache line)的固定大小的数据块,其大小是以突发读或者突发写周期的大小为基础,当从内存中取单元到cache中时,会一次取一个cacheline大小的内存区域到cache中,然后存进相应的cacheline中,最小单元)

(3)Java内存模型

image-20220210163340065

\1) 主存中的数据所有线程都可以访问(共享数据)

\2) 每个线程都有自己的工作空间,(本地内存、私有数据)

\3) 工作空间数据:局部变量、内存的副本

\4) 线程不能直接修改内存中的数据,只能读到工作空间来修改,修改完成后刷新到内存

JMM和CPU内存直接的关系:

image-20220210163352423

Volatile关键字的语义分析

volatile作用:让其他线程能够马上感知到某一线程多某个变量的修改

(1)保证可见性

对共享变量的修改,其他的线程马上能感知到

不能保证原子性,如:读、写、(i++)

(2)保证有序性

重排序(编译阶段、指令优化阶段),输入程序的代码顺序并不是实际执行的顺序,重排序后对单线程没有影响,对多线程有影响。

Happens-before中的volatile规则:

对于volatile修饰的变量:

(1)volatile之前的代码不能调整到他的后面

(2)volatile之后的代码不能调整到他的前面(as if seria)

(3)位置不变化

(3)volatile的原理和实现机制(锁、轻量级)

HSDIS --> 反编译 --> 汇编

Volatile的使用场景

(1)状态标志(开关模式)

public class ShutDownDemo extends Thread{
  private volatile boolean started=false;
  @Override
  public void run() {
    while(started){
      // 代码逻辑
    }
  }
  public void shutdown(){
    started=false;
  }
}

(2)双重检查锁定(DCL:double-checked-locking)

public class SingletonDemo {

// volatile的作用是可以使内存中的数据对象线程可见
// 主内存对线程是不可见的,添加volatile关键字之后,主内存对线程可见
// 使用volatile后直接跳过工作内存复制一份的情况,进行主内存操作判断是否为空
// 如果不加volatile,可能会引起指令重排,导致空指针异常
  private volatile static  SingletonDemo singleton;
  private SingletonDemo(){
    System.out.println("创建了单例对象!");
  }
  // double check
  public static SingletonDemo getInstance(){
    // 必须是一个唯一的对象
    if(singleton == null){
      synchronized (SingletonDemo.class){
        if(singleton == null){
          singleton = new SingletonDemo();
        }
      }
    }
    return singleton;
  }
}

分析:不加volatile关键字的影响

修改代码:

private Socket socket;
private static SingletonDemo singleton;
private SingletonDemo(){
  socket = new Socket();
  singleton = new SingletonDemo();
  socket.toString();
}

在初始化的时候,根据JVM优化,可能会引起指令重排,导致socket.toString()在没有被初始化的情况下优先执行,就会导致空指针异常,所以必须加volatile关键字,这样singleton = new SingletonDemo();前面的代码就不会被改变执行顺序不会出现空指针了。

(3)需要利用顺序性

volatile与synchronized的区别

(1)使用上的区别:volatile只能修饰变量,synchronized只能修饰方法和语句块

(2)对原子性的保证:synchronized可以保证原子性,volatile不能保证原子性

(3)对可见性的保证:都可以保证可见性,但实现原理不同

volatile对变量加了lock,synchronized使用monitorEnter和monitorexit

(4)对有序性的保证

volatile能保证有序,synchronized可以保证有序性,但是代价(重量级)并发退化到串行

(5)其他

3)需要利用顺序性

volatile与synchronized的区别

(1)使用上的区别:volatile只能修饰变量,synchronized只能修饰方法和语句块

(2)对原子性的保证:synchronized可以保证原子性,volatile不能保证原子性

(3)对可见性的保证:都可以保证可见性,但实现原理不同

volatile对变量加了lock,synchronized使用monitorEnter和monitorexit

(4)对有序性的保证

volatile能保证有序,synchronized可以保证有序性,但是代价(重量级)并发退化到串行

(5)其他

synchronized引起阻塞、volatile不会引起阻塞

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Please Sit Down

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值