Synchronized已经有了锁升级
偏向锁:只有一个线程反复获得这个对象锁,直接判断线程指向就可
轻量级锁:CAS自旋占用CPU,有自适应自旋次数
重量级锁:超过自旋次数会挂起不占用CPU,需要从用户态转换到核心态
最开始的时候是对象MarkWord头是一个无锁的状态,第一个线程来CAS抢占锁之后变成了偏向锁,第二个线程来了,他俩来回抢这把锁就变成了轻量级锁,没抢到锁的线程不会阻塞,一直CAS自旋,当自旋到一定次数(自适应自旋)为了不占用cpu,挂起线程变成重量级锁。
补充细节:
锁的升级是单向的
ObjectMonitor中有两个队列,WaitSet 和 EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象)当多个线程同时访问一段同步代码时,首先会进入 EntryList 集合,当某个线程获取到对象的monitor 后monitor中的计数器count加1,若该线程调用 wait() 方法,将释放当前持有的monitor,count自减1,同时该线程进入这个对象的WaitSet集合中等待被之后获取到monitor的线程调用notify,唤醒通知自己拿到这个锁继续执行。
在字节码里面,同步块是monitorEnter和monitorExit,同步方法是ACC_SYNCHRONIZED标志位标识为同步方法。
监视器monitor依赖底层的操作系统的MutexLock实现,操作系统实现线程之间的切换时需要从用户态转换到核心态,状态之间的转换时间成本相对较高。
Synchronized用法:
void myMethod() {
synchronized(obj) {//代码块锁住这个对象级别
//这里是同步区
}
}
void myMethod() {
synchronized(MyClass.class) {//代码块锁住类级别
//这里是同步区
}
}
synchronized void myMethod() {//同步方法这里锁住的是这个实例对象
}
synchronized static void myMethod() {//静态同步方法这里锁住的是这个类级别
}
//补充:当一个线程访问对象的一个同步代码块时,另一个线程仍然可以访问该对象中的非同步代码块。
对于上述补充也很好解释,如果别的线程也能访问到锁住同一个对象的另一个方法块,那markwork头记录谁好呢,还有就是如果这两个方法对某些属性产生了关联呢就容易出错了。当然,如果一个块是锁住类级别一个是锁住对象级别的同时可以访问。
Synchronized等待唤醒:
synchronized (obj) {
obj.wait(); //要有try块包裹
obj.notify();
obj.notifyAll();
}
//补充这个三个方法要用在synchronized里,因为这三个方法使用前提要拿到monitor
线程执行wait方法会释放当前持有的monitor让出CPU进入等待状态,只有当 notify/notifyAll() 被执行时候才会唤醒一个/多个处于等在状态的线程继续执行,执行又碰到了wait就再释放锁。
注意:要是两个线程争抢一把锁,A先得到了执行notify()唤醒,B线程还没wait() 进入等待区,就没法唤醒了。
notify/notifyAll方法调用后,并不会马上释放monitor,而是在相应的synchronized方法或者块执行结束后才自动释放锁(建议在代码块最后一句写notify/notifyAll)。
notify 和 notifyAll的区别:notify方法只唤醒一个在这个对象的等待池里等待的线程并使该线程开始继续执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有在这个对象的等待池里等待的线程,哪一个线程将会第一个处理取决于操作系统的实现。
再说ReentrantLock已经在另一个AQS写了,主要就是依靠AQS,ReentrantLock自己有Sync内部类然后Sync有公平FairSync和非公平NonFairSync两种子类实现,根据构造方法返回公平或非公平,默认非公平。
再说两者的区别:1、 ReentrantLock支持公平与非公平锁,Synchronized只有非公平。
2、 使用上:lock要注意在finally解锁,Synchronized可以锁住整个类的级别。
3、 lock有trylock的方法使现称不阻塞根据返回值继续执行。
4、 根本实现上,lock是依靠AQS,Synchronized依靠JVM本身monitor+markword+EntryList和WaitSet。
5、ReentrantLock可响应中断,synchronized是不可以响应中断的(响应中断我不懂)
两锁为啥都设计成可重入的:为了同一个线程反复获取这把锁的时候避免死锁。



