- 原理概要:
java虚拟机中的同步基于进入与结束Monitor对象实现,无论是显式同步(同步代码块进入在jvm是根据monitorenter标志、结束是monitorexit标志,那最后一个是monitorexit是异常结束时被执行的释放指令
)、隐式同步(同步方法无需通过字节码控制,而是ACC_SYNCHRONIZED声明为同步方法在jvm内部包装一个监视器锁被调用
)都是如此,
- 底层原理:(可重入的实现)
每一个锁关联一个线程持有者和一个计数器、当计数器为0时表示该锁没有被任何线程持有,此时任何线程都可以获得该锁并调用,当有一个线程请求成功时,jvm会记录下持有锁的线程并将计数器设为1,此时其他线程请求该锁,则必须等待,当持有该锁的线程再次请求这个锁,重入后将计数器加1,当线程退出该锁,计数器就会递减,如果计数器为0时则释放锁
同步代码块字节码
3 monitorenter
//...........
71 monitorexit
//...........
78 monitorexit
//...........
82 return
同步方法字节码
-
应用范围
:
- 可以保证在同一时刻,只有一个线程在访问被锁修饰的代码,也可以保证线程的变化,被另一个线程看到(可见性)
- 是一个互斥(所以有阻塞)、重量级锁
- 可以阻止jvm的指令重排序
- 总之使用同步会系统的很大开销、甚至可能会死锁
-
应用方式:
- 修饰代码块:作用的对象是整个代码块的对象
FQA
:当有一个线程访问这个代码块时,其他线程试图访问会出现阻塞 – | - 方法:作用的对象是调用这个方法的对象
FQA:多线程下阻塞和修饰代码块等价,都锁定整个方法的内容
– | - 静态方法:作用的对象是这个类的所有对象
FQA:多个对象也是一把锁,所以线程同步
- 类:作用主的对象是这个类的所有对象
FQA:多个对象也是一把锁,所以线程同步
-
锁的状态:
1.无锁、
2.偏向锁、
3.轻量锁、
4.重量锁,
随着锁的竞争、从偏向锁到轻量级、在到重量级。锁的的升级是单向,也就是只能从低到高升级不会出现锁的降级
- synchronized 的用法
a. synchronized 修饰 static静态方法 —— 锁住的是class对象
b.synchronized 修饰 普通方法 —— 锁住的是实例对象 (this)
c.synchronized (obj) 修饰代码块 —— 锁住的是obj对象
锁的状态记录在jvm对象头中
synchronized:他是对于当前虚拟机而言,但是同一个应用部署到多台服务器,它的实例是运行在多个不同的jvm中的之间是相互独立的,所以在多台服务器之间加锁需要用到分布式锁分布式锁:(redis实现,并不是实际意义上的加锁,只是setx一个key,每次去获取这个key(获取锁),释放这个key(释放锁))
在使用分布式锁是会出现,在释放锁的时候如果前面的业务出问题了,导致释放锁没有执行,所以要对之前的业务逻辑进行异常捕获,并且将释放锁放到finally中,但是还会出现锁超时
问题,将释放锁放到finally中就一定会被执行?在java中Exception是可以捕获的,但是机房停电、kill -9等操作,导致整个逻辑还没执行到finally就会出现问题,所以这个时候可以在setx之后设置一个过期时间,即使kill -9等操作,也会在这个过期时间将锁释放,这样的话后期的线程还是可以获取到锁的,