Synchronized作用范围
- 作用在方法上的时候,锁的是this
- 作用在静态方法时,锁的是类的class对象。因为是静态方法,所以所有线程都会共享这个锁
-
Synchronized核心组件
Wait Set: 调用wait的线程被存放在这里
- Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中
- Entry List:Contention List中有资格成为候选资源的线程被移动到Entry List中
- OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,这个线程被称为OnDeck
- Owner:当前已获取到锁资源的线程被称为Owner
-
Synchronized实现
JVM每次从队列尾取出一个数据用于锁竞争候选者(OnDeck), 但在并发情况下,ContentionList会被大量的并发线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将一部分线程移动到EntryList中作为候选线程
- Owner线程会在Unlock时,将ContentionList中的部分线程移动到EntryList中,并指定EntryList中的某个线程为OnDeck线程(一般是最先进去的那个线程)
- Owner线程并不直接把锁传递给OnDeck,而是把锁竞争的权利交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在JVM中,也把这种选择称之为“竞争切换”
- OnDeck线程获取到锁资源后会成为Owner线程,而没有竞争到锁资源的仍然停留在EntryList中。如果Owner线程被wait方法阻塞,会进入到WaitSet,直到某个时刻notify或notifyAll唤醒,会重新进入EntryList
- 处于ContentionList,EntryList,WaitSet的线程都处于阻塞状态,这个阻塞是由操作系统完成的(Linux系统下采用pthread_mutex_lock内核函数实现的)
- Synchronized是非公平锁,在线程进入ContentionList时,等待的线程会自旋获取锁,如果获取不到就进入ContentionList,这明显对已经进入队列的线程是不公平的。自旋获取锁的线程还可能会直接抢占OnDeck线程的锁资源
- 每个对象都有一个monitor对象,加锁就是在竞争monitor对象,代码块加锁是在前后分别加上monitorenter和monitorexit指令来实现的,方法加锁是通过一个标记位来判断的
- synchronized是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线程加锁消耗的时间比有用操作消耗的时间还多
- java1.6,synchronized进行了很多优化,有适应自旋,锁消除,锁粗化,轻量级锁及偏向锁效率有了本质上的提升。之后java1.7,1.8中均对该关键字的实现机理做了优化,引入了偏向锁,轻量级锁。都是在对象头中标记的,不需要经过操作系统加锁
- 锁可以从偏向锁升级到轻量级锁,再升级到重量级锁。这种升级过程叫做锁膨胀
- jdk1.6中默认开启偏向锁和轻量级锁,可以通过-xx:UseBiasedLock来禁用偏向锁