synchronized的作用位置及其锁对象
- 普通方法:this对象
- 静态方法:类的class对象
- 代码块:自定义对象
加锁和释放锁的原理
public class SynchronizedDemo {Object object = new Object();public void run1() {synchronized (object) {}run2();}public static void run2() {}}
synchronized锁住的不是代码块,而是对象。
每一个对象在同一时间只与同一个monitor(监视器)关联,而一个monitor在同一时间只能被一个线程获得。
- monitor=0,意味着没有线程获得锁,一旦有线程获得锁,monitor自动+1,其他线程想要获得锁,就必须等待,如果锁被释放,则计数器-1。
- 如果该线程已经获得锁,又重入了这把锁,那么计数器再+1变成2,随着重入次数的增加monitor值累加,如果monitor不为0,该线程依旧持有这把锁。
- 如果其他线程获得了锁,等待锁被释放。
可重入原理:加锁次数计数器 上面的demo中在执行完同步代码块之后紧接着再会去执行一个静态同步方法,而这个方法锁的对象依然就这个类对象,那么这个正在执行的线程还需要获取该锁吗? 答案是不必的,从上图中就可以看出来,执行静态同步方法的时候就只有一条monitorexit指令,并没有monitorenter获取锁的指令。这就是锁的重入性,即在同一锁程中,线程不需要再次获取同一把锁。 Synchronized先天具有重入性。每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一。
保证可见性的原理:内存模型和happens-before规则
Java内存模型:对线程而言,虚拟机栈(私有)、程序计数器(私有)、堆(公有)。
happens-before:JVM会对代码进行编译优化,会出现指令重排序情况,为了避免编译优化对并发编程安全性的影响,需要happens-before规则定义一些禁止编译优化的场景,保证并发编程的正确性
Synchronized的happens-before规则,即监视器锁规则:对同一个监视器的解锁,happens-before于对该监视器的加锁。
synchronized的缺陷
- 效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时
- 不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活
无法知道是否成功获得锁,相对而言,Lock可以拿到状态,如果成功获取锁,….,如果获取失败,…..
Lock解决相应问题
Lock类这里不做过多解释,主要看里面的4个方法:
lock(): 加锁
- unlock(): 解锁
- tryLock(): 尝试获取锁,返回一个boolean值
- tryLock(long,TimeUtil): 尝试获取锁,可以设置超时
Synchronized只有锁只与一个条件(是否获取锁)相关联,不灵活,后来Condition与Lock的结合解决了这个问题。
多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。高并发的情况下会导致性能下降。ReentrantLock的lockInterruptibly()方法可以优先考虑响应中断。 一个线程等待时间过长,它可以中断自己,然后ReentrantLock响应这个中断,不再让这个线程继续等待。有了这个机制,使用ReentrantLock时就不会像synchronized那样产生死锁了。
