Java锁分类原来是这个样子

886 阅读5分钟

学了几天python,辣条君始终不忘自己其实是个Javaer。来,跟着辣条君一起看看Java锁是如何分类的吧。

Java锁是为了保证并发过程中,数据维护的准确性。

乐观锁与悲观锁

乐观锁:认为当前读取数据的时候,不会有线程去修改数据,所以不需要加锁。当更新数据的时候,首先查看数据和自己曾经记录的数据是否一致,如果一致,则更新之;如果不一致,采取一些手段,比如报错或者自旋(自旋后面会讲)。

举个例子,一个线程A读取账户余额时,不会加锁,读到20元,线程A账户记录更新为20元。然后线程A为账户余额增加5元,现在想把账户余额更新为25元,先去查看账户现在的数值为20元,和账户记录一致,就将账户余额直接更新为25元,同时将自己的账户记录更新为25元。过了一会儿,线程A又想给账户余额增加5元,于是拿着30元去更新账户余额。此时,发现账户余额为100元,和自己的账户记录(25元)不一致了,就报错(或者自旋),此次更新失败。

CAS(比较交换)就是一种常见的乐观锁实现方案,java.util.concurrent.atomic中的那些原子类就是通过CAS算法实现的。为了保证更新的原子性,原子类最终实质上是通过JNL调用了CPU的原子操作。CAS先天有两点不足:1、ABA问题。2、长时间的自旋会消耗过多的资源。

所以乐观锁多用于读数据多的场景,效率较高。

悲观锁:认为无论自己进行什么操作,那一瞬间都会有其他线程来污染数据,所以一定要加锁。

悲观锁很好理解,不需要加什么例子了,synchronizedLock都属于悲观锁。关于这两个类的使用,我想另写一篇博客,毕竟日常使用比较多嘛。悲观锁多用于写数据多的场景

在这里插入图片描述

自旋锁

上一节反复提到自旋,自旋究竟是个什么东东呢?

首先我们要知道,一个Java线程被阻塞,会放弃CPU使用权;被唤醒,会重新获得CPU使用权。这两个切换上下文的过程,是极其消耗资源的。如果,一个同步操作(线程占用锁)的时间极短,那需要用锁的线程可以先等一会儿,待会不用进行上下文切换,拿到锁直接执行,那岂不是极好的。这个等待操作就叫做自旋。

自旋操作一般会规定自旋次数,如果一定次数还是没有得到锁,那就放弃自旋,进行阻塞。为了更加提升效率,自适应自旋锁出现了,它不拘泥于固定的次数,而是根据以往经验,如果以前自旋一段时间可以得到锁,那么超过最大自旋数的时候,允许多自旋几次;如果以往经验总是失败,那么不一定非得到达最大自旋数,就直接进入自旋状态。

无锁、偏向锁、轻量级锁、重量级锁

根据切换资源消耗成本,可以将锁分为无锁、偏向锁、轻量级锁、重量级锁。

无锁:就是不对资源加锁,例如上面讲到的CAS算法,只是在更新的时候进行一下比较判断就好。

偏向锁:有一种理想的状态,一段时间内只有一个线程访问同步代码块,这样是不是连更新时比较的步骤都可以省略了。这种情况下可以挂上偏向锁,这样该线程在访问同步代码块的时候就不需要CAS操作了。当,有其他线程来访问共享资源的时候,偏向锁自动升级为轻量级锁。如果没有线程来打扰,只有当虚拟机运行到全局安全点的时候才能撤销偏向锁。

轻量级锁:当一个线程拥有轻量级锁,另一个线程想拥有这把锁,不会进入阻塞状态,而是先自旋,等待获得锁的机会。但是,当多个线程(至少两个)来获取这把锁时,这把锁会直接升级为重量级锁

重量级锁:当一个线程拥有重量级锁时,其他线程想要获取该锁,都会直接进入阻塞状态。在JDK1.6之前synchronized机制使用的时重量级锁,1.6版本之后开始使用轻量级锁偏向锁

在这里插入图片描述

公平锁和非公平锁

公平锁:当多个线程请求获取锁时,根据请求的先后顺序放到一个队列里,然后按顺序获取锁。此时,线程从阻塞到唤醒是需要上下文切换的。保证公平性,但是效率可能较低。

非公平锁:非公平锁,尝试被线程获取的时候,不一定从线程队列中获取,先看看此时有没有新的线程来获取本锁,如果有,直接把锁给该线程,不需要进行上下文切换。失去公平性,但是可能会提高效率。

可重入锁

可重入锁是指同一个线程可以多次加同一把锁。ReentrantLocksynchronized都属于可重入锁

public class MyTest {
    // 方法嵌套
    public synchronized void outThing() {
        // do someting
        innerThing();
    }
    public synchronized void innerThing() {
        // do something
    }
}

看上面这种情况,方法嵌套通过synchronized机制2次获取了对象的锁(Monitor)。如果是非可重入锁,一定会发生死锁。

共享锁和独享锁

共享锁:一个线程给共享资源加上共享锁后,其他线程还可以给这个共享资源加上其他的共享锁。比如常见的读锁。

独享锁:一个资源被加上独享锁后,就不能添加其他锁了。比如常见的写锁。

共享锁和独享锁在mysql层面也是通用概念。

小结

有了这些Java锁的概念,再去看代码就方便多了。接下来会好好研究下synchronizedReentrantLock