【23】synchronized关键字辨析

(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)好好活就是做有意义的事情.
(8)亡羊补牢,为时未晚
(9)科技领域,没有捷径与投机取巧。
(10)有实力,一年365天都是应聘的旺季,没实力,天天都是应聘的淡季。
(11)基础不牢,地动天摇
(12)写博客初心:成长自己,辅助他人。当某一天离开人世,希望博客中的思想还能帮人指引方向.
(13)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~

synchronized关键字辨析

1.synchronized的实现原理

1.1使用monitorenter和monitorexit指令实现的

(1)monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束和异常处。

(2)每个monitorenter必须有对应的monitorexit与之配对。

(3)任何对象都有一个monitor与之关联

1.2非线程安全与线程安全案例

(1)加锁解决线程安全问题

  • 方法上加锁
  • 代码块加锁
/**
 * 类说明:简单的程序会有线程安全问题吗?
 */
public class SimplOper {

    //计数器
    private volatile long count = 0;

    /**
     * 非线程安全方法
     */
    public void incCount1() {
        count = count + 1;//count++;
    }

    /**
     * 线程安全方法
     */
    public synchronized void incCount2() {
        count = count + 1;//count++;
    }

    /**
     * 线程安全代码块
     */
    public void incCount3() {
        synchronized (this) {
            count = count + 1;//count++;
        }
    }


    //进行累加的线程
    private static class Count extends Thread {

        private SimplOper simplOper;

        public Count(SimplOper simplOper) {
            this.simplOper = simplOper;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                //simplOper.incCount1();//加10000
                //simplOper.incCount2();//加10000
                simplOper.incCount3();//加10000
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SimplOper simplOper = new SimplOper();
        //启动两个线程
        Count count1 = new Count(simplOper); 
        Count count2 = new Count(simplOper);
        count1.start(); 
        count2.start(); 
        Thread.sleep(50);
        System.out.println(simplOper.count);//=20000?
    }

}

(2)java文件在编译为class文件的过程中,会由编译器在class文件中插入monitorenter与monitorexit字节码指令,这两个指令即是synchronized关键字在底层的实现。

  • 执行到monitorenter指令,表示要去获取锁,monitorenter在虚拟机里面它其实是一个对象与其相对应的,即Monitor对象。拿锁就是拿这个Monitor对象的所有权,哪个线程拿到了这个对象的所有权,就表示它进入了这个锁,执行相关业务逻辑。

(3)在同步代码块上加synchronized锁

在这里插入图片描述

(4)在方法上面加synchronized锁

在这里插入图片描述

  • flags中会加上ACC_SYNCHRONIZED标记,表示是一个同步方法,它的底层实现还是通过monitorenter与monitorexit指令实现的。

1.3锁的存放位置

(1)锁放在Java的对象头里面。

(2)当在Java代码里面,new Object();或者是new User();每一个对象在内存里面都要存放数据,除了这些数据之外,还有对象头。

(3)对象头里面包含当前对象一些比较关键的信息,比如GC年龄,hashcode码,MarkWord

(4)对象头里面还包含对象属于哪一个类,这个类上面有哪些相关的属性,由对象头的另一个类进行保存,虚拟机要知道这个对象属于哪个类的时候,通过对象头区域就可以找到这个对象属于哪个类,即属于哪个类的实例,即类型指针ClassPoint,用ClassPoint标明这个对象是哪个类。

(5)对象头包含两个部分

  • MarkWord:对象
  • ClassPoint:对象指针

对象头会随着程序的运行,发生一些变化

在这里插入图片描述

2.为了提升synchronized的性能做的相关优化

(1)在任一时刻,只有一个线程可以拿到锁,其他线程要被阻塞住,意味这个线程会被操作系统挂起,一旦挂起会发生两次上下文切换。

(2)发生上下文操作是比较昂贵的操作,因为CPU需要花5000-20000个指令周期来执行上下文的切换操作。

2.1轻量级锁

在这里插入图片描述

(1)通过CAS操作来加锁和解锁

(2)在轻量级锁的概念中出现了自旋锁的概念。就是在轻量级锁里面,推测可能前一个拿到锁的线程释放锁的过程可能很快,没有必要去进行线程上下文切换的操作,在解锁与拿锁操作上面通过自旋,使用CAS操作加锁与解锁,尝试一下拿锁,拿不到就再尝试多次拿锁,由此来避免加锁解锁过程中的上下文切换。

(3)CAS大部分情况下是处在空转的情况,是消耗CPU的,不停的自旋检测CPU是有消耗的,(但是比上下文切换的消耗要小得多)。

(4)如果加锁的操作是一个很重的操作,比如要等待第三方接口的调用,比如要访问后台服务器,拿到数据,这个时候自旋就很消耗CPU,会导致手机发热。

2.2适应性自旋锁

(1)太多的线程去自旋,会占用CPU资源,自旋的次数一定要做控制,早期JDK中定义为10次,JDK1.6以后,自旋的次数不再是一个固定的值,而是由虚拟机自行进行判断,即由虚拟机动态调整。

(2)现在自旋的时间大概是一个线程上下文切换所需要的时间。

(3)引入轻量级锁最终的目的是避免引入重量级锁时产生上下文切换。如果自旋的时间已经超过了上下文切换的时间,那么使用适应性自旋锁取代重量级锁的操作已经没有必要了。

2.3偏向锁

(1)总是同一个线程获得同一把锁。
(2)轻量级锁里面,不管是什么情况,会将CAS的操作也去掉,测试一下拥有这把锁的是不是当前线程自己,是自己,就直接拿来用,这种锁就称为偏向锁。
(3)偏向锁就是锁的获得者总是偏向于第一个拿到这把锁的线程。
(4)偏向锁的好处就是减少CAS操作来加锁与解锁,只做一次CAS操作,后续不再做CAS操作。

大多数情况下,锁不仅不存在于多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低由此引入了偏向锁。无竞争时不需要进行CAS操作来加锁和解锁。

(1)加锁可能存在多线程竞争的情况,此时一旦出现两个线程或者多个线程,如果线程处在偏向锁的状态,但是第二个线程出现了,它也需要拿这把锁,此时偏向锁开始撤销,开始进行锁的升级,升级为轻量级锁。

(2)将偏向锁撤销,替换成轻量级锁的数据,会出现Stop the World,STW现象,停止世界,因此偏向锁的撤销工作是一个比较消耗资源的工作。

2.4synchronized在实现的时候,JDK所做的优化有哪些?

(1)引入了偏向锁的机制
(2)引入了轻量级锁的机制
(3)锁会根据竞争的不同情况,一点点由偏向锁膨胀为轻量级锁,再由轻量级锁膨胀为重量级锁。

在这里插入图片描述

(1)偏向锁适应的场景是当前线程没有竞争,而且总是由同一个线程获得同一个锁的时候,synchronized关键字会走偏向锁模式。

(2)一旦发生了竞争的情况,偏向锁就会撤销,晋升为轻量级锁,轻量级锁在获取锁的时候,它使用了一个自旋的CAS操作,CAS操作是比较耗费CPU资源的。自旋的次数太多,反而会导致性能下降,于是轻量级锁就会膨胀为重量级锁。

(3)引入阻塞状态,将线程阻塞起来,将CPU释放出来做更多有效的事情,这是synchronized关键字实现的一个例子。

(4)回答synchronized关键做了哪些优化的时候,要说到3点,偏向锁、轻量级锁、自适应的自旋锁。

(5)synchronized在实现的时候都是基于monitorenter和monitorexit指令实现的。进入同步块就意味着执行monitorenter指令,拿到Monitor对象的所有权。

(6)早期使用的是阻塞的方式实现,对于一些轻量级的类似于同步块的操作,这种方式比较重,所以说,synchronized又做了一些相关的优化。

(7)在原有的重量级的基础之上,引入了偏向锁和轻量级锁这两个状态,如果当前synchronized加锁的情况下面,偏向锁对象没有竞争,同一个线程获取锁的时候,进入偏向锁。

(8)一旦产生了竞争, 升级膨胀为轻量级锁,如果轻量级锁的自旋超过一定的次数,又会膨胀为一个重量级锁。

(9)一旦引入了加锁,如果底层实现没有进行优化,就会很重,于是Java开发者就想到了提升虚拟机synchronized的性能,于是底层实现就引入了偏向锁、轻量级锁来避免重量级加锁情况下无谓的上下文切换。

2.5synchronized锁与Lock显示锁应该用哪个好?

如果没有特殊的需求,即业务里面要求可中断拿锁,要尝试拿锁,尽可能的使用synchronized关键字,尽量少用显示锁Lock,除了完成业务有特殊的需求再应用显示锁Lock.

3.打赏鼓励

感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!

3.1微信打赏

在这里插入图片描述

3.2支付宝打赏

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值