Synchronized学习记录(超详解)

目录

1. 定义及作用

2. 使用方式

2.1 常见的三种使用方式如下:

2.2 锁的使用规则

2.3 锁的类型

3. 特点

4.缺点及使用synchronized的注意事项

        4.1 缺陷

        4.2 注意事项

5. synchronized 优化(偏向锁、轻量级锁及重量级锁)

5.1 自旋锁

5.2 偏向锁

5.3 轻量级锁

5.4 锁分配和膨胀过程


1. 定义及作用

定义:synchronized是Java中的一个关键字

作用:synchronized关键字能解决多个线程访问共享资源的同步性问题,能保证被他修饰的方法或代码块在任一时刻只有一个线程执行。

2. 使用方式

synchronized 用于修饰代码块,类的实例方法和静态方法

2.1 常见的三种使用方式如下:

  • 修饰代码块,即同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象。
  • 修饰普通方法,即同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
  • 修饰静态方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象

2.2 锁的使用规则

        1.锁对象的设置:

                a. 修饰代码块时,需要一个 reference 对象 作为锁的对象

                b. 修饰实例方法时,默认锁的对象是=当前对象

                c. 修饰类方法(静态方法) 时,默认的锁对象是当前类的class对象

        2. 根据锁对象的不同,一把锁同时最多只能被一个线程持有

                a. 若当前线程持有目标锁,其他线程仍然可以调用目标类种没有被synchronized修饰的方法

        3. 当对象获取多个锁时,必须以相反顺序释放      

2.3 锁的类型

由于synchronized会修饰代码块,类的实例方法和静态方法,所以有不同锁的类型

  

3. 特点

            保证原子性、可见性和有序性

 

  1. 可重入性:对同一线程获得锁后,在调用其他需要同样锁的代码时可直接调用 
  2. 不可中断性:通俗解释:一旦这个锁被别人获得了,如果我想获得,只能等待或者阻塞,直到别的线程释放这个锁,如果别人永远不释放锁,我只能永远等下去;很执着。
  3. 重量级:底层时通过监视器对象(monitor)完成的,监视器锁本质是依赖于底层操作系统的互斥锁(Mutex Lock)实现,而操作系统实现线程需要从用户态转换为内核态。因此synchronized效率低,是重量级锁

4.缺点及使用synchronized的注意事项

        4.1 缺陷

                效率低:锁的释放情况少,试图获的锁时不能设定超时、不能中断一个正在试图获得锁的线程

                不够灵活:加锁和释放的时机单一,每个锁仅有单一的条件(某个对象)

                无法知道是否成功获取到锁

        4.2 注意事项

                a .锁的对象不能为空

                b. 作用域不宜过大

                c. 避免死锁一组相互竞争资源的线程因为互相等待,导致“永久”阻塞的现象) 

死锁发生的条件:

  • 互斥:共享资源 X 和 Y 只能被一个线程占用;
  • 占有且等待:线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
  • 不可抢占:其他线程不能强行抢占线程 T1 占有的资源;
  • 循环等待:线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待

破坏死锁发生的条件:

  • 占用且等待:一次性申请所有的资源,这样就不存在等待了。
  • 不可抢占,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。(java.util.concurrent提供了Lock解决这个问题)
  • 循环等待,靠按序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化申请后就不存在循环了。

        

5. synchronized 优化(偏向锁、轻量级锁及重量级锁)

 为了减少获得锁和释放锁所带来的性能消耗,在JDK 1.6里引入了4种锁的状态:无锁、偏向锁、轻量级锁和重量级锁,它会随着多线程的竞争情况逐渐升级,但不能降级。

这三种锁中偏向锁和轻量级锁是乐观锁,重量级锁是针对synchronized的,都是指锁的状态。JDK1.6通过引入锁升级(偏向锁->轻量级锁->重量级锁)机制来高效实现synchronized。

乐观锁与悲观锁

独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。 

5.1 自旋锁

首先,内核态与用户态的切换上不容易优化。但通过自旋锁,可以减少线程阻塞造成的线程切换(包括挂起线程和恢复线程)。

如果锁的粒度小,那么锁的持有时间比较短(尽管具体的持有时间无法得知,但可以认为,通常有一部分锁能满足上述性质)。那么,对于竞争这些锁的而言,因为锁阻塞造成线程切换的时间与锁持有的时间相当,减少线程阻塞造成的线程切换,能得到较大的性能提升。具体如下:

  • 当前线程竞争锁失败时,打算阻塞自己
  • 不直接阻塞自己,而是自旋(空等待,比如一个空的有限for循环)一会
  • 在自旋的同时重新竞争锁
  • 如果自旋结束前获得了锁,那么锁获取成功;否则,自旋结束后阻塞自己

5.2 偏向锁

在没有实际竞争的情况下,还能够针对部分场景继续优化。如果不仅仅没有实际竞争,自始至终,使用锁的线程都只有一个,那么,维护轻量级锁都是浪费的。偏向锁的目标是,减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁产生的性能消耗。轻量级锁每次申请、释放锁都至少需要一次CAS,但偏向锁只有初始化时需要一次CAS。

“偏向”的意思是,偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),因此,只需要在Mark Word中CAS记录owner(本质上也是更新,但初始值为空),如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁

偏向锁无法使用自旋锁优化,因为一旦有其他线程申请锁,就破坏了偏向锁的假定。

5.3 轻量级锁

自旋锁的目标是降低线程切换的成本。如果锁竞争激烈,我们不得不依赖于重量级锁,让竞争失败的线程阻塞;如果完全没有实际的锁竞争,那么申请重量级锁都是浪费的。轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。

顾名思义,轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁

Mark Word是对象头的一部分;每个线程都拥有自己的线程栈(虚拟机栈),记录线程和函数调用的基本信息。二者属于JVM的基础内容,此处不做介绍。

当然,由于轻量级锁天然瞄准不存在锁竞争的场景,如果存在锁竞争但不激烈,仍然可以用自旋锁优化,自旋失败后再膨胀为重量级锁

5.4 锁分配和膨胀过程

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mikasa_akm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值