转载

Java的一堆锁是干嘛的?

Java中的锁有很多种,经常会听到“锁”这个词。

犹如每天出门时,:key:就是一种“锁”,拿不到:key:,就进不去了。

Java那么多种类的锁,都是按不同标准来分类的。就像商店里的各种商品,可以按式样,也可以按颜色或者尺寸。

其实它们都是一种思想。

为什么会有锁?

一个进程可以包含多个线程,那么多个线程就会有竞争资源的问题出现,为了互相不打架,就有了锁的概念了。 一个线程也可以自己完成任务,但就像一个小组可以互相配合、共同完成任务,比一个人要快很多是不是?

分类

来个图~

Java的一堆锁是干嘛的?

1 悲观 Vs 乐观

林妹妹比较悲观,宝玉比较乐观~

Java的一堆锁是干嘛的?

1.1 悲观锁

看名字便知,它是悲观的,总是想到最坏的情况。 锁也会悲观,它并不是难过,它只是很谨慎,怕做错。

每次要读data的时候,总是觉得其他人会修改数据,所以先加个:closed_lock_with_key:,让其他人不能改数据,再慢慢读~

要是你在写一篇日记,怕别人会偷看了,就加了个打开密码,别人必须拿到密码才能打开这篇文章。这就是悲观锁了。

应用: synchronized关键字和Lock的实现类都是悲观锁。

1.2 乐观锁

它很乐观,总是想着最好的情况。 它比较大条,不会太担心。如果要发生,总会发生,如果不会发生,那就不会。为什么要担心那么多?

每次读data时,总是乐观地想没有其他人会同时修改数据,不用加锁,放心地读data。 但在更新的时候会判断一下在此期间别人有没有去更新这个数据。

就像和别人共同编辑一篇文章,你在编辑的时候别人也可以编辑,而且你觉得别人不会改动到你写的部分,那就是乐观锁了。

事事无绝对,悲观也好乐观也好,没有绝对的悲观,也没有绝对的乐观。只是在这个当时,相信,还是不相信。

悲观 & 乐观 比较

类型 实现 使用场景 缺点
悲观锁 synchronized关键字和Lock的实现类 适合写操作多的场景,可以保证写操作时数据正确 如果该事务执行时间很长,影响系统的吞吐量
乐观锁 无锁编程,CAS算法 适合读操作多的场景,能够大幅提升其读操作的性能 如果有外来事务插入,那么就可能发生错误

应用

乐观锁 —— CAS(Compare and Swap 比较并交换)

是乐观锁的一种实现方式。

简单来说,有3个三个操作数:

  • 需要读写的内存值 V。
  • 进行比较的值 A。
  • 要写入的新值 B。

2 公平 Vs 非公平

没有绝对的公平,也没有绝对的不公平。

公平,就是按顺序排队嘛。 公平锁维护了一个队列。要获取锁的线程来了都排队。

Java的一堆锁是干嘛的?

非公平,上来就想抢到锁,好像一个不讲道理的,抢不到的话,只好再去乖乖排队了。 非公平锁没有维护队列的开销,没有上下文切换的开销,可能导致不公平,但是性能比fair的好很多。看这个性能是对谁有利了。

举个栗子

Java的一堆锁是干嘛的?
Java的一堆锁是干嘛的?

3 可重入锁 Vs 非可重入锁

广义上的可重入锁,而不是单指JAVA下的ReentrantLock。

可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。

这句话神马意思?

这种锁是可以反复进入的。

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

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

    public synchronized void method2() {
        // todo
    }
}
复制代码

两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。

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

可重入锁最大的作用是避免死锁。

实现:

  • synchronized
  • ReentrantLock

ReentrantLock构造函数

提供了是否公平锁的初始化:

/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
复制代码

使用ReentrantLock必须在finally控制块中进行解锁操作。

在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍,而ReentrantLock确还能维持常态。

高并发量情况下使用ReentrantLock。

优点: 可一定程度避免死锁。

  • Semaphore
  • AtomicInteger、AtomicLong等

不可重入锁的栗子

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

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()释放锁的话,第二次调用自旋的时候就会产生死锁。

原文  https://juejin.im/post/5cf4d520f265da1b6a347ed1
正文到此结束
Loading...