[JUC-18] 一文搞定所有的锁

1、锁的分类

  你是不是听说过什么乐观锁、悲观锁、可重入锁、不可重入锁、共享锁、独占锁等等一堆锁,听到头皮发麻?

  其实 Java 的锁也是按照不同的标准来分类的。
在这里插入图片描述

2、乐观锁和悲观锁

  悲观锁很悲观,怕出错,总考虑最坏的情况,线程来访问就锁死,不让其他线程来访问,直到锁释放。

  乐观锁很乐观,不怕出错,不担心别人修改,所有线程一起上。

  两者的流程:
在这里插入图片描述
  两者的区别:
在这里插入图片描述

3、公平锁和非公平锁

  公平锁很公平,当一个线程获取了资源并上锁,后面接着来的线程依次排队,当资源的锁释放时,后面的线程依次访问,很公平。
在这里插入图片描述
  非公平锁很不公平,排队的线程看谁执行的效率高就插队去访问资源,不管它是不是先来的。
在这里插入图片描述

4、可重入锁和非可重入锁

  可重入锁,也叫递归锁,是指在同一个线程的最外层方法获取锁的时候,再进入该线程的内层方法时会自动获取锁。(前提是锁对象是同一个或者是class对象)

  ReentrantLocksynchronized都是可重入锁。

class MyClass {
    public synchronized void method1() {
        doSth();
    }

    public synchronized void method2() {
        doAnother();
    }
}

  当一个线程执行到某个synchronized方法时,比如说method1,在method1中又会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2

  如果不是可重入锁的话,method2可能不会被当前线程执行,可能造成死锁。

  用自旋锁来模拟,代码如下:

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {
    Thread current = Thread.currentThread();
    //这句是很经典的“自旋”语法,AtomicInteger中也有
    for (;;) {
        if (owner.compareAndSet(null, current)) {
            return;
        }
    }
}

    public void unlock() {
        Thread current = Thread.currentThread();
        owner.compareAndSet(current, null);
    }
}

  同一线程两次调用lock()方法,如果不执行unlock()释放锁的话,第二次调用自旋的时候就会产生死锁。

5、自旋锁

  上面举例非可重入锁时拿自旋锁来当例子,那什么是自旋锁呢?

  所谓自旋锁,就是一个线程拿不到锁时,不会进入阻塞状态,而是原地空转等待,直到锁释放。如果等待超过一定时间,则进入阻塞状态。循环多少次可以认为指定,也可以自适应,自适应的也叫做自适应旋转锁。

  下面是自旋锁的例子:

public class SpinLock {
    private AtomicReference<Thread> cas = new AtomicReference<Thread>();
    public void lock() {
        Thread current = Thread.currentThread();
        // 利用CAS
        while (!cas.compareAndSet(null, current)) {
            // DO nothing
        }
    }
    public void unlock() {
        Thread current = Thread.currentThread();
        cas.compareAndSet(current, null);
    }
}

  当线程 A 进入 lock() 方法后,while 语句里设置成功,加上取反符号,因此不会进入 while 内部,直接结束 lock() 方法。此时cas的值为线程 A。

  然后线程 B 进入lock()方法,while 判断为 true,导致自己直接在这里不断判断循环。

  当线程 A 调用unlock()方法,把cas的值又变为了 null。此时,在那里空转的线程 B 突然判断出cas.compareAndSet(null, current)为 true,加上取反,于是跳出 while 循环,此时cas的值变为线程 B,我们也叫做:线程 B 拿到了锁。

  这就是完整的自选锁原理,利用的是 CAS 机制,即比较和交换(Compare And Swap)。

  CAS 锁主要存在以下问题:
  1、如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。
  2、本身无法保证公平性,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。
  3、无法保证可重入性。基于自旋锁,可以实现具备公平性和可重入性质的锁。

6、阻塞锁

  上面说的自旋锁,就是等待锁释放的线程不会进入阻塞状态,而是在原地空转。

  阻塞锁在等待锁释放期间会进入阻塞状态,等待被唤醒。

  二者的区别:
在这里插入图片描述

7、只升不降的锁

  锁主要存在四种状态:“无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态”。

  其实这四种状态都不是Java语言中的锁,而是 Jvm 为了提高锁的获取与释放效率而做的优化(使用synchronized时)。

  它们会随着竞争的激烈而逐渐升级,并且是不可逆的升级。

  升级过程是这样的:偏向锁 -> 轻量级锁 -> 重量级锁。
在这里插入图片描述

  关于无锁:如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性,因此会有一些代码天生就是线程安全的。它没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。CAS算法 即compare and swap(比较与交换),就是有名的无锁算法。

8、独享锁和占有锁

在这里插入图片描述
  可以参考:读写锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值