JAVA并发编程学习笔记06-synchronized及锁优化
Monitor
Monitor是属于操作系统层面的一个对象,由JAVA对象头指向,直接翻译过来是管程或监视器。主要分为如下三个部分。
锁竞争流程
-
当第一个线程尝试获取锁,检测到Owner为null,则直接获取,此时Owner指向该线程;
-
当第二个、第三个线程尝试获取锁,发现Owner有指向,则将这两个线程存入EntryList;
-
此时线程一执行到wait()方法,故放弃锁,将Owner置为null,然后将第一个线程存入WaitSet中;
-
系统发现Owner为null,开始从EntryList中挑选一个线程开始执行,也就是所谓的锁竞争,竞争后,线程二赢得了锁的所有权,Owner重新指向县城二,线程三继续呆在EntryList中;
-
线程二执行到notifyAll()方法,唤醒所有等待的线程,将所有处于WaitSet中的线程转移到EntryList中,但此时由于线程二并未执行完,依旧会继续执行,Owner依旧指向线程二;
-
当线程二执行完,锁被释放,Owner再次被置为null,操作系统再次开始锁竞争,然后由线程一(或线程三)竞争到锁。
synchronized关键字
synchronized是JAVA自带的一种隐式锁,进入synchronized代码块则加锁,出代码块则解锁,加锁解锁过程由JDK底层实现。锁对应的每个对象就对应一个Monitor对象,这也是为什么说要使用相同的对象加锁。
修饰静态方法:
锁的是类对象,即该类的所有对象的被synchronized和static修饰的方法公用一把锁。
@Slf4j
public class Test03 {
public static void main(String[] args) {
new Thread(LockObject::print1).start();
new Thread(LockObject::print2).start();
}
}
@Slf4j
class LockObject {
public static synchronized void print1() {
log.info("start print1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("end print1");
}
public static synchronized void print2() {
log.info("start print2");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("end print2");
}
}
打印结果可以看到,虽然是不同对象的不同方法,但两个线程是串行的。
修饰普通方法:
锁的是对象本身,与上面的静态方法不一样,不同对象的方法互不干涉,只有同一对象的普通方法被synchronized修饰才会互斥(代码类似静态方法,不做演示)。
总结:
synchronized关键字本质是锁关键字指定的对象,只要指定对象一直就互斥,而修饰普通方法和静态方法,代码编写上并未指定锁对象,是JDK底层默认指定了对象实例和类对象。
锁优化
由于synchronized是由操作系统提供,故开销较大,从JAVA6开始,对synchronized关键字获取锁的方式进行了改进。
偏向锁
锁指向的对象,默认是可偏向的,当第一次加锁,转变为偏向锁,偏向锁开销是最小的,表示系统默认以后都只有该线程使用这把锁。
轻量锁
当锁对象偏向于线程1后,在锁释放后,线程2获取了锁,则由偏向锁升级为轻量锁。轻量锁的开销大于偏向锁,小于重量锁,表示线程之间会对同一把锁有竞争,但时间是错开的。
锁膨胀
当一个线程持有锁对象的过程中,另一个线程尝试获取锁时,会将锁升级为重量锁,这个过程称为锁膨胀。
自旋优化
当进行重量级锁竞争时,当发现Owner被占有时,不会立即进入EntryList等待(进入等待意味着会进行上下文切换,开销较大),而是先进行自旋重试,当重试了几次后,若成功,则获取锁,反之再进入EntryList等待。
批量重偏向
当同一类的不同对象,同时偏向于线程1后,此时触发批量偏向线程2,当这个偏向次数达到阈值20次时,系统会任务之前的偏向不合理,故会直接将该对象的所有偏向线程由线程1改为线程2,这个过程称为批量重偏向。
批量撤销
类似批量重偏向,当同一类对象由偏向锁升级为轻量锁的次数达到阈值40次时,系统会认为该对象不适合用偏向锁,此时系统会将所有该对象的偏向锁升级为轻量锁。
锁消除
当某些代码的锁不可能有其他线程竞争时,JVM会将代码优化,即将无用的锁代码去掉,这个过程称为锁消除,本质是一种底层的代码优化策略。