• Java锁分类原来是这个样子


    学了几天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

  • 相关阅读:
    使用部分函数时并未include其所在头文件,但是能编译成功且能运行,为什么?
    ubuntu开发机所需工具,做个记录,不断补充
    c cpp编程用到的系统边角与其拾遗
    几个递进的make file
    可以用命令行控制eclipse断点增加删除、远程调试创建与启动的插件
    Mac 从睡眠恢复后没有声音的问题
    [netty4][netty-handler]netty之idle handler处理
    《将博客搬至CSDN》
    python2与python3中__metacalss__的不同用法
    python3 type和object的关系
  • 原文地址:https://www.cnblogs.com/pjjlt/p/13924076.html
Copyright © 2020-2023  润新知