java 锁_Java之线程并发的各种锁、锁、锁

因为两周没更新了d79dc2a851818eb9805cb8b56aac5a2d.png...

也不是懒,这两周确实有些忙,赶项目进度赶的不亦乐乎facd76f673bf4accb32c258953c9af70.png...

终于赶在工期前,可以进入内测了,我也有了些时间,可以更新啦...

    线程并发锁是很常见的问题,而且在Java中锁的类型、概念、使用场景等等也是面试必问的,所以今天就来先简单的说一说线程并发中常用的一下锁。

0 1

  公平锁

    何为公平锁?就字面来理解,它就是一种有公平机制,并且不会因为你有任何的“背景”、“关系”就可以为所欲为的锁。在并发环境下,每个线程在获取锁时先会看该锁维护的等待队列,如果为空,或者当前线程时等待队列的第一个,就占有锁,否则就会加入到等待队列中,此后所有的线程都是以FIFO(先进先出)的规则来执行。

    在Java中提供了公平锁的实现类,先看个代码demo:

db1e2195424550967b405edfa14d80d6.png

打印值:

fa1d48f2349679fa89d0b35c7d9b50ea.png

可以看到线程获取到锁一定和启动保持一致的,另外ReentrantLock有参构造默认是true,也就是开启公平锁,

还有一点,代码中有虚线的问题,是因为我创建线程是显式的,alibaba检测编码规约是提示,必须使用线程池的方式创建线程,避免出现OOM。

2beebd98d5a4ea617ad90985081d42b9.png

优点

    那公平锁的有点也就一目了然了:所有的线程在CPU执行调度的时候,都能够得到资源,不至于有些线程因为抢占不到资源,而一直无法被执行。

缺点:

    有优点就必然有缺点:既然可以有序的执行,那肯定就会降低吞吐量,队列里面除了第一个线程,其它线程将会全部阻塞,CPU唤醒阻塞线程的开销会很大。

0 2

非公平锁

    何为非公平锁?同样字面意思理解,就是一种不公平机制的锁,随机抢占资源,不跟你整那一套先到先得的规则。线程加锁时先尝试获取锁,获取不到就自动到队尾等待。

上述同样的代码,把ReentrantLock的有参构造改成false,执行看下打印值:

ee4f24bca283e206f49c1f2539c5c833.png

非公平锁时随机抢占的机制,所以也会出现公平锁的情况,所以测试时候不用纠结这个。

优点

    非公平的性能肯定是高于公平锁性能的,非公平锁能更充分的利用CPU的时间片,尽量的减少CPU空闲的状态时间。

缺点:

    既然是随机抢占资源的,那就肯定会有队列中间的线程一直获取不到锁或长时间获取不到锁,导致“饿死”。

0 3

可重入锁

          何为可重入锁:可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,但前提是同一个对象或同一个类中,这样的锁就叫可重入锁。

    上面两种锁介绍的都是ReentrantLock实现的,而在真正的使用中保证线程安全synchronized也是不可缺少的,这两者都是可重入锁。

c85ccec26c16c79723f60044544ccb8e.png

86e97d2eb523a3b7e4d46c8f58ab0fc4.png

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

那这两者有什么区别呢?

0 4

ReentrantLock和synchronized区别

1,synchronized依赖于JVM,ReentrantLock依赖于API,需要lock()和unlock()方法配合try/finally语句块来实现。

2,synchronized使用比较简单方便,并且有编译器去保证锁的加锁和释放,而ReentrantLock需要手动声明加锁和释放锁,因此ReentrantLock锁的粒度和灵活度要高于synchronized。

3,synchronized在优化以前,性能方面和ReentrantLock是差很多的,但是在JDK1.6之后synchronized引入了偏向锁和自旋锁,两者的性能就差不多了,官方甚至都建议使用synchronized。其实synchronized的优化感觉就是采用ReentrantLock的CAS技术(这里不要有歧义:ReentrantLock底层是基于AQS实现的,但AQS的本质其实就是volatile+CAS)。都是试图在用户态就把加锁的问题解决,避免进入内核态的线程阻塞。

0 5

读写锁

    读写锁实际上就一种特殊的自旋锁,读写锁的规则可以共享读,但只能一个写,读读不互斥、读写互斥、写写互斥,而一般的独占锁是啥都互斥,而我们实际场景中,读一定是大于写的,一般情况下独占锁的效率低来源于高并发下对临界区的资源抢占导致线程上下文切换。因此当并发不是很高的情况下,读写锁需要额外的维护读锁的状态,可能还不如独占锁的效率高,所以要根据实际场景而定。(本篇不深入将方法的实现和源码)

2e78254b376a4ced76489f416aca57ec.png

0 6

synchronized JDK1.6优化之后的锁

6.1  偏向锁

        当线程执行到临界区时,会利用CAS操作,将线程ID插入到Mark Word(对象头中的标记子),同时修改偏向锁的标志位。

    所谓临界区,就是只允许一个线程进去执行操作的区域,即同步代码块,cas是一个原子性的操作。

    这个锁会偏向于第一个获得它的线程,在接下来的执行过程中,假如该锁没有被其它线程所获取,没有其它线程来竞争该锁,那么持有偏向锁的线程将永远不需要进行同步操作。也就是说,在此线程之后的执行过程中,如果再次进入或者退出同一段同步块代码,并不再需要去进行加锁或者解锁操作。

6.2 锁膨胀

    综合偏向锁,当出现有两个线程来竞争的话,那么偏向锁就失效了,此时锁就会膨胀,升级为轻量锁,这就是我们经常说的锁膨胀。

6.3 锁撤销

    当偏向锁失效,就得把该锁撤销,锁撤销的开销花费是很大的,大概的过程:

  1. 在一个安全点停止拥有锁的线程。

  2. 遍历线程栈,如果存在锁记录的话,需要修复锁记录和Markword,使其变成无锁状态。

  3. 唤醒当前线程,将当前锁升级成轻量级锁。所以,如果某些同步代码块大多数情况下都是有两个及以上的线程竞争的话,那么偏向锁就会是一种累赘,对于这种情况,我们可以一开始就把偏向锁这个默认功能给关闭

6.4 自旋锁(jdk1.4.2引入的)

    自旋锁属于轻量级锁。所谓自旋,就是指当有另外一个线程来竞争锁时,这个线程会在原地循环等待,而不是把该线程阻塞,直到那个获得锁线程释放锁之后,这个线程就可以马上获取到锁。

    注意:锁在原地循环的时候,是会消耗CPU的,就相当于在循环一个啥也没有的for循环。所以,轻量级锁适用于那些同步代码块执行的很快的场景,这样,线程原地等待很短很短的时间就能够获得锁了。

经验表明,大部分同步代码块执行的时间都是很短很短的,也正是基于这个原因,才有了轻量级锁这么个东西。

自旋锁存在的问题:

1,如果同步代码块执行的很慢,需要消耗大量的时间,这个时候,其它线程在原地等待空消耗的CPU,这点就会有问题。

2,本来一个线程把锁释放之后,当前线程是能够获得锁的,但是假如这个时候好几个线程都在竞争这个锁的话,那么可能当前线程会获取不到锁,还在原地”转圈圈“,转到怀疑人生。

那基于这个问题,我们就需要给线程空循环设置一个值,当线程超过了这个次数,我们就认为继续使用自旋锁就不合适了,一直原地空循环,这谁受得了。此时锁就会再次膨胀,升级为重量级锁。

默认情况下,自旋的次数为10次,当然可以通过-XX:PreBlockSpin来进行更改。

6.5 自适应自旋锁

    所谓自适应自旋锁就是线程空循环等待的自旋次数并非是固定的,而是会动态着根据实际情况来改变自旋等待的次数。
其大概原理是这样的:
    假如一个线程1刚刚成功获得一个锁,当它把锁释放了之后,线程2获得该锁,并且线程2在运行的过程中,此时线程1又想来获得该锁了,但线程2还没有释放该锁,所以线程1只能自旋等待,但是虚拟机认为,由于线程1刚刚获得过该锁,那么虚拟机觉得线程1这次自旋也是很有可能能够再次成功获得该锁的,所以会延长线程1自旋的次数。
另外,如果对于某一个锁,一个线程自旋之后,很少成功获得该锁,那么以后这个线程要获取该锁时,是有可能直接忽略掉自旋过程,直接升级为重量级锁的,以免空循环等待浪费资源。

轻量级锁也被称为非阻塞同步、乐观锁,因为这个过程并没有把线程阻塞挂起,而是让线程空循环等待,串行执行。

本篇着重讲的是一下名词的概念,之后会对于ReentrantLock的源码解析来真正的理解这些锁的底层实现原理,因为知其然先知其所以然啦。

- End -

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337491.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言编程中错误异常该如何统一处理?1.8万字总结

点击蓝字关注我们本文主要总结嵌入式系统C语言编程中,主要的错误处理方式。一、错误概念1.1 错误分类从严重性而言,程序错误可分为致命性和非致命性两类。对于致命性错误,无法执行恢复动作,最多只能在用户屏幕上打印出错消息或将其…

pycharm remote 远程项目 同步 本地_利器:PyCharm本地连接服务器搭建深度学习实验环境的三重境界...

作为实验室社畜,常需要在本地使用Pycharm写代码,然后将代码同步到服务器上,使用远程登录服务器并运行代码做实验。这其中有很多事情如果没有好的工具,做起来会非常麻烦。比如如何快速同步本地与服务器的代码?如何优雅地…

防御性编程技巧

点击蓝字关注我们在防御性编程的大框架之下,有许多常识性的规则。人们在想到防御性编程的时候,通常都会想到“断言”,这没有错。我们将在后面对此进行讨论。但是,还是有一些简单的编程习惯可以极大地提高代码的安全性。尽管看上去…

cookie无法读取bdstoken_第二章(第12节):cookie操作

有时候我们需要验证浏览器中 cookie 是否正确,因为基于真实 cookie 的测试是无法通过白盒和集成测试进行的。WebDriver 提供了操作 cookie 的相关方法,可以读取、添加和删除 cookie信息。WebDriver 操作 cookie 的方法:get_cookies()&#xf…

C++ 基本的输入输出

点击蓝字关注我们C 标准库虽然提供了一组丰富的输入/输出功能,但是本章只讨论 C 编程中最基本和最常见的 I/O 操作。C 的 I/O 发生在流中,流是字节序列。如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,…

http 和 https_HTTPS与HTTP区别

HTTPS与HTTP的认识:HTTP 加密 认证 完整性保护 HTTPSHTTP的全称是 Hypertext Transfer Protocol Vertion (超文本传输协议)HTTPS: HTTPS(Secure Hypertext Transfer Protocol)安全超文本传输协议。HTTPS和HTTP的区别:HTTPS协议需要到ca申…

C语言指针的自我介绍(你了解我吗?了解多少?)

点击蓝字关注我们hey! Ladies and Gentlemen.😁欢迎大家来看望我,对,我就是指针(pointer),被很多人吐槽😔,也被人说好。我希望大家了解过我以后,能够爱上我😘。大家在了解我之前 &am…

flex 解析json文件_使用 Python 处理 JSON 格式的数据 | Linux 中国

如果你不希望从头开始创造一种数据格式来存放数据,JSON 是一个很好的选择。如果你对 Python 有所了解,就更加事半功倍了。下面就来介绍一下如何使用 Python 处理 JSON 数据。-- Seth KenlonJSON 的全称是 JavaScript 对象表示法JavaScript Object Notati…

【C语言】指针进阶第一站:字符指针 typedef关键字!

点击蓝字关注我们简单回顾一下指针的概念内存会划分以字节为单位的空间,每一个字节都有一个编号(地址/指针)指针变量可以存放这个地址/指针注:我们日常所说的指针,一般是指针变量下面让我们坐上指针进阶的直通车&#…

python编译helloworld_python3学习笔记--001--python HelloWorld

python默认使用UTF-8编码 一个python3版本的HelloWorld代码如下: #!/usr/bin/env python print (Hello World!) 如果此python脚本文件名为:hello.py,则运行此脚本文件的方法有两种: 1、python hello.py [laolanglocalhost python]…

漫谈 C++:良好的编程习惯与编程要点

点击蓝字关注我们以良好的方式编写C class假设现在我们要实现一个复数类complex,在类的实现过程中探索良好的编程习惯。① Header(头文件)中的防卫式声明complex.h: # ifndef __COMPLEX__ # define __COMPLEX__ class complex {} # endif防止头文件的内容被多次包含…

【C语言】指针进阶第二站:指针数组!

点击蓝字关注我们指针数组数组是一种类型的数的集合整型数组的元素都是int类型指针数组的元素都是指针变量int* arr1[10];//整型指针的数组char*arr2[10];//一级字符指针的数组char** arr3[5];//二级字符指针的数组参考这一份示意图示例1:定义多个字符指针在上一站的字符指针里…

C语言初学者常见错误 | 总结22点

点击蓝字关注我们正文一.语言使用错误在打代码的过程中,经常需要在中文与英文中进行转换,因此常出现一些符号一不小心就用错,用成中文。例如:“;”中文中的分号占用了两个字节,而英文中“;”分号只占用一个…

nginx配置vue项目500_一个Nginx部署多个vue前端项目总结

摘要:近来接手了一个二次开发的前后端分离模式的项目,其中在前端项目的部署上需要让2个前端项目都部署到一个IP地址和端口下,那么我们这里就要用到Nginx了,接下来我们看看如何在一个Nginx下部署2个前端项目的编译打包2个前端项目执…

【C语言】指针进阶第三站,数组指针!

点击蓝字关注我们数组指针整型指针:指向整型的指针字符指针:指向字符的指针数组指针:指向数组的指针基本概念下面哪个是数组指针呢?指针数组和数组指针的概念很容易混淆,一定要分清楚哦!int *p1[10]; int (…

【C语言】指针进阶第四站:数组/指针的传参问题!

点击蓝字关注我们朋友们,到站啦!指针进阶第四站:传参问题0.引例自定义函数里形参的类型,要和函数调用中传过去的实参类型相对应test函数里的是int类型,我们传过去的参数a也是int类型void test(int n) {} int main() {i…

python做自动化控制postman_python自动化测试入门篇-postman

接口测试基础-postman 常用的接口有两种:webservice接口和http api接口。 Webservice接口是走soap协议通过http传输,请求报文和返回报文都是xml格式。 http api接口是走http协议,通过路径来区分调用的方法,请求报文都是key-value形…

Python3实现红黑树[上篇]

Python3实现红黑树[上篇]由于时间有限,这次只写了红黑树添加节点,关于节点的删除放在下一讲 https://blog.csdn.net/qq_18138105/article/details/105324025。 关于红黑树的介绍,来由,性质和定义,可以看这篇文章&…

web应用程序并发测试_测试并发应用

web应用程序并发测试本文是我们名为Java Concurrency Essentials的学院课程的一部分。 在本课程中,您将深入探讨并发的魔力。 将向您介绍并发和并发代码的基础知识,并学习诸如原子性,同步和线程安全性的概念。 在这里查看 ! 目录…

深入理解存储器层次结构

点击蓝字关注我们1概述对于一个简单的计算机系统模型,我们可以将存储器系统看做是一个线性的字节数组,而 CPU 能够在一个常数时间内访问每个存储器的位置。实际上,存储器系统(memory system)是一个具有不同容量、成本和…