关于synchronized同步,能用无锁结构就不要用锁;能锁块,就不要锁整个方法;能用对象锁,就不要用类锁。
用锁能够保证数据的安全性,但性能下降。无锁,性能提升,但安全性下降,如何平衡?
在Java早期版本,synchronized是重量级锁,效率低下,因为监视器锁monitor依赖底层操作系统的Mutex Lock(系统互斥量)来实现。
Java5之前,用户态和内核态之间的切换:
用户态和内核态之间切换指的是:
Java的线程是映射到操作系统的原生线程之上的(Java的start方法底层是native start0方法),因此阻塞和唤醒一个线程都需要操作系统介入去切换CPU的状态来完成(内核态),这种状态切换需要耗费处理器的时间,如果同步代码块很简单,那切换的时间可能比代码执行时间还长。
而用户态和内核态有各自专用的内存空间、专用的寄存器 ? 因此,用户态切换到内核态还需要传递很多变量和参数过去,且内核也要去维护这些值。
鉴于以上,Java6后,引入轻量级锁和偏向锁,别一下就捅到重量级锁。一句话,为了尽量减少用户态和内核态的切换次数。
如果一个Java对象被某线程锁住,则:
关于Monitor的复习:【Monitor】
Monitor是在JVM底层实现的,底层代码是c++。本质是依赖于底层操作系统的Mutex Lock实现,而Mutex Lock 的切换需要从用户态转换到内核态中,因此状态转换需要耗费很多的处理器时间,所以synchronized在Java中是一个重量级操作。
synchronized锁升级主要依赖Mark Word中锁标志位和释放偏向锁标志位。
偏向锁:MarkWord存储的是偏向的线程ID
轻量锁:MarkWord存储的是指向线程栈中Lock Record的指针
重量锁:MarkWord存储的是指向堆中的monitor对象的指针
无锁:即初始状态,一个对象被实例化后,如果还没有被任何线程竞争锁,那它就是无锁状态(001)
用JOL展示下,一个对象在无锁状态下,其对象头是如何记录的。
Object object = new Object();
//hashcode方法是native的,调用了才会生成,否则为0
System.out.println("10进制:" + object.hashCode());
System.out.println("16进制:" + Integer.toHexString(object.hashCode()));
System.out.println("2进制:" + Integer.toBinaryString(object.hashCode()));
System.out.println(ClassLayout.parseInstance(object).toPrintable());
结果分析:前25位是没使用的unUsed,再往后31位是hashcode,倒着看