• 各种锁机制


    一、乐观锁

    乐观锁是一种思想,严格的说乐观锁不能称之为锁。总是假设最好的情况,当前环境是读多写少,遇到并发写的概率比较低,读数据时认为别的线程不会正在进行修改(所以没有上锁)。写数据时,判断当前 与期望值是否相同,如果相同则进行更新(更新期间加锁,保证是原子性的)。

    乐观锁应用

    Java中的乐观锁:CAS,比较并替换,比较当前值(主内存中的值),与预期值(当前线程中的值,主内存中值的一份拷贝)是否一样,一样则更新,否则继续进行CAS操作。

    实现方式:

    • 版本号机制:
      • 数据版本(Version)记录机制;
      • 使用时间戳(timestamp);
    • CAS算法:
      • Compare And Swap(比较与交换),是一种有名的无锁算法。
      • CAS操作包含三个操作数——内存位置的值(V)、预期原值(A)和新值(B)。

    二、悲观锁

    悲观锁是一种悲观思想。总是假设最坏的情况,为写多读少,遇到并发写的可能性高,每次去拿数据的时候都认为其他线程会修改,所以每次读写数据都会认为其他线程会修改,所以每次读写数据时都会上锁。其他线程想要读写这个数据时,会被这个线程block,直到这个线程释放锁然后其他线程获取到锁。

    悲观锁应用

    Java中的悲观锁:synchronized修饰的方法和方法块(vector、hashtable)、ReentrantLock。
    关系型数据库中的悲观锁:行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

    三、自旋锁

    自旋锁是一种技术,为了让线程等待,我们只须让线程执行一个忙循环(自旋)。

    • 自旋锁的优点:避免了线程切换的开销。挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给Java虚拟机的并发性能带来了很大的压力。
    • 自旋锁的缺点:占用处理器的时间,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程。

    自旋锁应用

    Java中的自旋锁:CAS操作中的比较操作失败后的自旋等待。 自旋次数默认值:10次,可以使用参数-XX:PreBlockSpin来自行更改。

    四、可重入锁(递归锁)

    可重入锁是一种技术,任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞。避免死锁。

    原理:通过组合自定义同步器来实现锁的获取与释放。识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。获取锁后,进行计数自增,释放锁:释放锁时,进行计数自减。

    可重入锁应用

    Java中的可重入锁:ReentrantLock、synchronized修饰的方法或代码段。

    五、读写锁

    读写锁是一种技术,读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥。

    读写锁应用

    Java中的读写锁:ReentrantReadWriteLock,CopyOnWriteArrayList、CopyOnWriteArraySet

    六、公平锁

    公平锁是一种思想,多个线程按照申请锁的顺序来获取锁。在并发环境中,每个线程会先查看此锁维护的等待队列,如果当前等待队列为空,则占有锁,如果等待队列不为空,则加入到等待队列的末尾,按照FIFO的原则从队列中拿到线程,然后占有锁。

    七、非公平锁

    非公平锁是一种思想,线程尝试获取锁,如果获取不到,则再采用公平锁的方式。多个线程获取锁的顺序,不是按照先到先得的顺序,有可能后申请锁的线程比先申请的线程优先获取锁。

    • 优点:非公平锁的性能高于公平锁。
    • 缺点:有可能造成线程饥饿(某个线程很长一段时间获取不到锁)

    非公平锁应用

    Java中的非公平锁:synchronized是非公平锁,ReentrantLock通过构造函数指定该锁是公平的还是非公平的,默认是非公平的。

    八、共享锁

    共享锁是一种思想,可以有多个线程获取读锁,以共享的方式持有锁。和乐观锁、读写锁同义。

    共享锁应用

    Java中用到的共享锁: ReentrantReadWriteLock中读锁。

    九、独占锁

    独占锁是一种思想,只能有一个线程获取锁,以独占的方式持有锁。和悲观锁、互斥锁同义。

    独占锁应用

    Java中用到的独占锁:synchronized,ReentrantLock中写锁

    十、重量级锁

    重量级锁是一种称谓,synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本身依赖底层的操作系统的 Mutex Lock来实现。操作系统实现线程的切换需要从用户态切换到核心态,成本非常高。这种依赖于操作系统 Mutex Lock来实现的锁称为重量级锁。为了优化synchonized,引入了轻量级锁,偏向锁。

    重量级锁应用

    Java中的重量级锁:synchronized

    十一、轻量级锁

    轻量级锁是JDK6时加入的一种锁优化机制,在无竞争的情况下使用CAS操作去消除同步使用的互斥量。

    • 优点:如果没有竞争,通过CAS操作成功避免了使用互斥量的开销。
    • 缺点:如果存在竞争,除了互斥量本身的开销外,还额外产生了CAS操作的开销,因此在有竞争的情况下,轻量级锁比传统的重量级锁更慢。

    十二、偏向锁

    偏向锁是JDK6时加入的一种锁优化机制,在无竞争的情况下把整个同步都消除掉,连CAS操作都不去做了。偏是指偏心,它的意思是这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁一直没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作(例如加锁、解锁及对Mark Word的更新操作等)。

    • 优点:把整个同步都消除掉,连CAS操作都不去做了,优于轻量级锁。
    • 缺点:如果程序中大多数的锁都总是被多个不同的线程访问,那偏向锁就是多余的。

    十三、分段锁

    分段锁是一种机制。

    ConcurrentHashMap原理:
    它内部细分了若干个小的 HashMap,称之为段(Segment)。默认情况下一个 ConcurrentHashMap 被进一步细分为 16 个段,既就是锁的并发度。如果需要在 ConcurrentHashMap 添加一项key-value,并不是将整个 HashMap 加锁,而是首先根据 hashcode 得到该key-value应该存放在哪个段中,然后对该段加锁,并完成 put 操作。在多线程环境中,如果多个线程同时进行put操作,只要被加入的key-value不存放在同一个段中,则线程间可以做到真正的并行。

    线程安全:ConcurrentHashMap 是一个 Segment 数组, Segment 通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全

    十四、互斥锁

    互斥锁与悲观锁、独占锁同义,表示某个资源只能被一个线程访问,其他线程不能访问。

    十五、同步锁

    同步锁与互斥锁同义,表示并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。

    十六、死锁

    死锁是一种现象,如线程A持有资源x,线程B持有资源y,线程A等待线程B释放资源y,线程B等待线程A释放资源x,两个线程都不释放自己持有的资源,则两个线程都获取不到对方的资源,就会造成死锁。

    十七、锁粗化

    锁粗化是一种优化技术,如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作都是出现在循环体体之中,就算真的没有线程竞争,频繁地进行互斥同步操作将会导致不必要的性能损耗,所以就采取了一种方案:把加锁的范围扩展(粗化)到整个操作序列的外部,这样加锁解锁的频率就会大大降低,从而减少了性能损耗。

    十八、锁消除

    锁消除是一种优化技术,就是把锁干掉。当Java虚拟机运行时发现有些共享数据不会被线程竞争时就可以进行锁消除。

    synchronized是Java中的关键字:用来修饰方法、对象实例。属于独占锁、悲观锁、可重入锁、非公平锁。
    Lock:是Java中的接口,可重入锁、悲观锁、独占锁、互斥锁、同步锁。
    ReentrantLock是Java中的类 :继承了Lock类,可重入锁、悲观锁、独占锁、互斥锁、同步锁。
  • 相关阅读:
    用Maloja创建音乐收听统计数据
    强化学习总结1 马尔科夫过程
    阴影(shadow mapping)(硬阴影)
    详解Unity中的Nav Mesh新特性|导航寻路系统 (一)
    进程初识
    【网络是怎么连接的】第二章(中):一个网络包的发出
    FPGA实战小项目2
    手机微博保存的图片无法在电脑端查看 - 解决方案
    再也不想去阿里巴巴面试了,太侮辱人了,6年测开经验面试被按在地上摩擦。
    Python机器学习入门指南
  • 原文地址:https://blog.csdn.net/jingmiaowill/article/details/126507763