synchronized 关键字
一、使用方式
1、修饰普通方法,锁对象为类实例
2、修饰静态方法,锁对象为类class实例
3、修饰代码块,锁对象为括号里的对象
private synchronized void fun(){// 普通方法互斥
}
public synchronized static void funStatic(){// 静态方法互斥
}
synchronized (object){
//代码块互斥
}
这样使用后各线程都是互斥的,保证了线程安全。
二、锁优化
参考自《深入理解java虚拟机》java 1.6 以后 jvm 优化了 synchronized。一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。
优化
1、锁粗化(Lock Coarsening):也就是减少不必要的紧连在一起的unlock,lock操作,将多个连续的锁扩展成一个范围更大的锁,减少了锁获取释放的次数。
private void fun1(){
synchronized (object){
//do somethings 1
}
synchronized (object){
//do somethings 2
}
synchronized (object){
//do somethings 3
}
}
//粗化合到一起
private void fun2(){
synchronized (object){
//do somethings 1
//do somethings 2
//do somethings 3
}
}
2、锁消除(Lock Elimination):锁削除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除。
3、适应性自旋(Adaptive Spinning):自旋是指获取锁时,先不挂起线程,而是while循环使用CAS的方式获取,这样效率更高。适应性自旋则会根据之前当前线程获取锁的结果,来动态调整while循环的次数, 优化了效率,更“智能”。
java中每个对象都可以作为锁对象,因为每个对象都拥有一个对象头,记录对象hashCode、GC分代、偏向锁编程ID,锁状态等。
正如图展示的这部分内存,不是固定的,在不通的锁状态有不通的指向。
1、无锁状态,如果jvm检测到不可能有多线程执行此方法,则是无锁状态,内存就是表格第一行描述的那样。表格中第1行。
2、加入一个线程调用了方法或者代码块,锁对象的线程ID使用CAS标记当前线程,在没有其他线程调用的情况下,不会再CAS,直接就调用了。表格中第5行。
3、如果在偏向锁的状态下,有其他线程调用,线程ID与当前调用线程不一致,则 膨胀为轻量级锁。膨胀过程为:使用CAS在当前线程栈帧复制此对象头信息保存在Lock Recorder空间,并将锁对象头指向此栈帧中的内存。就是说当前线程获取了此轻量级锁。表格中第2行。
如果一个线程调用方法,使用CAS复制失败,发现markwork指向当前栈帧内存,就直接调用了。
4、如果一个线程调用方法,使用CAS复制失败,而且markword不指向当前栈帧,则膨胀为重量级锁,将markwork指向锁对象。本线程挂起,并等待持有锁释放。表格第三行。
三、重量级锁
重量级锁会将线程挂起,等待执行。
上图简单描述多线程获取锁的过程,当多个线程同时访问一段同步代码时,首先会进入 Entry Set当线程获取到对象的monitor 后进入 The Owner 区域并把monitor中的owner变量设置为当前线程,同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。