我们知道每一个Object类都自带锁,在了解锁之前我们先了解Java的对象结构。
Java的对象分为三个部分,主要的部分就是对象头和对象体,填充部分是因为JVM规定对象的起始地址必须为8字节的整数倍,所以在实例不满足8字节的整数倍情况下进行填充,对象头一定满足。
Mark Word
长度为一个JVM字,取决于JVM为32位还是64位;包含了对象的关键信息,例如hashCode, GC分代年龄,锁状态标识,线程持有的锁等
Class Pointer
指向对象所属类的Class对象,长度也为一个JVM Word,不过通常可以进行指针压缩
我们知道了Mark Word中含有对象的锁信息,那Java内置锁有什么信息呢,我们可以分为以下几种状态,注意,这个状态的顺序只能由低到高,是不可逆的,随着竞争的情况升级。
无锁即对象不会被线程竞争,所以不需要锁,在它的Mark Word中也没有锁相关的信息。
这里补充一个概念:安全点是在程序执行期间的所有GC Root
已知并且所有堆对象的内容一致的点。
如果一个线程获得了一个对象的锁,但是实际上,不会有其他的线程来和它竞争这个锁,那么此时运用偏向锁应该是最高效的。锁对象的Mark Word的结构中锁标记位变为01,并且记录下线程的ID和标志位,之后线程再次获取该锁时,比较Mark Word中的信息和自己的信息,如果一致就直接进入同步区,无需其他操作。
偏向锁的获取流程:
偏向锁的释放:
如果出现其他线程竞争锁,检查锁拥有线程是否存活,如果非存活则直接撤销偏向锁,如果存活需要将该线程先挂起,然后升级为轻量级锁,这些操作需要在安全点进行。
出现竞争时,锁转向轻量级锁。
当一个抢锁线程发现锁对象没有锁定时,它会在栈帧中记录拷贝一份当前锁对象的Mark Word,然后用CAS操作尝试修改对象Mark Word,把标记位修改为00,剩下内容替换为指向线程栈中Displaced Mark Word中的指针,这个操作如果成功,则说明线程此时占有锁对象,可以进入同步区。当锁重入时,DisPlaced Mark Word置空,obj指向对象头,表示为冲入锁。
与偏向锁不同的是,同步区方法执行完毕,轻量级锁会自动释放锁,这是一个CAS操作。
线程竞争过程中,如果一个线程无法CAS替换Mark Word成功,则自旋尝试替换,直到自旋次数达到阈值,膨胀为重量级锁 。
重量级锁采用了监视器机制,Java中每一个对象都关联一个监视器(Monitor),监视器保障代码的互斥,一次只能有一个线程进入临界区,得不到许可的线程将被阻塞。
膨胀到重量级锁后,会指向ObjectMonitor对象,它有以下核心变量:
线程抢占锁的流程图如下:
Cxq的本质其实是一个单向链表,链表头插实现队列,也就是说在链表头添加入队线程,从队尾获取线程;不过线程进入Cxq之前,会先自旋尝试获取锁,即将owner修改为自己,这样对Cxq中的线程不公平。
当owner线程释放锁时,cxq中的线程会进入entrylist,entrylist中的线程,通常为head节点会被指定为onDeck线程
JVM在释放锁后,让onDeck线程去参与锁竞争,这个过程中会和新入的抢锁线程竞争,所以还是不公平的。
如果一个正在执行的owner线程调用wait方法阻塞,将进入waitset队列,直到被主动唤醒。
下面是notify和wait的执行流程:
这些方法都由监视器完成,监视器在底层交给了操作系统来实现,即OS中熟悉的mutex互斥机制。
CAS操作即比较并替换,在Java中通常通过调用Unsafe类的方法进行实现
Unsafe提供的方法为原子性的比较并替换方法,方法接收四个参数:
Unsafe类通过第一个参数和第二个参数计算对象的具体位置,然后依赖CPU的原子性操作,在相应的位置上获取值,与期望值相比较,如果符合期望值,就将其修改为新值,否则继续自旋更新直到成功
这里说的期望值是我们的方法在进行CAS操作时,先在内存获取的值,进入CAS操作时很可能被其他的线程修改了。
如果一个线程执行某个操作时预期值是A,而它正准备操作时,另一个线程进来把A修改为B,然后再修改为A,回到该线程,预期值仍是A,对它而言可以正确执行操作;然而事实是两个A不能等同于一个A。
解决的方法是可以给对象的引用设置一个唯一的标记,比较时不仅比较值还需要比较标记,二者相等时才能进行正确的修改。
悲观锁悲观的认为并发一定会造成数据冲突。
悲观锁的机制就是独占锁的机制,把同步区加锁防止并发访问,进入区后释放锁,悲观锁具有强烈的独占性和排他性。sychronized中的重量级锁就是典型的悲观锁。悲观锁又能分为共享锁和排他锁,共享锁为只读不写,排他锁为互斥访问的锁。
悲观锁的特点为安全性足够高,加锁和释放合理很难出现并发相关的问题,缺点为悲观锁的开销通常比较大,在竞争不大的情况下,频繁加锁释放,挂起线程会降低效率,同时有可能引起死锁。
乐观锁严格来说加的锁是比较轻量的,它更期望使用数据本身来解决,核心机制就是冲突检测和数据更新,这是乐观锁的思想,CAS是乐观锁的一种实现。
这里的公平/非公平指的是线程获得锁的顺序是否按照锁申请的顺序得到的。
sycronized使用的内置锁是不可中断锁,这里指的是否可以中断指的是抢锁的过程是否能响应中断,即其他线程调用的Thread.interrput方法是否能在抢锁过程中做出响应。
Lock中的lockInterruptibly和tryLock
方法就可以响应抢锁时中断。
通俗地介绍可重入的概念:当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。
主要依赖的是owner指针和计数器,在owenr指针指向某个线程后,计数器会置1,其他的线程申请锁将阻塞;如果是owner线程再次申请锁,将得到锁,并且将计数器递增;每次线程退出同步区后,计数器减一,直到计数器为0时锁释放。