[转]Java 基础 之 Synchronized 锁升级

2022-10-05 487点热度 0人点赞 0条评论

synchronize是平时用得比较多的多线程问题的解决方案,一般说存在多线程问题,加个锁,就用synchronize吧,简单,方便。今天,就来深入了解一下synchronize知识点。

Synchronized可以加在对象上,也可以加在函数上。

Synchronized 介绍

1:Synchronized. 加在对象上

表示对这个对象加锁,当有线程进入函数后先获取对象的锁,如果获取到锁,进入同步块执行,获取失败,放入阻塞队列中。

public void getOneThings(){
    synchronized (object) {
        int x = Wharehows.number;
        if (0 == x) {
            System.out.print("#false");
        } else {
            System.out.print("#true");
            Wharehows.number = 0;
        }
    }
}

2:Synchronized.加在类的普通函数上

表示对这个当前对象上加锁( 对this加锁 ),

public synchronized void getOneThings(){
        int x = Wharehows.number;
        if (0 == x) {
            System.out.print("#false");
        } else {
            System.out.print("#true");
            Wharehows.number = 0;
        }
}

3:Synchronized.加在类的static函数上

表示对这个类加锁( 对class加锁 )

public static synchronized void getOneThings(){
        int x = Wharehows.number;
        if (0 == x) {
            System.out.print("#false");
        } else {
            System.out.print("#true");
            Wharehows.number = 0;
        }
}

多线程内存操作

当线程被创建后,jvm就会为每个线程单独分配一块属于自己的运行内存,并把需要的变量从内存中copy一份到自己的工作内存,操作完成后再会写到内存中。当多线程操作内存中的共享变量时,每个线程都会copy一份在自己的内存中。如果不加锁就会导致T1线程已经修改了,但是T2未及时读取到导致操作异常。当我们加锁后,T2线程不能进入同步块也不能copy内存中的共享变量。只有等T1运行结束并把共享变量写回到内存后,才会通知T2开始运行。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

Synchronized 的锁升级

Java对象一般由二部分组成,一部分是mark word,主要存放的是该对象的hashcode值和锁的信息,另一部分是存放该对象的class metadata信息。

在mark word中会用2个bit来记录锁的状态(偏向锁:01, 轻量级锁:00, 重量级锁:10)

1:无锁-〉偏向锁

对象刚创建时对象头中的偏向锁位设置为0 ,锁状态设置01,初始化状态是无锁。当第一个线程进入同步块时,会检查锁标识位是否为01,是否是偏向锁,发现该对象是无锁状态然后将该对象占为己有,修改对象头中的threadID为自己的threadID,同时将偏向锁位设置1(即:偏向锁)。

当重新创建一个对象时,系统会将该对象的锁状态默认设置成01(可偏向的状态)同时将markword中的指针位设置成0(该对象没有被任何线程锁住)

当有一个线程试图获取该对象锁时,会先判断该对象的锁状态,如果锁状态是可偏向的并且markword中的线程ID是0 则直接占用该对象并将自己的线程id写入该对象的markword中。

2:偏向锁 -> 轻量级锁

如果线程在试图获取该对象锁时,发现锁状态是可偏向的并且markword中的线程ID不是0 也不是自己的时候,说明已经有另外一个线程在占用该对象了,jvm就会将获取偏向锁的线程挂起,并取消偏向锁,然后判断该线程是否活着,如果活着将对象锁升级成轻量级锁释放被阻塞的线程让其进入轻量级锁的执行路径继续执行。若线程已经死了,将该对象恢复成可偏向状态(无锁)。

升级为轻量级锁后: 1)jvm会在线程栈中创建一块内存区域用来存放锁记录,然后再把资源对象的markword 复制到线程的工作栈中。2)修改Object中的锁标识记录为00。3) 将线程栈中存放markword的地址写入到object中

3:轻量级锁 -> 重量级锁

当有一线程试图获取资源对象锁时,会尝试使用CAS操作将资源对象的markword复制到工作栈中,如果复制成功则表示获取了资源对象的锁,则进入同步代码块执行同步代码,执行完成后,替换markword内容并释放锁。如果CAS操作失败,当前线程自动切成自旋模式来自旋获取资源锁,如果自旋获取锁失败,资源对象锁膨胀为重量级锁同时将锁状态修改为10。然后将该线程阻塞。Jvm恢复拥有资源锁的线程并继续执行同步代码块。

什么是自旋锁

所谓自旋锁就是线程在获取锁的时候,如果锁被其他线程持有,则当前线程将循环等待,直到获取到锁。也就是说,一直执行CAS操作(就是一个while循环一直做CAS操作)。自旋锁等待期间,线程的状态不会改变,线程一直是用户态并且是活动的(active).下面我们就简单地模拟一下自旋锁:

AtomicReference<Integer> atomF =  new AtomicReference<>();

public void spingGetLock(){
    while(atomF.compareAndSet(0, 1)){
        System.out.println("Success get lock!");
    }
}

admin

这个人很懒,什么都没留下

文章评论

您需要 登录 之后才可以评论