java 并发多线程 锁的分类概念介绍 多线程下篇(二)

时间:2021-06-28 00:34:34
接下来对锁的概念再次进行深入的介绍
之前反复的提到锁,通常的理解就是,锁---互斥---同步---阻塞
其实这是常用的独占锁(排它锁)的概念,也是一种简单粗暴的解决方案
抗战电影中,经常出现为了阻止日本人炸桥?炸路?的场景,这只是阻止日本人的一种手段,如果大喊一声TMD滚蛋,日本人就走了,还炸桥干嘛?
用锁是为了线程安全,而不是为了上锁,上锁是一种途径,独占锁则是“上锁”的其中一种形式
如果有更优雅的上锁方式,自然不必要每次都简单粗暴的使用独占锁,不是嘛
 
从几个维度可以大致分为下面几种
分类是以锁为核心,而延展出来的优雅的使用方式
java 并发多线程 锁的分类概念介绍 多线程下篇(二)

乐观锁与悲观锁

  • 乐观就像小米的宣传语-永远相信,美好的事情即将发生;
  • 而悲观就像透过有色眼睛看世界,永远都带有颜色;
对应到程序中
使用锁是为了什么?因为线程之间会共享数据,共享数据就一定会出现问题吗?当然是概率的
  • 两个人去同一个水井打水,如果每次都错开,那么美好一直存在
  • 如果天天赶到同一个时间点,我相信早晚必有一战.....
所以如何看待在多线程共享数据中,出现的概率性竞争问题?
  • 乐观的眼光就是坚信绝大多数时候是没问题的,只需要最后发生修改或者操作时进行校验,比如校验是否被修改过,然后再去进一步处理
  • 悲观的眼光就是坚信肯定会有问题,所以我就一直加锁,只要一直锁住反正肯定不会出现问题
所以你看,乐观锁其实可以并没有锁,是在逻辑上实现了锁的业务
假设这样一种比较极端的场景
A,B两个线程,共享数据c
A线程每分钟都会对c进行操作(一天24*60次),B每天都会对c进行一次操作
试想一下乐观锁和悲观锁之间的性能差异?
在这个场景中可以认为即使没有任何防范措施,每天正确的概率也会大于99.9%(不考虑一次错误后导致的后续连锁错误),如果选用悲观锁,将会有多少无谓的损耗?
synchronized就是悲观锁,他会为你保障百分百的加锁与同步

公平锁与非公平锁

公平与非公平的字面意思大家都很清晰,没有人理解起来有难度
比如语文老师对你和同桌的态度一视同仁、比如领导对你和同事的年终你觉得不公平....
但是,对于锁来说,这个公平与不公平针对的是什么?
之前在监视器概念中提到的,线程如果在某个监视器的等待集合中,那么如果当前线程执行结束后,谁应该被选作下一个进入监视器的线程呢?
这就是锁的公平性针对的点
  • 对于锁的请求,每个线程总是有一个先来后到的先后顺序,如果按照先后顺序,那么就是公平锁
  • 如果不按照先后顺序,随机的或者按照什么算法优先级等选择,那么就是非公平锁
除非有额外的公平性要求,否则不应该使用公平锁,因为对性能是有损耗的

独占锁和共享锁

  • 如果一个锁仅仅只能被一个进程拥有,那么他就是独占的;
  • 如果一个锁可以同时被多个线程拥有,那么他就是共享的;
独占锁会保障任何时候都只是有一个线程进行访问,ReentrantLock就是独占锁,synchronized的原理也是独占锁
而读写锁ReadWriteLock就是共享锁,可以同时允许多个读线程进行操作

可重入锁

重入,就好像是你结账后餐馆老板对你说的下次再来!
当一个线程想要获取一个被其他线程独占的锁时,你需要等待,但是如果是自己已经获得的锁呢?
答案是你可以多次获取,这就是可重入
比如一个类中有两个同步的实例方法,而锁对象都是当前对象this
如果不可重入会发生什么?
调用了A方法之后,想要调用方法B但是锁却被自己占有了,如果不可重入,就成了自己等自己,岂不是*?
可重入锁在内部维护了一个计数器,用于记录重入次数
自己获得一次,那么计数器+1,释放一次计数器-1,如果计数器为0,说明该线程释放了该锁,否则,锁仍旧被该线程持有

自旋锁

在之前线程简介中有提到,Java线程是内核级映射的线程(1.2吧?之后)
如果一个线程请求获取一个锁时,并不能获取到,那么将会进入阻塞状态,也就是会被切换到内核态然后挂起
当该线程获取到锁时,又需要切换到内核状态进行唤醒,说白了需要用户状态与内核状态的切换
而且,这个状态的切换,还是比较消耗性能的
怎么办?
有一种解决办法就是线程继续运行,过一会儿再次尝试锁的获取
怎么做到的?
其实就是相当于CPU空跑,而不是直接将线程进行挂起
所以说相当于牺牲了CPU的时间片,换取内核状态的开销,这就涉及到一个平衡点的问题
如果线程之间竞争不激烈,可能下次的尝试获取就成功了
但是如果线程之间竞争非常激烈,下次还是抢不到,下下次还是抢不到......会发生什么情况?
那就是浪费了太多的CPU时间片,而且,你也不能永远的像“死循环”一样尝试呀,所以一般会有一个时长或者次数,总之有个上限
现在对自旋锁应该有了一定的了解了,自旋就是自己在那边不停地打转,不给糖吃就打滚的哭
书面点的说法:
当前线程获取锁时,如果发现锁已经被其他线程占有,并不会马上阻塞自己,在不放弃CPU的情况下,多次尝试
刚才也说明了,对于线程是否竞争激烈,自旋锁有着不同的反应,也说不定会导致CPU白白浪费了时间片,所以要根据业务来

适应性自旋

刚才说到,对于自旋不能无止境的,那就是类似“死循环”,所以都有限制,通常用次数,比如规定次数为5
但是实际情况中,可能有时经常1次就能够成功,也可能经常5次了还没有成功,如果1次就成功的还好,如果说限制为5次,每次都5次后还是失败,这就是纯粹的白忙活
所以后来出现了自适应的自旋锁,自旋的次数(限制)不再是固定的了
  • 如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源
  • 如果对于某个锁,自旋等待刚刚成功获得过锁,那么将会认为这次也很可能是成功的,所以将会允许自旋,甚至允许更多次数(时间)的自旋等待
对于适应性自旋,大致逻辑是这样,具体细节由具体算法决定
再次强调
对于自旋,表面上看是减少了阻塞的发生,进而可以减少状态切换的性能损耗,但是这是以浪费CPU为代价的
所以并不能认为自旋锁是治疗百病的良药
实际使用中,不能只看到带来的好处,也需要关注付出的代价,而对于适应性自旋,只是对于自旋的“死板”的一种调优而已

小结

以上分类只是就某一个维度对锁的概念以及应用的分析
他们概念上不是完全隔离的,不是说就存在那么几种锁,A,B,C....A就是A,B就是B
比如人类都是人,但是分为男人、女人,还可以分为好人、坏人等,好人有男女,坏人也有男女
比如独占锁属于悲观锁,独占就是要保障同一时间只有一个线程操作,其他线程必须等待
共享锁就属于乐观锁,因为他放宽了加锁的条件
理解锁的分类有助于后续关于其他高级工具、类的理解与学习

java 并发多线程 锁的分类概念介绍 多线程下篇(二)的更多相关文章

  1. Java并发编程(三)概念介绍

    在构建稳健的并发程序时,必须正确使用线程和锁.但是这终归只是一些机制.要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问. 对 ...

  2. Java并发 - (无锁)篇6

    , 摘录自葛一鸣与郭超的 [Java高并发程序设计]. 本文主要介绍了死锁的概念与一些相关的基础类, 摘录自葛一鸣与郭超的 [Java高并发程序设计]. 无锁是一种乐观的策略, 它假设对资源的访问是没 ...

  3. Java并发编程锁系列之ReentrantLock对象总结

    Java并发编程锁系列之ReentrantLock对象总结 在Java并发编程中,根据不同维度来区分锁的话,锁可以分为十五种.ReentranckLock就是其中的多个分类. 本文主要内容:重入锁理解 ...

  4. 从源码学习Java并发的锁是怎么维护内部线程队列的

    从源码学习Java并发的锁是怎么维护内部线程队列的 在上一篇文章中,凯哥对同步组件基础框架- AbstractQueuedSynchronizer(AQS)做了大概的介绍.我们知道AQS能够通过内置的 ...

  5. Java并发编程锁之独占公平锁与非公平锁比较

    Java并发编程锁之独占公平锁与非公平锁比较 公平锁和非公平锁理解: 在上一篇文章中,我们知道了非公平锁.其实Java中还存在着公平锁呢.公平二字怎么理解呢?和我们现实理解是一样的.大家去排队本着先来 ...

  6. java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock

    原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...

  7. Java基础加强之并发(一)基本概念介绍

    基本概念介绍 进程:它是内存中的一段独立的空间,可以负责当前应用程序的运行.当前这个进程负责调度当前程序中的所有运行细节. 线程:它是位于进程中,负责当前进程中的某个具备独立运行资格的空间. 进程是负 ...

  8. java并发里的一些基础概念

    转载自:https://my.oschina.net/hosee/blog/597934: 摘要: 本系列基于炼数成金课程,为了更好的学习,做了系列的记录. 本文主要介绍 1.高并发的概念,为以后系列 ...

  9. java 并发(六) --- 锁

          阅读前阅读以下参考资料,文章图片或代码部分来自与参考资料 概览 一张图了解一下java锁. 注 : 阻塞将会切换线程,切换内核态和用户态,是比较大的性能开销 各种锁 为什么要设置锁的等级 ...

随机推荐

  1. POJ 2823 Sliding Window 再探单调队列

    重新刷这个经典题,感觉跟以前不一样了,变得更加容易理解了,不讲解了,看代码.注意:要用C++提交,用G++会超时.. 代码: #include <iostream> #include &l ...

  2. NetBeans快捷键的使用

    .Ctrl-Tab:在打开的文件之间切换: .Ctrl-N:在当前打开的项目里新建文件: .Ctrl-F:当前文件查找匹配的字符(支持正则): .Ctrl-H:当前文件查找.替换匹配的字符(支持正则, ...

  3. DAG模型

    数字三角形: 1.递归计算 int solve(int i,int j) { :max(solve(i+,j),solve(i+,j+))); } 2.记忆化搜索,不用指明计算顺序,并且保证每个状态只 ...

  4. 一些常用css技巧的为什么(二)我所理解的line-height

    要用到的基本术语和概念: 替换元素:用作为其他内容占位符的一个元素,或说替换元素内容的部分并非由文档内容直接表示.比如img元素它由文档本身之外的一个图像来替换,比如input元素要由一个单选按钮,复 ...

  5. PHP5生成图形验证码&lpar;有汉字&rpar;

    利用PHP5中GD库生成图形验证码 类似于下面这样 1.利用GD库函数生成图片,并在图片上写指定字符 imagecreatetruecolor   新建一个真彩色图像      imagecolora ...

  6. wpa&lowbar;supplicant&lowbar;8&lowbar;ti hostapd wpa&lowbar;supplicant TI 官方的wpa&lowbar;supplicant hostapd 移植到linux

    在移植 wpa_supplicant_8_ti 的时候碰到很多头文件找不到.然后参考了下面的博客 http://blog.csdn.net/penglijiang/article/details/85 ...

  7. java关键字transient与volatile小结

    本文转自:http://heaven-arch.iteye.com/blog/1160693 transient和volatile两个关键字一个用于对象序列化,一个用于线程同步,都是Java中比较高阶 ...

  8. IE9浏览器打开开发者工具代码正常执行,反之报错

    1.can i use console  IE9开发者工具打开时支持console对象,否则报错. 2.由于出现错误 80020101 而导致此项操作无法完成 测试代码 <!DOCTYPE ht ...

  9. 【原创】python模拟腾讯网页登录

    近日,研究Tencent网页模拟登录的过程,过程有些忐忑,但最终还是实现了这一功能.先将结果写于此,供大家参考: 其加密过程在c_login_old.js文件中执行,将JS关键代码提取出来如下: fu ...

  10. ios开发周期之--(向上,向下,四舍五入)取整

    ceil(x)返回不小于x的最小整数值(然后转换为double型). floor(x)返回不大于x的最大整数值. round(x)返回x的四舍五入整数值.