一、synchronized的特性
1.互斥
当某个线程已经执行到某个对象的synchronized中时,如果其他线程也执行到同一个对象的synchronized,就会发生阻塞。
进入被synchronized修饰的方法使,相当于对当前对象加锁;方法执行完毕时,相当于对当前对象解锁。
2.可重入
synchronized 同步块对同一条线程来说是可重入的,不会出现死锁问题。
二、synchronized的使用
1.修饰代码块
//锁对象
synchronized(this){
...
}
//锁类对象
synchronized(className.class){
...
}
锁对象时,同一个类的不同对象有不同的锁,对象之间不会出现锁竞争;锁类对象时,同一个类的不同对象用同一把锁,对象之间也会出现锁竞争。
2.修饰方法
public synchronized void methondName() {}
相当于
public void methondName() {
synchronized(this) {
...
}
}
3.修饰静态方法
public static synchronized void methondName() {}
相当于
public void methondName() {
synchronized(methondName.class) {
...
}
}
三、synchronized的锁机制
1.锁升级
JVM将synchronized锁分为:无锁、偏向锁、轻量级锁、重量级锁,根据实际情况,依次升级(不会降级)。
偏向锁:
偏向锁是一种准备加锁但还没有加锁的状态,只是给对象头做了一个标记。
第一个尝试加锁的线程,优先进入偏向锁状态。如果后续没有线程来竞争该锁,就节省了一次加锁解锁的开销;但是如果后续有线程来竞争该锁,那就会取消偏向锁状态,进入轻量级锁状态。类似于小时候在家里偷偷看电视,虽然我们在看电视,但是一旦有风吹草动,我们就可以以迅雷不及掩耳之势关掉电视,防止被父母发现。
轻量级锁:
自适应的自旋锁,通过CAS实现。
自旋锁会让CPU一直空转,处于忙等状态,比较浪费CPU资源,但好处是响应快。
自旋不会一直持续下去,达到一定的时间/次数就不再自旋了,因此称为“自适应”。
重量级锁:
自旋不能快速获取到锁状态,就会膨胀为重量级锁。实际上就是内核提供的 mutex。
2.锁消除
如果编译器和JVM确信某处代码可以不加锁,就直接消除。
3.锁粗化
如果一段逻辑中多次出现加锁解锁操作,编译器和JVM会自动进行锁粗化。
锁的粒度:粗和细
一段代码,如果每条语句都加同一把锁,就是细;如果将整段代码加锁,就是粗。频繁的加锁解锁操作会消耗大量的资源。
例如
while(...){
synchronized{
n++;
}
}
会被自动粗化为
synchronized{
while(...){
n++;
}
}