是什么synchronized是java同步锁,同一时刻多个线程对同一资源进行修改

synchronized是什么

synchronized是java同步锁,同一时刻多个线程对同一资源进行修改时,能够保证同一时刻只有一个线程获取到资源并对其进行修改,因此保证了线程安全性。

synchronized可以修饰方法和代码块,底层实现的逻辑略有不同。

Object obj=new Object();
synchronized(obj){
    //do soming
}

编译后的代码为:

 ...
10 astore_2
11 monitorenter
12 aload_2
13 monitorexit
14 goto 22 (+8)
17 astore_3
18 aload_2
19 monitorexit
20 aload_3
21 athrow

图片[1]-是什么synchronized是java同步锁,同一时刻多个线程对同一资源进行修改-唐朝资源网

22 return

当代码执行到synchronize(obj)时,对应的字节码为monitorenter进行加锁操作,代码执行完后就是monitorexit进行锁的释放。两个 monitorexit是正常退出和异常退出两种情况下锁的释放。

public synchronized void test1(){
  //do somthing
}

当修饰方法时是在编译后的字节码上加上了synchronized的访问标识

Monitor机制

Monitor是一种同步机制,它的作用是保证同一时刻只有一个线程能访问到受保护的资源,JVM中的同步是基于进入和退出监视对象来实现的,是synchronized的底层实现,每个对象实例都是一个Montor对象,Monitor对应的是底层的MonitorObject,是基于操作系统的互斥mutex实现的。

ObjectMonitor中有几个关键属性

属性描述

_owner

指向持有ObjectMonitor对象的线程

_WaitSet

存放处于wait状态的线程队列

_EntryList

存放处于等待锁block状态的线程队列

_recursions

锁的重入次数

_count

用来记录该线程获取锁的次数

进入monitor,被分配到Entry List中,等待持有锁的线程释放锁,当线程获取到锁后,是锁的持有者,owner指向当前线程当线程进行wait时进入Wait Set,等待锁的持有者进行唤醒。synchronized锁的实现原理当代码执行到被synchronized修饰的代码块或方法时,首先通过monitor去获取对象实例的锁当获取到锁时,会在对象实例的对象头上添加锁标识位没有获取到锁的线程,会进行到对对象实例的entry list中进行等待持有锁的线程的业务处理完后通过修改对象头上锁标识位来进行释放锁当线程进行wait操作时,当前也会释放锁,然后进行wait set区等待被唤醒在entry list中处理等待的线程再次进行锁的竞争Mark Word

一个对象的创建要经过这几步:

加载:如果对象的Class还没加载链接:由符号引用转换为地址引用初始化:执行Class的方法开辟一个地址空间(可以使用TLAB技术进行优化,避免通过CAS产生的资源竞争)初始化对象头信息执行代码的方法

7.返回对象地址

一个对象有:对象头、实例数据和对齐填充三部分组成

对象头有:对象标记(Mark Word)和类型指针组成,如果对象是数组,对象头中还有数组的长度

在64位系统中,对象标记占8个字节,类型指针占8个字节,对象头共点16个字节

对象标记中有hashcode码、GC年龄、锁标记组成

每个字节占8位,8个字节的MarkWord共占64位

在无锁的状态下,前25位没有使用,紧接着的32位保存了对象的hashcode,在1位未使用,后面的4位对象的GC年龄,后面的3位是锁标记位。为什么GC年龄不能超过16

在MarkWord中可以看出GC年龄标记只有4位,二进制表示就是:1111,对应的十进制就是15。

下面通过jol进行查看MarkWord的信息,


  org.openjdk.jol
  jol-core
  0.9

无锁时

import org.openjdk.jol.info.ClassLayout;
public class MarkWordTest {
    public static void main(String[] args) {
        Hummy hummy=new Hummy();
        int hashCode = hummy.hashCode();
        System.out.println(hashCode);
        System.out.println("二进制:"+Integer.toBinaryString(hashCode));
        System.out.println("十六进制: "+Integer.toHexString(hashCode));
        System.out.println(ClassLayout.parseInstance(hummy).toPrintable());
    }
}

class Hummy{}

打印出的结果如下:

可以看到对象的hashcode是:6f496d9f,可以在左边的Value的找到hashcode值,只不过是反过来的。

最后1字节的00000001包含了gc年龄和锁标记位。

加锁时

import org.openjdk.jol.info.ClassLayout;
public class MarkWordTest {
    public static void main(String[] args) {
        //java -XX:BiasedLockingStartupDelay=0
        Hummy hummy=new Hummy();
        synchronized (hummy){
            System.out.println(ClassLayout.parseInstance(hummy).toPrintable());
        }
    }
}
class Hummy{}

最后一个00000101的最后3位101表示偏向锁

synchronized的优化

jdk1.6之前只有重量级锁,面在java1.6之后对synchronized的锁进行了优化,有偏向锁、轻量级锁、重量级锁,主要是因为重量级锁需要用到操作系统mutex,操作系统实现线程之间的切换需要从用户态到内核态的,成本非常高。

锁锁标识场景

无锁

001

不受保护时

偏向锁

101

只有一个线竞争时

轻量级锁

00

竞争不激烈时

重量级锁

10

竞争非常激烈

锁升级的过程:

当访问同步代码时,首先判断markword是否是无锁状态(001)或者在偏向锁状态下markword中的线程id与当前线程id是否一样,如果是则把当前线程id通过CAS的方式设置到markword中设置成功后则锁标记修改为(101),升级为偏向当前线程的编向锁(101),执行同步内的方法如果失败,则由jvm进行偏向锁的撤消当持有锁的线程运行到安全点时,检查偏向锁的状态当持有锁的线程已退出同步方法时,释放原线程持有的锁,变成无锁状态,到1处执行当持有锁的线程还在同步代码中,则升级锁为轻量级锁(00),当前线程持有,另个线程通过CAS的方法进行获取锁,当自旋到一定次数(20)时,则升级为重量级锁(10),进入堵塞状态。

© 版权声明
THE END
喜欢就支持一下吧
点赞298赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容