栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

由浅到深对synchronized的理解

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

由浅到深对synchronized的理解

文章目录
  • synchronized关键字的用法
  • Synchronized的作用
  • synchronized调优
    • java对象头
    • Monitor对象
    • Synchronized字节码
    • synchronized优化
      • 锁升级
        • 偏向锁
        • 轻量级锁
        • 重量级锁
      • 锁消除
      • 锁粗化
  • 多线程中 synchronized 锁升级的原理
  • synchronized 为什么是非公平锁?

synchronized关键字的用法
  • 修饰普通方法:作用于当前对象实例,进入同步代码前要获得当前对象实例的锁
  • 修饰静态方法:作用于当前类,进入同步代码前要获得当前类对象的锁,synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁
  • 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁

特别注意:

①如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁

②尽量不要使用 synchronized(String s) ,因为JVM中,字符串常量池具有缓冲功能

Synchronized的作用
  1. 原子性:确保线程互斥的访问同步代码;
  2. 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 “对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
  3. 有序性:有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”。
synchronized调优 java对象头

java对象由两部分组成:对象头和局部变量

以32位虚拟机为例

普通对象

object Header (64 bits)

  • Mark word(32 bits)
  • kclass word(32 bits) 每个对象有一个类型(teacher类型,student类型),通过class word指向从属的class,找到自己的类对象

mark word结构:

数组对象

object Header (92 bits)

  • Mark word(32 bits)
  • kclass word(32 bits) 每个对象有一个类型(teacher类型,student类型),通过class word指向从属的class,找到自己的类对象
  • array length (占4个字节)

在java中,int占4个字节,Integer占8(对象字节数)+4(数字字节数)

Monitor对象

Monitor:监视器或管程,每个java对象都可以关联一个Monitor对象,如果使用Synchronized给对象加锁之后,该对象的Mark word就被指向Monitor对象

Monitor结构

  1. 开始时,Monitor对象中Owner为空
  2. Thread2执行Synchronized修饰的同步代码块时就会将Monitor的所有者Owner置为Thread2,Monitor中只能有一个Owner
  3. 在Thread2上锁后,Thread3,Thread4也来执行Synchronized修饰的同步代码块,就会进入EntryList阻塞队列
  4. Thread2执行同步代码块之后,然后唤醒EntryList阻塞队列中的线程来竞争锁(非公平)

Waitset中的Thread0和Thread1是之前获得过上锁,但是条件不满足进入WAITING状态的线程

注意

  • Synchronized必须是进入同一对象的Monitor才有上述效果
  • 不加Synchronized的对象不会关联Monitor
Synchronized字节码
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: getstatic     #2                  // lock引用 (synchronized开始)
         3: dup
         4: astore_1						 //lock引用复制一份(解锁使用)
         5: monitorenter					 //将lock对象MarkWord置为Monitor指针
         6: getstatic     #3                  // 准备常熟I
         9: iconst_1
        10: iadd
        11: putstatic     #3                  // Field count:I
        14: aload_1                           //拿到复制的lock
        15: monitorexit                       //将lock对象markword重置,唤醒EntryList 还原之前的对象头(hashcode,分代年龄)
        16: goto          24                 //执行24行(正常执行情况) 如果发生异常,执行下面代码
        19: astore_2                          //将异常对象存储到临时变量
        20: aload_1
        21: monitorexit                         将lock对象markword重置,唤醒EntryList 还原之前的对象头(hashcode,分代年龄)
        22: aload_2
        23: athrow
        24: return
 Exception table:
         from    to  target type
             6    16    19   any
            19    22    19   any

tips:synchronized在字节码中,考虑的非常完善,不论同步代码块是正常运行还是发生异常,都会帮我们解锁,不会发生死锁

synchronized优化

在锁对象的对象头中有一个threadid字段,当一个线程第一次访问同步代码块时,threadid为空,jvm让其持有偏向锁,并将threadid设置为当前线程id,再次进入同步代码块,会判断threadid是否与其线程id一直,如果一直就直接使用该锁对象,如果不一致,升级偏向锁到轻量级锁,通过自旋一定的次数来获取锁,执行一定次数后,还没有正常获取到要使用的对象,就变为重量级锁,线程进入阻塞状态

锁升级

偏向锁->轻量级锁->重量级锁

偏向锁

一个线程获得了锁,那么锁就进入偏向模式,此时markword变为偏向锁结构,当前线程再次请求锁时,不用在做任何同步操作,获取锁的过程的只需检查mark word的锁标记为偏向锁以及当前线程ID等于ThreadId即可,这样就可以省去大量有关的申请操作

轻量级锁

当存在第二个线程申请同一个锁对象时,偏向锁就会立即升级为轻量级锁,这里的第二个线程只是申请锁,不存在两个线程同时申请锁,不存在两个线程同时竞争锁,一前一后地交替执行同步块

重量级锁

同一时间有多个线程竞争锁时,锁就会升级为重量级锁,此时锁带来的开销就越大

锁消除

锁消除是虚拟机另一种锁的优化方式,在JIT编译时,对运行上下文进行扫描时,去除不存在竞争的锁

运行两个method方法时,执行效率是一样的,

public static void methods() {
    Object  lock = new Object();
  synchronized (lock){
      System.out.println("hello word");
     
  }
}
 public static void methods1() {
     Object  lock = new Object();
     System.out.println("hello word");
}
锁粗化

锁粗化是扩大锁的范围,避免重复加锁和释放锁

多线程中 synchronized 锁升级的原理

synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。

锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。

synchronized 为什么是非公平锁?

synchronized 的非公平其实在源码中应该有不少地方,因为设计者就没按公平锁来设计,核心有以下几个点:

1)当持有锁的线程释放锁时,该线程会执行以下两个重要操作:

  1. 先将锁的持有者 owner 属性赋值为 null
  2. 唤醒等待链表中的一个线程(假定继承者)。

在1和2之间,如果有其他线程刚好在尝试获取锁(例如自旋),则可以马上获取到锁。

2)当线程尝试获取锁失败,进入阻塞时,放入链表的顺序,和最终被唤醒的顺序是不一致的,也就是说你先进入链表,不代表你就会先被唤醒。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/955974.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号