Java锁

作者: 简楼 | 来源:发表于2021-04-17 20:31 被阅读0次

    前言

    在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。
    锁旨在强制实施互斥排他、并发控制策略。

    锁的分类

    乐观锁和悲观锁

    乐观锁/悲观锁不是指具体类型的锁,而是看待并发的角度。

    乐观锁

    乐观锁认为不存在很多的并发更新操作,不需要加锁;
    数据库中乐观锁的实现一般采用版本号,Java中可使用CAS实现乐观锁。

    1. ABA问题(JDK1.5之后已有解决方案):CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。
    2. 循环时间长开销大:CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。
    3. 只能保证一个共享变量的原子操作(JDK1.5之后已有解决方案):对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。

    使用场景:
    比较适合读取操作比较频繁的场景。

    实现方式:
    1.版本号
    2.CAS
    3.原子类(Java的java.util.concurrent.atomic包下)

    悲观锁

    悲观锁认为存在很多并发更新操作,采取加锁操作,如果不加锁一定会有问题;

    Java 里面的同步原语 synchronized 关键字的实现就是悲观锁。

    使用场景:
    比较适合写入操作比较频繁的场景。

    实现方式:
    synchronized 和 Lock

    公平锁和非公平锁

    公平锁

    指多个线程按照申请锁的顺序来获取锁;

    非公平锁

    指多个线程获取锁的顺序是随机的,并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。
    有可能,会造成优先级反转或者饥饿现象。

    饥饿现象
    线程一直无法获取锁或其他资源,导致线程一直无法到执行的状态;
    导致无法获取的原因:

    1. 线程优先级较低,没办法获取cpu时间;
    2. 其他线程总是能在它之前持续地对该同步块进行访问;
    3. 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方法),因为其他线程总是被持续地获得唤醒;

    实现方式:
    ReenTrantLock(公平(true)/非公平(false))

    对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大;

    对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的控制线程对锁的获取, 所以并没有任何办法使其变成公平锁;

    可重入锁

    可重入锁又名递归锁,是指同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁;

    ReentrantLock和Synchronized都是可重入锁。可重入锁的一个好处是可一定程度避免死锁;

    需要注意的是,可重入锁加锁和解锁的次数要相等;

    独享锁和共享锁(读写锁)

    独享锁是指该锁一次只能被一个线程所持有, 具体实现有ReentrantLock、Synchronized;
    共享锁是指该锁可被多个线程所持有,具体实现有ReadWriteLock;

    独享锁和共享锁(读写锁)也是通过AQS来实现的;

    锁升级:读锁到写锁 (不支持)
    锁降级:写锁到读锁 (支持)

    偏向锁/轻量级锁/重量级锁(jdk 1.6 以后)

    偏向锁:当前只有这个线程获得,没有发生争抢,此时将方法头的markword设置成0,然后每次过来都cas一下就好,不用重复的获取锁,降低获取锁的代价;

    轻量级锁:在偏向锁的基础上,有线程来争抢,此时膨胀为轻量级锁,多个线程获取锁时用cas自旋获取,而不是阻塞状态;

    重量级锁:轻量级锁自旋一定次数后,膨胀为重量级锁,其他线程阻塞,当获取锁线程释放锁后唤醒其他线程。(线程阻塞和唤醒比上下文切换的时间影响大的多,涉及到用户态和内核态的切换)

    实现方式: synchronized

    分段锁

    分段锁是一种锁的设计,并不是一种具体的锁。
    对于ConcuttentHashMap就是通过分段锁实现高效的并发操作,有兴趣的可以去阅读下相关源码;

    分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作;

    自旋锁

    自旋锁是指尝试获取锁的线程不会阻塞,而是采用循环的方式尝试获取锁。
    好处是减少上下文切换,缺点是一直占用CPU资源;

    相关文章

      网友评论

          本文标题:Java锁

          本文链接:https://www.haomeiwen.com/subject/cbdplltx.html