什么是锁

在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他、并发控制策略。

锁通常需要硬件支持才能有效实施。这种支持通常采取一个或多个原子指令的形式,如"test-and-set", “fetch-and-add” or “compare-and-swap””。这些指令允许单个进程测试锁是否空闲,如果空闲,则通过单个原子操作获取锁。

公平锁

  • 定义:就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。
  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
  • 公平锁获取锁例子
 /**
* true 表示 ReentrantLock 的公平锁
*/
private ReentrantLock lock = new ReentrantLock(true); public void testFail(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() +"获得了锁");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
FairLockTest fairLock = new FairLockTest();
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName()+"启动");
fairLock.testFail();
};
Thread[] threadArray = new Thread[10];
for (int i=0; i<10; i++) {
threadArray[i] = new Thread(runnable);
}
for (int i=0; i<10; i++) {
threadArray[i].start();
}
}

运行结果

Thread-1启动
Thread-1获得了锁
Thread-3启动
Thread-3获得了锁
Thread-5启动
Thread-5获得了锁
Thread-2启动
Thread-2获得了锁
Thread-4启动
Thread-4获得了锁
Thread-6启动
Thread-6获得了锁
Thread-10启动
Thread-8启动
Thread-10获得了锁
Thread-9启动
Thread-7启动
Thread-8获得了锁
Thread-9获得了锁
Thread-7获得了锁

看到结果里面获得锁的顺序和线程启动顺序是一致的,这就是公平锁。

非公平锁

  • 定义:线程加锁时直接尝试获取锁,获取不到就自动到队尾等待。
  • 优点:非公平锁性能高于公平锁性能,非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。
  • 缺点:可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
  • 非公平锁列子:只需要将上面公平锁的代码改为new ReentrantLock(false);

    运行结果
Thread-1启动
Thread-0启动
Thread-2启动
Thread-3启动
Thread-4启动
Thread-8启动
Thread-7启动
Thread-6启动
Thread-1获得了锁
Thread-0获得了锁
Thread-5启动
Thread-5获得了锁
Thread-2获得了锁
Thread-3获得了锁
Thread-4获得了锁
Thread-8获得了锁
Thread-7获得了锁
Thread-6获得了锁
Thread-9启动
Thread-9获得了锁

线程启动顺序是1、0、2、3、 4、 8 、7 、6 、5 、9,获得锁的顺序却是1 、0 、5 、2 、3 、4 、8 、7 、6 、9,这就是非公平锁,它不保证先排队尝试去获取锁的线程一定能先拿到锁。

重入锁

  • 定义: - 可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁

    下面是一个synchronized重入锁的列子:
public class ReentrantLockTest {

    public static void main(String[] args){
for (int i = 0; i < 10; i++) {
new Thread(() -> A()).start();
}
}
public static synchronized void A(){
System.out.println(Thread.currentThread().getName());
B();
}
public static synchronized void B(){
System.out.println(Thread.currentThread().getName());
}
}

输出:

Thread-1
Thread-1
Thread-0
Thread-0

A方法和B方法同时输出了线程名称,表明即使递归使用synchronized也没有发生死锁,证明其是可重入的。

读写锁

百度百科定义的读写锁是:

读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。

  • 与传统锁不同的是读写锁的规则是可以共享读,但只能一个写,总结起来为:读读不互斥,读写互斥,写写互斥,而一般的独占锁是:读读互斥,读写互斥,写写互斥,而场景中往往读远远大于写,读写锁就是为了这种优化而创建出来的一种机制。注意是读远远大于写,一般情况下独占锁的效率低来源于高并发下对临界区的激烈竞争导致线程上下文切换。因此当并发不是很高的情况下,读写锁由于需要额外维护读锁的状态,可能还不如独占锁的效率高。因此需要根据实际情况选择使用。
  • Java里面ReentrantReadWriteLock读写锁特性

    公平选择性: 支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。

    重进入: 读锁和写锁都支持线程重进入。

    锁降级: 锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。

乐观锁、悲观锁

  • 乐观锁:乐观锁总是认为不存在并发问题,每次去取数据的时候,总认为不会有其他线程对数据进行修改,因此不会上锁。但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用“数据版本机制”或“CAS操作”来实现。
  • 悲观锁: 悲观锁认为对于同一个数据的并发操作,一定会发生修改的,哪怕没有修改,也会认为修改。因此对于同一份数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁并发操作一定会出问题。典型的数据库的查询 for update
    • 在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。
    • 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。
    • 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。期间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

分段锁

分段锁其实是一种锁的设计,并不是具体的一种锁,对jdk1.7 及以前的ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

自旋锁

在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

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

这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

  • 偏向锁:是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
  • 轻量级锁:是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
  • 重量级锁:是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让他申请的线程进入阻塞,性能降低。

独享锁、共享锁

  • 独享锁是指该锁一次只能被一个线程所持有。
  • 共享锁是指该锁可被多个线程所持有。

    对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。 读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。对于Synchronized而言,当然是独享锁。

参考文章

https://blog.csdn.net/qiuwenjie123/article/details/79950532

https://segmentfault.com/q/1010000009659039

https://blog.csdn.net/qq_43519310/article/details/100107346

https://blog.csdn.net/u010648018/article/details/79750608

https://www.cnblogs.com/hustzzl/p/9343797.html

http://ifeve.com/locks/

http://ifeve.com/locks/

史上最全java里面的锁的更多相关文章

  1. 史上最全 Java 中各种锁的介绍

    更多精彩原创内容请关注:JavaInterview,欢迎 star,支持鼓励以下作者,万分感谢. 锁的分类介绍 乐观锁与悲观锁 锁的一种宏观分类是乐观锁与悲观锁.乐观锁与悲观锁并不是特定的指哪个锁(J ...

  2. 史上最全Java面试题(带全部答案)

    今天要谈的主题是关于求职,求职是在每个技术人员的生涯中都要经历多次.对于我们大部分人而言,在进入自己心仪的公司之前少不了准备工作,有一份全面细致面试题将帮助我们减少许多麻烦.在跳槽季来临之前,特地做这 ...

  3. 史上最全Java面试题整理(附参考答案)

    下列面试题都是在网上收集的,本人抱着学习的态度找了下参考答案,有不足的地方还请指正,更多精彩内容可以关注我的微信公众号:Java团长 1.面向对象的特征有哪些方面? 抽象:将同类对象的共同特征提取出来 ...

  4. 史上最全Java面试题全集

    2013年年底的时候,我看到了网上流传的一个叫做<Java面试题大全>的东西,认真的阅读了以后发现里面的很多题目是重复且没有价值的题目,还有不少的参考答案也是错误的,于是我花了半个月时间对 ...

  5. 史上最全java面试题

    基本概念 操作系统中 heap 和 stack 的区别 什么是基于注解的切面实现 什么是 对象/关系 映射集成模块 什么是 Java 的反射机制 什么是 ACID BS与CS的联系与区别 Cookie ...

  6. 史上最全Java多线程面试题及答案

    多线程有什么用? 线程和进程的区别是什么? Java实现线程有哪几种方式? 启动线程方法start()和run()有什么区别? 怎么终止一个线程?如何优雅地终止线程? 一个线程的生命周期有哪几种状态? ...

  7. 史上最全Java表单验证封装类

    package com.tongrong.utils; import java.util.Collection; import java.util.Map; import java.util.rege ...

  8. 史上最全java pdf精品书籍整理

    算法,多线程,spring,数据库,大数据,面试题等等.喜欢的小伙伴加群获取 QQ群号825199617 (非广告培训技术群,纯java知识交流,请自重)

  9. 史上最全Java集合中List,Set以及Map等集合体系详解

    一.概述 List , Set, Map都是接口,前两个继承至collection接口,Map为独立接口 Set下有HashSet,LinkedHashSet,TreeSet List下有ArrayL ...

随机推荐

  1. 第一次UML编程作业

    博客班级 https://edu.cnblogs.com/campus/fzzcxy/2018SE2/ 作业要求 https://edu.cnblogs.com/campus/fzzcxy/2018S ...

  2. 口述完SpringMVC执行流程,面试官就让同事回家等消息了

    Srping MVC 执行流程真的是老生常谈的话题了,最近同事小刚出去面试,前面面试官相继问了几个 Spring 相关的问题,但当面试官问他,你知道 Srping MVC 的执行流程吗?小刚娴熟的巴拉 ...

  3. Spark存储Parquet数据到Hive,对map、array、struct字段类型的处理

    利用Spark往Hive中存储parquet数据,针对一些复杂数据类型如map.array.struct的处理遇到的问题? 为了更好的说明导致问题的原因.现象以及解决方案,首先看下述示例: -- 创建 ...

  4. .Net Core JWT 动态设置接口与权限

    通过上一篇.Net Core官方的 JWT 授权验证学习到了JWT的授权.可以发现一个问题,就是如果每个接口可以使用的角色都是写死的,这样如果有所修改会非常麻烦,虽然用policy可以一定程度上缓解, ...

  5. PyQt(Python+Qt)学习随笔:QMainWindow的takeCentralWidget对QDockWidget作用案例图解

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 QMainWindow的takeCentralWidget方法作用是将 ...

  6. Python中排序方法sort、函数sorted的key参数的作用分析

    从Python2.4开始,list.sort方法 和 sorted方法 都增加了一个 'key' 参数用来在进行比较之前指定每个列表元素上要调用的函数,将函数的返回值作为比较的依据. 那么怎么使用这个 ...

  7. PyQt(Python+Qt)学习随笔:Qt Designer中Action关联menu菜单和toolBar的方法

    1.Action关联菜单 通过菜单创建的Action,已经与菜单自动关联,如果是单独创建的Action,需要与菜单挂接时,直接将Action Editor中定义好的Action对象拖拽到菜单栏上即可以 ...

  8. deepFM(原理和pytorch理解)

    参考(推荐):https://blog.csdn.net/w55100/article/details/90295932 要点: 其中的计算优化值得注意 K代表隐向量维数 n可以代表离散值one-ho ...

  9. 彻底搞懂js this指向问题

    在这里必须要提一句的是,this指向是学习js必须要掌握的(必须),再开始之前先看底部的总结,然后回上面看例子便一目了然. 例子1: function a(){ var user = "Ta ...

  10. P4317 花神的数论题,关于luogu题解粉兔做法的理解

    link 题意 设 \(\text{sum}(i)\) 表示 \(i\) 的二进制表示中 \(1\) 的个数.给出一个正整数 \(N\) ,求 \(\prod_{i=1}^{N}\text{sum}( ...