Java并发场景中,公平锁、乐观锁、悲观锁等各种锁,本文介绍各种锁的分类:公平锁/非公平锁可重入锁独占锁/共享锁乐观锁/悲观锁段锁自旋锁01.乐观锁vs悲观锁乐观锁和悲观锁是广义上的概念,它反映了线程同步的不同观点,并且在Java和数据库中都有对应这个概念的实际应用。1.乐观锁,顾名思义就是非常乐观。每次去拿数据,你以为别人不会修改,所以不会上锁。但是在更新的时候,你会判断这段时间别人有没有更新过数据。您可以使用版本号等机制。乐观锁适用于多读应用类型。Java中使用无锁编程实现了乐观锁。最常用的是CAS算法。Java原子类中的自增操作是通过CAS自旋实现的。CAS的全称是CompareAndSwap(比较和交换),是一种无锁算法。多线程之间的变量同步是在不使用锁的情况下实现的(没有线程被阻塞)。java.util.concurrent包中的原子类通过CAS实现了乐观锁。简单来说,CAS算法有3个操作数:需要读写的内存值V。要比较的值A。要写入的新值B。修改内存值V为B当且仅当期望值A与内存值V相同,否则返回V。这是乐观锁的一种思想。它相信在它被修改之前没有其他线程会修改它;synchronized是悲观锁。它认为在修改之前一定有其他线程修改它。悲观锁效率很低。2.悲观锁总是假设最坏的情况。每次去拿数据,你都以为别人会修改,所以每次拿数据的时候,你都会加锁,让别人一直阻塞,直到拿到数据。锁。传统的MySQL关系型数据库中使用了很多这样的锁机制,比如行锁、表锁、读锁、写锁等,都是在操作前加锁。详见:阿里P8架构师谈:MySQL行锁、表锁、悲观锁、乐观锁的特点及应用。比如上面提到的Java中synchronized关键字的实现就是典型的悲观锁。3、简而言之:悲观锁适用于写操作较多的场景。先加锁可以保证写操作时数据是正确的。乐观锁适用于读操作较多的场景,无锁的特性可以极大的提高读操作的性能。02.公平锁vs非公平锁1.公平锁非常公平。在并发环境下,每个线程在获取锁的时候都会先查看这个锁维护的等待队列。如果为空,或者当前线程在等待队列中*第一个持有锁,否则加入等待队列,以后按照先进先出的规则从队列中取出。公平锁的好处是等待锁的线程不会饿死。缺点是整体吞吐效率低于非公平锁,等待队列中除第一个线程外的所有线程都会被阻塞,CPU唤醒阻塞线程的开销比非公平锁大。2.当出现非公平锁时,尝试直接占用锁。如果尝试失败,则使用类似于公平锁的方法。unfairlylock的好处是可以减少调用线程的开销,整体吞吐效率高,因为线程有机会直接获取锁而不阻塞,CPU不用唤醒所有线程。缺点是等待队列中的线程可能会饿死,或者等待很长时间才能获取锁。3、典型应用:javajdk并发包中的ReentrantLock可以指定构造函数的boolean类型来创建公平锁和非公平锁(默认),例如:可以使用newReentrantLock(true)实现公平锁。03.独占锁vs共享锁1.独占锁是指锁在同一时间只能被一个线程持有。2.共享锁是指锁可以被多个线程持有。3、与JavaReentrantLock相比,它是一种独占锁。但是对于Lock的另一个实现类ReadWriteLock,它的读锁是共享锁,写锁是排它锁。读锁的共享锁可以保证并发读的效率很高,并且读和写、写和读、写和写的过程是互斥的。独占锁和共享锁也是通过AQS实现的,通过实现不同的方法来实现独占或者共享。4、AQS抽象队列同步器(AbstractQueuedSynchronizer,简称AQS)是构建锁或其他同步组件的基础框架。它通过内置的FIFO队列使用一个整型volatile变量(命名为state)来维护同步状态。完成资源获取线程的排队工作。并发包的实现结构如上图所示。AQS、非阻塞数据结构、原子变量类等基础类都是基于volatile变量的读写和CAS实现,而锁、同步器、阻塞队列、Executors、并发容器等高级类都是基于基类实现的。04.分段锁分段锁其实是一种锁的设计,并不是一种具体的锁。对于ConcurrentHashMap,它的并发实现是通过分段锁的形式实现高效的并发操作。下面用ConcurrentHashMap来谈谈段锁的含义和设计思想。ConcurrentHashMap中的段锁称为Segment,类似于HashMap的结构(JDK7和JDK8中HashMap的实现),即内部有一个Entry数组,数组中的每个元素都是一个链表;同时,它也是一个ReentrantLock(Segment继承了ReentrantLock)。当需要放一个元素的时候,不是锁定整个hashmap,而是先通过hashcode知道要放到哪个segment,然后锁定这个segment,所以多线程put的时候,只要不放到一段,实现了真正的平行插入。但是在统计size的时候,需要先获取所有的segmentlock,然后才能获取hashmap的全局信息。分段锁的设计目的是细化锁的粒度。当操作不需要更新整个数组时,只锁定数组中的一项。
