超屌的多线程锁分类,你确定不看看吗?( 二 )

还记得前面讲的Volatile是基于总线监听实现的可见性吗?这里如果线程特别多,大家都在监听flag,这对于带宽容量有限的主存来说,线程的不断增加,压力会越来越大,这也桑畅TicketLock的缺点 。
CLHlockCLHLock其实是三个人发明的:Craig, Landin和Hagersten所以叫CLH了,它的底层是基于链表的公平自旋锁 。赫赫有名的AQS(AbstractQueuedSynchronizer)就是基于这种锁变种而来的 。在CLH中,所有相互竞争的线程都被放到一个链表中排队,每一个线程被抽象成一个链表的节点,每一个节点在前趋结点的locked域上自旋等待 。当前驱释放锁状态,则后续节点就可以进行获取锁的操作 。在以后的文章里会手撕AQS的,今天主要介绍一下有啥,埋个种子 。
MCSlockCLHLock这么牛13了,还整个MCSLock干啥呢?原因竟然是为了兼容硬件系统,从架构上来看,分为三大怪物:SMP, MPP和NUMA,问题就出在了这个NUMA上了 。它的中文名是:非一致存储访问结构 。正是因为这种结构,导致了在使用CLHLock时,后节点在获取前节点中的locked域状态时内存过远 。行了,当做八股文背住就行了,面试估计也没人问这个,写BUG更用不到 。

超屌的多线程锁分类,你确定不看看吗?

文章插图
 
公平锁与非公平锁公平锁是指多个线程按照申请锁的顺序来依次获取锁,线程直接进入队列中排队,当共享资源可用时,只有队列中的第一个线程才能获得锁 。公平锁的优点是等待锁的线程不会饿死 。但是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大 。
非公平锁是指多个线程加锁时直接尝试获取锁,加锁失败时,才会被放入队列中去 。但如果此时锁刚好可用,那么这个线程可以直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景 。优点是可以减少唤起线程的开销,吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程 。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁 。
重入锁和不可重入锁可重入锁以Java为例ReentrantLock和Synchronized都是可重入锁,是指在同一个线程在外层方法获取锁的时候,如果其内部调用的方法也有锁,则可以直接获取锁,不会因为之前的锁还没释放而阻塞,一定程度上避免了死锁 。在下面的代码中,testRoller()和testRunning() 都是加了锁的两个方法,因为Synchronized是可重入锁,所以在testRoller()中调用testRunning()时,可以直接获取锁 。
/** * FileName: TestLock * Author:   RollerRunning * Date:     2020/12/3 9:34 PM * Description: */public class TestLock {    public synchronized void testRoller() {        System.out.println("testRoller....");        testRunning();    }    public synchronized void testRunning() {        System.out.println("testRunning....");    }}共享锁和独占锁独享锁是一种吃独食的锁,一次只能被一个线程持有 。如果线程A对共享数据独占锁以后,那么其他线程就都没有机会再加锁了,获得排它锁的线程就拥有了对该数据的读写权限 。JDK中的Synchronized以及Lock的实现类就是互斥锁 。
共享锁是指这类锁可被多个线程持有 。如果线程A对共享数据加上共享锁后,则其他线程也只能对共享数据加共享锁,不能加排它锁 。而获得共享锁的线程只能读数据,不能修改数据 。
最后贴一张锁分类图:
超屌的多线程锁分类,你确定不看看吗?

文章插图




推荐阅读