1.什么是锁:在多线程并发的环境下,对同一个资源进行争抢时,可能会出现线程不安全的情况,通过锁来对资源进行锁定,当一个线程持有锁时,其他线程必须等待,其实这是失去了多线程的意义,变成了串行化。
2.锁是一种抽象概念,在Java中采用了两种实现方式 :
1) 基于对象的悲观锁 : 在Java中,每个对象都有一把锁,这把锁存放在对象头中,记录了当前对象被哪个对象占用。
2)基于CAS的乐观锁
3. 对象,对象头结构
1)对象:分为 对象头,实例数据(初始化对象时设定的属性和状态等内容),对齐填充字节(是为了满足Java对象大小是8字节的倍数而设计的)
2)对象头: (32位系统,8字节,64bit)
Class Pointer 一个指针,指向当前对象类型所在方法区的Class信息
MarkWord(32bit) 存储了许多当前对象的运行时的状态信息。如HashCode,锁状态标志,指向锁记录的指针,偏向线程ID,锁标志位等。
这把抽象的锁的信息存储在对象头的MarkWord中
4.锁的四种状态:
无锁 ,偏向锁,轻量级锁,重量级锁
5.synchronized关键字
在Java中,启用悲观锁(对象锁)是利用synchronized关键字。
synchronized被编译后会生成monitorenter和monitorexit两个字节码指令,依赖这两个字节码指令来进行线程同步
6. Monitor 监视器(一个只能容纳一个人的房间)
这就是synchronized的同步机制(通过Monitor来对线程进行控制),但是性能大大下降,由于monitor的下层时依赖于操作系统的Mutex Lock 来实现的。Java线程本质上是对操作系统线程的映射,每当挂起或者唤醒一个线程都要切换到操作系统的内核态,这个操作是比较重量级的,有的时候,切换时间超过了执行任务时间。
7.对synchronized(重量级锁)进行优化 : 偏向锁,轻量级锁
8.对象锁的四种状态
1)无锁
没有对资源进行操作系统级别(Mutex Lock)的锁定
两种语义:(1)没有多线程,没有共享对象,也没有修改数据。(2)资源会被竞争,但是不会使用操作系统同步原语进行资源锁定,而是通过别的机制来控制同步。比如CAS,通过这种函数级别的锁,我们可进行无锁编程。虽然无锁效率高,但是并不意味着无锁可以替代有锁。
2)偏向锁
偏向即为偏爱。假如只有一个线程,那么最为理想,不需要通过操作系统进行状态切换,只在用户态就可以把这件事做完。我们设想,这个对象的锁可以认识这个线程,只要它来,就把锁交出去,即这把锁偏爱这个线程,即偏向锁。
偏向锁的具体实现:
从后往前先判断锁的状态,在去读MarkWord的前23个bit,即线程ID。
3)轻量级锁
当对象发现不止有一个线程,有多个线程在竞争锁,那么偏向锁会升级为轻量级锁。
其标志为会变为00.
轻量级锁如何判断线程和锁之间的绑定关系呢?
MarkWord前30个bit变为了指向虚拟机栈中锁记录的指针。
当一个线程想要得到这个对象的锁时,假如看到MarkWord的标志为为00,就知道了它是一个轻量级锁,这时这个线程会在自己的虚拟机栈中开辟一块称为 Lock Record 的空间。
那么Lock Record 中存放什么呢?对象头的MarkWord副本和Owner指针。
线程通过CAS尝试去获取锁,一旦获得那么就复制该对象的MarkWord到自己的虚拟机栈中的Lock Record中,并且将LockRecord中的Owner指针指向该对象锁,同时MarkWord的前30个bit将生成指向持有该对象锁的线程虚拟机栈中的Lock Record ,这样一来就形成了线程和对象锁的绑定。彼此知道互相的存在。
此时,拿到锁的线程可以去干活了,这时如果有其它线程也要得到这把锁,其它线程将会“自旋”。
自旋:可以理解为一种轮询,想要获得锁的线程不断地尝试去看一下锁有没有被释放,如果释放了,就获取。如果没有释放就进行下一次循环。这种方式和被操作系统挂起堵塞不同,如果锁很快被释放了,那么这种通过自旋去获取锁的方式效率很高,完全是在用户空间上解决,不需要进行系统中断和现场恢复。
自旋相当于CPU在空转,如果时间过长,将会造成CPU资源浪费。于是有一种“适应性自旋”的优化。就是说自旋的时间动态调整。由上一次在同一个锁上的自旋时间及锁的状态来决定,比如在同一个锁上,当前正在自旋等待的线程刚刚成功获得过锁,但是锁被其他线程持有,那么虚拟机就会认为下次自旋很有可能再次成功,进而它将允许更长的自旋时间,反之亦然。这就是“适应性自旋”。
4)重量级锁
假如一个对象锁被一个线程持有,同时也有多个线程正在自旋等待,即一旦自旋等待的线程数超过一定的数量,轻量级锁升级为重量级锁。
如果对象锁被标记为重量级锁,那么和上文相同,用过Monitor来对线程进行控制,此时将会使用同步原语来锁定资源,对线程的控制也最为严格。
轻量级锁和重量级锁的区别:
轻量级锁等待的线程是在自旋,重量级锁等待的线程在等待队列中。
各有利弊,自旋时间过长,消耗资源过多,浪费CPU资源。不如直接进入队列列。



