java synchoronize_Java高并发之魂:synchronized深度解析

一、 Synchronized简介

1.1、Synchronized的作用

能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。通俗来讲,如果一段代码被synchronized修饰了,那么这段代码会以原子的方式执行,多个线程在这行这段代码时,他们不会相互干扰和相互影响,多个线程间不会同时执行这段代码。

1.2、Synchronized的地位

1、Synchronized是java的关键字,并java的语言原生支持

2、是最基础的互斥同步手段

3、是并发编程中的元老级角色,是并发编程的必学内容

1.3、不使用并发手段的后果

两个线程同时a++, 最后结果会比预计的少(读取a, a++, 将a写入内存),所以每一步执行完都有可能被打断,所以a值有可能没写进内存就执行另一个线程的a++操作了。

2531713d5972794085adfccbf70f7264.png

二、 Synchronized的俩种用法

synchronized的两个锁:对象锁(包含方法锁和同步的代码块锁)和类锁(指synchronized修饰静态的方法或指定锁为class对象)

2.1、对象锁(代码块)

d050742d355cd75001b149c345ea7772.png

2.2、对象锁(普通方法锁)

6d0c5d70cbba4b28924d5506afb51063.png

2.3、类锁(静态方法锁)

概念(重要):Java类可能有很多个对象,但只有1个Class对象

概念:

1、 只有一个Class对象:Java类可能会有很多个对象,但是只有1个Class对象。

2、 本质:所以所谓的类锁,不过是Class对象的锁而已。

3、 用法和效果:类锁只能在同一时刻被一个对象拥有。

两种形式:

形式1:synchronized加在static方法上

形式2:synchronized(*.class)代码块

31249948215fb59566635940cfd5cbef.png

2.4、类锁(.class)

ed28e71c933392cb3209e6187e0ba5e5.png

三、 多线程访问同步方法的7种情况

1、两个线程同时访问一个对象的同步方法。 锁生效√

2、两个线程访问的是两个对象的同步方法。 锁无效×

3、两个线程访问的是synchronized的静态方法。 锁生效√

4、同时访问同步方法(这里指被synchronized修饰的方法)与非同步方法。 非同步方法不受影响。

5、访问同一个类的不同的普通同步方法。 锁生效,前提是锁的关键字一样,即同一把锁

6、同时访问静态synchronized和非静态synchronized方法。 锁的不是同一个对象。静态synchronized锁的是.class对象,非静态synchronized锁的是实例本身this。

7、方法抛异常后,会释放锁。

7种情况总结:

3点核心思想

(1)一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况);

(2)每个实例都对应有自己的一把锁,不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象共用同一把类锁(对应第2、3、4、6种情况);

(3)无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)

四、Synchronized的性质

4.1、可重入性

什么是可重入:指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。

好处:避免死锁,提升封装性。

粒度:线程而非调用(用3种情况来说明和pthread的区别)pthread粒度是调用。

粒度:

情况1:证明同一个方法是可重入的。

dc8cb14b8e69b1fcbbf3e863d2b47cec.png

情况2:证明可重入不要求是同一个方法。

f397b4eb7868039365efc1f9615a6aae.png

情况3:证明可重入不要求是同一个类中的。

82d971519f574580e5bcc21ba9a7c504.png

4.2、 不可中断性

d0150178757314250f57e2e00ab0407a.png

五、深入原理

5.1、加锁和释放锁的原理

4c56c37ab2ae80edd4d93021370458bc.png

5.2、 反编译看monitor指令

synchronize用的锁是java对象头里的一个字段(每一个对象都有一个对象头,对象头可以存储很多信息,其中有一部分就是用来存储synchronize关键字的锁)。

细节:当线程访问代码块的时候必须要得到这把锁,退出整个代码块或者抛出异常的时候必须会释放锁,在JVM规范中对于jVM实现原理,已经有了说明,它的进入锁和释放锁是基于moniter对象来实现同步方法和同步代码块的,Monditor对象主要是两个指令,一个是Monditorenter(插入到同步代码块开始的位置),和Monditorexit(退出,插入到方法结束的时候和退出的时候),jvm的规范要求每一个enter必须要要有exit和他对应,但是可能一个moniterenter对应多个Monditorexit,退出的时机包括方法结束和抛出异常。每一个对象都有一个Monditor和他关联,并且moniter被持有后,就会处于锁定状态,当线程执行到Monditorenter指令时,会尝试获取这个对象对应的Monditor的所有权,也是尝试获取对象的锁。

反编译字节码

(1)目录定义到类所在的文件夹

(2)在命令行,java类编程class对象,使用javac

(3)javap 把class文件解读成字节码文件,java -verbose:加入verbose参数,事无巨细的所有的信息都打印出来

详细解读Monditorenter和Monditorexit指令:

Monditorenter和Monditorexit在执行的时候会使对象的锁计数加1或者减1,和操作系统中的PV操作(多线程对临界资源的访问)很像,每一个对象都和一个Monditor相关联,一个moditor的locksware只能被一个线程在同一时间获得,一个线程在尝试获得与这个对象关联的Monditor所有权的时候(Monditorenter),只会发生以下三种情况之一

(1)Monditor计数器为0,意味着目前还还没有被获得,这个线程就会立刻获得,然后把计数器加1,之后别人再想进来就会看到信号,知道它已经被其他线程所持有,加1,意味着当前线程是这个Moditor持有者。(成功获得锁)

(2)当前对象拿到锁的所有权,又重入了,就会导致计数器累加,会变成2,3……随着重入的次数而增加(已经有了这把锁再次重入的情况)

(3)Monditor已经被其他线程所持有了,当前线程再次获取就会得到现在无法获取的信号,就会进入阻塞状态,知道Moditor的计数器变为0,才会再次尝试获取这个锁

Monditorexit

作用,释放Monditor(Monditor可以理解为锁)的所有权,前提是已经拥有了锁的所有权,释放的过程就是将Monditor的计数器减1,减完之后变成0就意味着当前线程不在拥有对Monditor的所有权,就是解锁,如果减完之后不是0,意味着刚才是可重入进来的,所以还继续持有这把锁,最终减到0之后,不仅意味着释放锁了,还意味着刚才被阻塞的线程,会再次尝试获取对该把锁的所有权

5.3、可重入原理

1、JVM负责跟踪对象被加锁的次数;

2、有个monitor计数器,线程第一次给对象加锁的时候,计数变为1.每当这个相同的线程在此对象上再次获得锁时,计数会递增。

3、任务结束离开,则会执行monitorexit,计数递减,直到完全释放

5.4、可见性原理

一旦一个代码块或者方法被我们的synchronize关键字所修饰,他在执行完毕之后,被锁住的对象所做的任何修改都要在释放锁之前,从线程内存写会到主内存,也就是不会存在线程内存和主内存内容不一致的情况,同样在进入代码块得到锁之后,被锁定的对象的数据也是直接从主内存中读取出来的,而在释放的时候会把修改的内容写回到主内存中,所以从主内存中读取到的数据一定是最新的,通过这个原理synchronize保证了我们每一次的执行都是可靠的,保证了可见性。

1a5783b238c46b27bdbd580c9719741a.png

六、 Synchronized缺陷

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

2、不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的。

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

针对这些缺陷,lock锁能够有效解决:

1、 lock();//获取锁

2、 unlock();//释放锁

3、 tryLock();//判断锁是否可用。返回值为:boolean;

4、tryLock(time,TimeUnit);//在规定的时间内,如果未获得锁,则就放弃。第一项表示规定的时间;第二项表示设置时间的单位

七、常见面试问题

1、synchronized使用的注意点

锁对象不能为空,作用域不宜过大,避免死锁

2、如何选择Lock和synchronized关键字

建议都不使用,可以使用java.util.concurrent包中的Automic类、countDown等类。优先使用现成工具,如果没有就优先使用synchronized关键字,好处是写劲量少的代码就能实现功能。如果需要灵活的加解锁机制,则使用Lock接口。

八、总结和回顾

一句话介绍synchronized:

JVM会自动通过使用monitor来加锁和解锁,保证了同时只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入和不可中断的性质。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值