Java中的锁有很多种,经常会听到“锁”这个词。
犹如每天出门时,:key:就是一种“锁”,拿不到:key:,就进不去了。
Java那么多种类的锁,都是按不同标准来分类的。就像商店里的各种商品,可以按式样,也可以按颜色或者尺寸。
其实它们都是一种思想。
一个进程可以包含多个线程,那么多个线程就会有竞争资源的问题出现,为了互相不打架,就有了锁的概念了。 一个线程也可以自己完成任务,但就像一个小组可以互相配合、共同完成任务,比一个人要快很多是不是?
来个图~
林妹妹比较悲观,宝玉比较乐观~
看名字便知,它是悲观的,总是想到最坏的情况。 锁也会悲观,它并不是难过,它只是很谨慎,怕做错。
每次要读data的时候,总是觉得其他人会修改数据,所以先加个:closed_lock_with_key:,让其他人不能改数据,再慢慢读~
要是你在写一篇日记,怕别人会偷看了,就加了个打开密码,别人必须拿到密码才能打开这篇文章。这就是悲观锁了。
应用: synchronized关键字和Lock的实现类都是悲观锁。
它很乐观,总是想着最好的情况。 它比较大条,不会太担心。如果要发生,总会发生,如果不会发生,那就不会。为什么要担心那么多?
每次读data时,总是乐观地想没有其他人会同时修改数据,不用加锁,放心地读data。 但在更新的时候会判断一下在此期间别人有没有去更新这个数据。
就像和别人共同编辑一篇文章,你在编辑的时候别人也可以编辑,而且你觉得别人不会改动到你写的部分,那就是乐观锁了。
事事无绝对,悲观也好乐观也好,没有绝对的悲观,也没有绝对的乐观。只是在这个当时,相信,还是不相信。
类型 | 实现 | 使用场景 | 缺点 |
---|---|---|---|
悲观锁 | synchronized关键字和Lock的实现类 | 适合写操作多的场景,可以保证写操作时数据正确 | 如果该事务执行时间很长,影响系统的吞吐量 |
乐观锁 | 无锁编程,CAS算法 | 适合读操作多的场景,能够大幅提升其读操作的性能 | 如果有外来事务插入,那么就可能发生错误 |
是乐观锁的一种实现方式。
简单来说,有3个三个操作数:
没有绝对的公平,也没有绝对的不公平。
公平,就是按顺序排队嘛。 公平锁维护了一个队列。要获取锁的线程来了都排队。
非公平,上来就想抢到锁,好像一个不讲道理的,抢不到的话,只好再去乖乖排队了。 非公平锁没有维护队列的开销,没有上下文切换的开销,可能导致不公平,但是性能比fair的好很多。看这个性能是对谁有利了。
举个栗子
广义上的可重入锁,而不是单指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可能不会被当前线程执行,可能造成死锁。
可重入锁最大的作用是避免死锁。
实现:
提供了是否公平锁的初始化:
/** * 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。
优点: 可一定程度避免死锁。
用自旋锁来模拟,代码如下:
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()释放锁的话,第二次调用自旋的时候就会产生死锁。