1.理解synchronized首先需要明确以下概念:

什么是监视器:this,或者SomeClass.class。

什么是对象锁:this可以看做是java.lang.Object的实例,SomeClass.class可以看做是java.lang.Class的实例,因为JVM内部为每个这样的实例都关联一个锁,所以把这样的内部锁称为对象锁。

区别Class类的实例和类的实例: java每个类都对应一个Class类的实例,Class类的实例在JVM加载类时创建。synchronized (SomeClass.class) or public synchronized static void someStaticMethod()会使用SomeClass.class监视器。

2.获取和释放锁的过程:
同步方法和同步块,这两种监视区域都和一个引入对象相关联,当到达这个监视区域时,JVM就会锁住这个引用对象,不论它是怎么离开的(正常和异常),都会释放这个引用对象上的锁。
JAVA程序员不能自己加对象锁,对象锁是JVM内部机制,只需要编写同步方法或者同步块即可,操作监视区域时JVM会自动帮你上锁(monitorenter)或者释放锁(monitorexit )。
可以将获取监视器理解为获取锁+this入栈。操作this...., 释放监视器理解为this出栈+释放锁。如果不考虑细节,理解简化为同步方法或块中的代码需要线程获得监视器,才会执行。

monitorenter           //出栈并获取对象锁
aload_0            //将this引用压栈
.....
aload_1            //将this引用压栈
monitorexit            //弹出this引用释放对象锁



3.监视器使用场合:
对于对象来说,监视的是这个对象的实例变量,对于类来说,监视的是类变量,如果一个对象没有实例变量,就什么也不监视。
监视器有两种同步方式:互斥与协作。 协作就是wait/notify的场景。java语言的每个对象(都是java.lang.Object的子类)都实现了线程协作的方法,只是这些方法只有在同步方法或同步语句所标识的临界区内才能被调用,也就是调用这些方法的时候,相对应的对象已经被加锁。JUC中的锁也是互斥和协作的场景。

锁住一个类实际上锁住的是这个类对应的Class类的实例。Class实例可以理解为JVM中一把唯一存在的可重入锁。
锁住一个类的对象,则由于一个类可能有多个实例存在,则可以理解为JVM中可能存在多把可重入锁。
所以二种监视器的场景区别就是JVM中是否唯一的锁。

4.synchronized与JUC中提供的各种Synchronizer比较:
4.1.synchronized只有锁只与一个条件(是否获取锁)相关联,不灵活。synchronizer提供了各种有实际意义的所条件.
synchronzier可以抽象为下面的模式:

while(!state){
       wait);
}
changeState();


state决定了什么时候需要阻塞,每种synchronizer可能不同,拿每个人自己的业务应用来说,可能是判断XX集合是否为空等等,这些都可以通过实现抽象方法tryAcquire实现,只要返回boolean类型表明状态是否ok就好了,不管你是什么状态。

4.2.性能:synchronize会使其它线程不停尝试获取锁,有点类似轮询。而ReentrantLock由于有lockInterruptibly()方法支持的中断取消机制。阻塞线程可以被中断而退出等待。
多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。高并发的情况下会导致性能下降。
ReentrantLock的 lockInterruptibly()方法可以优先考虑响应中断。 一个线程等待时间过长,它可以中断自己,然后ReentrantLock响应这个中断,不再让这个线程继续等待。有了这个机制,使用 ReentrantLock时就不会像synchronized那样产生死锁了(网上有很多synchronized死锁的示例)

5.synchronized的细节

5.1 细节一

Java 给对象加锁 还是给方法加锁_Java 给对象加锁 还是给方法加锁

如上图所示,一个线程通过1号门进入Entry Set(入口区),如果在入口区没有线程等待,那么这个线程就会获取监视器成为监视器的owner,然后执行监视区域的代码。如果在入口区中有其它线程在 等待,那么新来的线程也会和这些线程一起等待。线程在持有监视器的过程中,有两个选择,一个是正常执行监视器区域的代码,释放监视器,通过5号门退出监视 器;还有可能等待某个条件的出现,于是它会通过3号门到Wait Set(等待区)休息,直到相应的条件满足后再通过4号门进入重新获取监视器再执行。

注意:当一个线程释放监视器时,在入口区和等待区的等待线程都会去竞争监视器,如果入口区的线程赢了,会从2号门进入;如果等待区的线程赢了会从4 号门进入。只有通过3号门才能进入等待区,在等待区中的线程只有通过4号门才能退出等待区,也就是说一个线程只有在持有监视器时才能执行wait操作,处于等待的线程只有再次获得监视器才能退出等待状态

5.2细节二 HotSwap

Java 给对象加锁 还是给方法加锁_对象锁_02

 

  • Contention List:所有请求锁的线程将被首先放置到该竞争队列
  • Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List
  • Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set
  • OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck
  • Owner:获得锁的线程称为Owner
  • !Owner:释放锁的线程