锁的概念
在java中,每一个对象都有一把锁,这把锁放到对象头中,锁记录了当前对象被那个线程占用
对象结构
对象头
存放对象运行时候的基本信息
包含两个部分
class Point:指向方法区中的class对象(所以我们可以用对象来获取class)
mark word:存放和当前对象运行状态有关的数据(比如hashcode,锁标志)
Monitor
每一个java对象都可以关联一个Monitor对象,如果使用Synchronized给对象加锁(重量级)之后,该对象的markword中就设置了指向Monitor的指针。
-
_owner:指向持有ObjectMonitor对象的线程
-
_WaitSet:存放处于wait状态的线程队列
-
_EntryList:存放处于等待锁block状态的线程队列
-
_recursions:锁的重入次数
-
_count:用来记录该线程获取锁的次数
假如当前线程A、B、C同时访问同步块。假如A获取到锁,也就会将 monitor 对象中的 _owner 的值赋值为当前线程ID。B、
C线程会进入EntryList中。count =1 , recursions=1。假如A线程第二次进入同步快,count = 2, recursions=2,当前线程退
出时,count和recursions会减一,直到count=0, recursions=0时,说明线程A释放了monitor锁,然后会唤醒EntryList中的线
程,EntryList线程会竞争monitor,竞争到了,和线程A的操作一致。
- 刚开始Monitor中的owner为null
- 当Thread-1执行Synchronized(obj)之后会把owner设置为Thread-1,只能有一个owner,把obj中的mark word保管起来
- 在Thread-1上锁中,其他线程执行Synchronized(obj),都会放到Monitor中EntryList中,线程置为阻塞状态
- 当Thread-1执行完代码快,唤醒EntryList中的线程,进行抢锁(非公平的)。
Syschronized
Syschronized被编译后,生成monitorenter和monitorexit这两个字节码指令,依赖这两个字节码执行来进行线程同步,monitorenter;获取monitor许可证,进入同步块,
monitorexit;离开同步块后,释放monitor许可证。
性能问题:
syschronized会造成严重的性能问题,多个线程来竞争锁,cpu切换到某个线程,却因为没有获得锁而堵塞等待,这种线程切换造成的事件浪费可能比程序执行时间还长。
解决性能问题:
java 1.6之后,加入的轻量级锁和偏向锁。现在是无锁,轻量级锁和偏向锁,和重量锁。锁只能升级,不能降级。
无锁
1、无竞争
2、有竞争,采用非锁的方式,多个线程想要修改资源,只能有一个成功,其他失败的,直接重试,这就是CAS(CAS:操作系统只需要一条指令就能实现,所以是原子性)
偏向锁
当看到锁标志是01时,而且是否时偏向锁显示为1,那么这个锁时一个偏向锁。此时可以修改mark word中前32bit,为当前的线程ID,此时,锁就和线程ID绑定关系了。
轻量锁
当看到锁标志是00时,那么这个锁时一个轻量锁,此时线程会在自己的虚拟机栈中开辟一个被称为Lock record的空间。线程通过CAS获取锁,一旦获取锁,就会复制该对象头中的mark word到Lock record中,并且Lock record的owner指针指向该加锁对象。此时mark word前32bit生成一个指针指向Lock record。此时就形成对象和线程的绑定。此时该线程将对象上锁,然后开始执行任务。此时如果有其他线程想要获取对象锁,就会自旋等待,自旋等待就是cpu空转,这种自旋区别与线程被挂起等待,如果对象锁很快就释放的话,自旋等待的线程就能立马获得锁,而不需要进行系统中断和现场恢复。所以自旋效率在某些场合效率高。并且这种自旋也被优化,叫做‘适应性自旋’:比如当前线程A正在自旋,但是刚刚我已经获得过一次锁了,所以现在我加长我的自旋时间,为了在自旋时间内,就可以直接获取锁。
重量锁
当自旋等待的线程超过1个,锁进行升级。此时就需要monitor对线程进行控制,堵塞的线程直接挂起等待。
在重量级锁之前,是不需要monitor参与的。