JUC学习——Synchronized底层原理

java对象头

以32位虚拟机为例
普通对象(klass word类型指针)
在这里插入图片描述
数组对象
在这里插入图片描述
其中Mark world结构为

在这里插入图片描述
其中1代表启用了偏向锁,0表示没启用

Monitor

被翻译为监视器或管程(syschronized底层实现原理)
每个java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象的Mark word中被设置指向Monitor对象的指针
Monitor的结构如下
在这里插入图片描述
在这里插入图片描述

流程:

  • 刚开始Monitor中Owner为null
  • 当Thread执行synchronized(obj)就会将Owner置为Thread2,Monitor中只能又一个Owner,同事obj对象中mark wrod变为指向monitor的指针
  • 在Thread-2上锁的过程中,如果Thread3,Thread-4也来执行同步代码块,就会进入EntryList BLOCKED
  • Thread-2执行完同步代码块的内容,然后唤醒EntryList等待的线程来竞争锁,竞争的是非公平的
  • 图中waitSet中的Thread-0, Thread-1是之前获得过锁,但条件按不满足进入WAITING状态的线程
    注意:1. synchronized必须是进入同一个对象的monitor才有上述效果
  1. 不加synchronized的对象不会关联监视器,不存从以上规则

轻量级锁

使用场景:如果一个对象对然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争)那么可以使用轻量级锁来优化
轻量级锁对使用者是透明的
假设有两个方法同步块,利用同一个对象加锁

static final Object obj = new Object();
    
    public static void method1(){
        synchronized (obj){
            //同步块A
            method2();
        }
    }
    
    public static void method2(){
        synchronized (obj){
            //同步块B
        }
    }

创建锁记录(Lock Record)对象,每个线程的帧栈都会包含一个锁记录的结构,内部可以存储缩影对象的Mark Word
在这里插入图片描述

  • 让锁记录中Object reference只想锁对象,并尝试用cas替换Object的Mark Word,将Mark Word的值存入锁记录
    在这里插入图片描述
  • 如果cas替换成功,对象头中存储了锁记录地址和状态00,表示由该线程给对象加锁,这时图示如下
    在这里插入图片描述
  • 如果cas失败,有两种情况
  • 如果是其他线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程
  • 如果是自己执行了synchronized锁冲入,那么再添加一条Lock Record作为重入的计数
    在这里插入图片描述
  • 当退出synchronized代码块(解锁时)如果有取值为null的锁记录,表示有重入,这时有重入,这时重置锁记录,表示重入计数减1
  • 当退出synchronized代码块(解锁时)所记录的值不为null,这时使用cas讲Mark Word的值回复给对象
  1. 成功,则解锁成功
  2. 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

锁膨胀

如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其他线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁
当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁
在这里插入图片描述
这时Thread-1加轻量级锁失败,进入锁膨胀过程

  1. 即为Object对象申请Monitor锁,让Object指向重量级锁地址
  2. 然后进入Monitor的EntryList BLOCKED
    在这里插入图片描述
    当 Thread-0退出同步块解锁时,使用cas讲Mark Word的值恢复给对象头,失败,这时会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中BLOCKED线程

自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功,即此时持锁线程已经退出了同步块,释放了锁,这时当前线程就可以避免阻塞

  • 在java6之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性很高,就多自旋几次,反之就少自旋甚至不自旋,总之,比较智能
  • 自旋会占用cpu时间,单核cpu自旋就是浪费,多喝cpu自旋才能发挥优势
  • java7之后不能控制是否开启自旋功能

偏向锁

轻量级锁在没有竞争时,每次重入仍然需要执行CAS操作
java6中引入了偏向锁来进一步优化:只有第一次使用CAS讲线程ID设置到对象的MARK WORD头,之后发现这个线程id是自己就表示没有竞争,不用重新cas,以后只要不发生竞争,这个对象就归该线程所有
在这里插入图片描述

在这里插入图片描述

  • 一个对象开启了偏向锁(默认开启),那么对象创建后,markword值为0x05即最后三位为101,这是它的thread/epoch/age都为0
  • 偏向锁是默认延迟的,不会再程序启东时立即生效,如果想避免延迟,可以加vm参数 -XX:BiasedLockingStartupDelay=0来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword值为0x01即最后三位为001,这时它的hashcode、age都为0,第一次用到hashcode时才会赋值

撤销-调用对象hashCode

调用了对象的hashcode,但偏向锁的对象markword中存储的是线程id, 如果hashCode会导致偏向锁被撤销

  • 轻量级锁会在锁记录中记录hashcode
  • 重量级锁会在Monitor中记录hashcode

交错的不同线程共同申请该锁则会升级为轻量级锁,有竞争的则升级为重量级锁
此外调用wait/notify也会升级为重量级锁

批量重偏向

如果对象虽然被多个线程访问,但没有竞争,这是偏向了线程T1的对象仍有机会重新偏向T2,重偏向会重置对象的ThreadID
当撤销偏向锁预支超过20次后,jvm会觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程

批量撤销

当撤销偏向锁阈值超过40次后,jvm会这样觉得,自己确实偏向错了,根本就不该偏向,于是整个累的所有对象都变为不可偏向的,新建的对象也是不可偏向的

锁消除

如果一个对象被发现只能从一个线程被访问到,那么这个对象的操作可以不考虑同步

原理值wait/notify

在这里插入图片描述

  • Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态
  • BLOCKED和WAITING的线程都处于阻塞状态,不占用时间片
  • BLOCKED会在Owner线程释放锁时唤醒
  • WAITING线程会在Owner调用notify或notifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList重新竞争

保护性暂停

join底层即为使用wait的保护性暂停来实现

class GuardedObject {
    private Object response;

    public Object get(long timeout) {
        synchronized (this) {
            long begin = System.currentTimeMillis();

            long passTime = 0;
            while(response == null){
                long waitTime = timeout - passTime;
                if(passTime >= timeout){
                    break;
                }
                try {
                    this.wait(waitTime);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
                passTime = System.currentTimeMillis() - begin;
            }
            return response;
        }
    }
}

Park & Unpark

他们是LockSupport类中的方法

//暂停当前线程
LockSupport.park();
//恢复某个线程的运行
LockSupport.unpark(暂停线程对象);

与Object的wait&notify相比

  • wait, notify和notifyAll必须配合Object Monitor一起使用,而park,unpark不必
  • park&unpark是以线程为单位来阻塞和唤醒线程的,而notify只能随机唤醒一个线程
  • park和unpark可以先unpark,而wait&notify不能先notify

原理之park和unpark

每个线程都有自己的一个Parker对象,由三部分组成_counter,_cond,_mutex打个比喻
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值