乐观锁与悲观锁
乐观锁:这个锁认为出现锁竞争大概率比较小(当前场景中,线程数目比较少,不太涉及锁竞争),工作量比较小,付出的代价也比较少。
悲观锁:这个锁认为出现锁竞争大概率比较大(当前场景中,线程数目比较多,非常可能涉及锁竞争),工作量比较大,付出的代价也比较多。
操作系统中的Mutex就是一个典型的悲观锁。Java中的synchronize既属于乐观锁也属于悲观锁,会根据当前锁冲突的状况来切换模式。
读写锁
这是一种特殊的锁,将读操作和写操作分别加锁,能够进一步减少锁冲突。一般具有以下三种情况:
读加锁和读加锁之间不发生互斥。
读加锁和写加锁之间发生互斥。
写加锁和写加锁之间发生互斥。
根据这三种情况不难判断出该锁更加适用于少写多读的场景当中。
重量型锁和轻量型锁
这两种锁非常类似于乐观锁和悲观锁,重量型锁大概率也是悲观锁,轻量型锁大概率也是乐观锁。乐观锁和悲观锁根据锁冲突来划分的,而重量型锁和轻量型锁根据工作量来划分的。
轻量型锁的实现:
1.如果同步对象没有被锁定,则在虚拟机在当前线程的栈帧中划分一个记录锁空间,存储锁对象目前Mark Word的拷贝。
2.虚拟机尝试使用CAS将Mark Word更新为指向锁的记录指针。
3.如果更新成功则表示这该线程拥有锁,锁标记为00;如果更新失败,就会检查Mark Word是否指向当前线程的栈帧,如果是则说明当前线程已经获取到锁,直接进入同步状态,如果不是则说明锁被另一个线程锁获取到了,这时就不再是轻量型锁了,而是膨胀为重量型锁。
公平锁和非公平锁
遵循先来后的加锁顺序称为公平锁,不遵循则成为非公平锁。一般的线程的调度是不确定的,因此默认不做任何处理的锁就是非公平锁,实现公平锁则需要额外的处理。
可重入锁和不可重入锁
一个线程对同一把锁进行两次加锁,若没有问题则成为可重入锁,若存在问题则成为不可重入锁。synchronized就属于一种可重入锁,通过对synchronized我们来了解一下可重入锁的机制。
在synchronized当中持有哪个线程获取到锁并且将会维护一个计数器,当该线程再次遇到加锁状况时并没有真正的加锁,而是计数器自增,同理可的当遇到解锁时并不会真正的解锁,而是计数器自减,直到计数器为0时才会真正地解锁。
死锁
死锁的出现往往伴随着线程挂掉等严重的Bug的出现:
1.一个线程一把锁之间通过可重入锁可完美的解决这个问题。
2.两个线程两把锁,线程一获取到锁A,在获取锁B,线程二获取到锁B在获取锁A,那么这个时候,线程一想要获取锁B则需要线程二解开锁B,而线程二想要获取锁A则需要线程一解开锁A,这样就形成了环路等待。
3.N个线程M把锁,与2情况相同原因,形成了环路等待。
解决方法:
1.一般的在锁当中不要轻易的加锁,不过说起来容易做起来难,很大的概率根据需求分析不可避免的锁上加锁。
2.约定一个固定的加锁顺序,例如要先加锁1,再加锁2,再加锁3。这样不会形成环路等待。