java线程池 锁_java多线程——锁

这是多线程系列第四篇,其他请关注以下:

如果你看过前面几篇关于线程的文字,会对线程的实现原理了然于胸,有了理论的支持会对实践有更好的指导,那么本篇会偏重于线程的实践,对线程的几种应用做个简要的介绍。

本篇主要内容:

线程安全的分类

线程同步的实现方式

锁优化

线程安全分类

线程安全并非是一个非真既假的二元世界,如果按照线程安全的“安全程度”来排序的话,java中可以分为以下几类

80f7025ce03669f7a0a52836d9b4b7e7.png

不可变。对数据类型修饰为final类型的,就可以保证其实不可变的(reference 对象除外,final对象属性不保证,只保证内存地址)。不可变的对象一定是线程安全,比如String类,它是一个典型的不可变对象,当调用它的subString()、replace()和concat()的方法都不会影响它原来的值,只会返回一个新构造的字符串对象。

绝对线程安全。这个定义是极其严格的,一个类要达到,不管运行时环境如何,调用者都不需要任何额外的同步措施。

相对线程安全。这个就是我们通常所讲的线程安全,它保证对对象的单独操作是线程安全的,不需要做额外保证工作,但对于特定顺序的连续调用,可能需要调用端采用额外的同步手段来保证调用的正确性。

线程兼容。这是指对象本身并非线程安全,可以通过调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用。我们常用的非线程安全类,都属于这个范畴。

线程对立。这个是指无论调用端是否采用同步措施,都无法在多线程环境中并发使用代码。这种我们应该避免。

上述中可能对绝对安全和相对安全,并不是很好区分,我们采用一个示例来区分:

public class VectorTest {

private Vector vector = new Vector();

public void remove() {

new Thread() {

@Override

public void run() {

for (int i = 0; i < vector.size(); i++) {

vector.remove(i);

}

}

}.start();

}

public void print() {

new Thread() {

@Override

public void run() {

for (int i = 0; i < vector.size(); i++) {

System.out.println(vector.get(i));

}

}

}.start();

}

public void add(int data) {

vector.add(data);

}

public static void main(String[] args) {

VectorTest test = new VectorTest();

for (int j=0;j<100;j++){

for (int i = 0; i < 10; i++) {

test.add(i);

}

test.remove();

test.print();

}

}

}

上述代码中运行会报错:ArrayIndexOutOfBoundsException的异常,这个异常是在print方法里面出现的,当remove线程删除 一个元素之后,print方法正好执行到vector.get()方法,此时就会出现这个异常。

我们知道vector是线程安全的,它的get()、remove()、size()、add()方法都采用synchronize 进行同步了,但是多线程的情况下,如果不对方法调用端做额外同步的情况下,仍然不是线程安全的。这就是我们说的相对线程安全,它能不保证何时,调用者都不需要任何额外的同步措施。

线程安全同步的实现方式

互斥同步是常见的一种并发正确性保障手段。同步是指多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个线程使用。java中常见的互斥同步手段就是synchronize和ReentrantLock。

想必这两种加锁方式对多线程有了解的人都知道。具体用法我们不再探讨。我们聊一聊这两者的不同和具体场景应用。

synchronize在前面的文字中我们也讲过了,它属于重度锁,由于jvm线程是映射于操作系统原生线程,在阻塞或者唤醒线程时候,需要从用户态转换到内核上,这个耗费有时候会耗费时间超过代码执行时间,所以jvm会对一些代码执行短的同步代码采用自旋锁等方式,避免频繁的切入到核心态之中。

synchronize是jvm提供的一种内置锁,被jvm推荐使用,它写出的代码相对比较简单紧凑,只有当内置锁满足不了需求的时候,再来用ReentrantLock。

ReentrantLock

那么ReentrantLock能提供哪些高级功能?我们看个示例;

public void synA() {

synchronized (lockA) {

synchronized (lockB) {

//doSomeThing....

}

}

}

public void synB() {

synchronized (lockB) {

synchronized (lockA) {

//doSomeThing....

}

}

}

上述通过synchronized的代码,在多个线程分别调用synA和synB的时候容易发生死锁的问题。要想避免,只能在编写的时候强制要求所有的调用顺序一致。而在ReentrantLock可以采用轮询锁的方式来避免此种问题。

public void tryLockA() {

long stopTime = System.currentTimeMillis() + 10000l

while (true) {

if (lockA.tryLock()) {

try {

if (lockB.tryLock()) {

try {

// doSomeThing.....

return;

} finally {

lockB.unlock();

}

}

} finally {

lockA.unlock();

}

}

if (System.currentTimeMillis() > stopTime) {

return;

}

}

}

上述trylock的方式,如果不能获取到所需要的锁,那么可以采用轮询的方式来获取,从而让程序从新获取控制权,而且会释放已经获得的锁。另外trylock还提供有定时重载方法,方便你在一定时间内获得锁,如果指定时间内不能给出结果,会零程序结束。

ReentrantLock除了提供可轮询,定时锁以外,还可以提供可中断的锁获取操作,以便获取可取消的操作中使用枷锁。另外还提供了锁获取操作,公平队列以及非块结构的锁。这些功能都极大的丰富了对锁操作的可定制性。

当然了,你如果ReentrantLock的这些高级功能你并用不上,还是推荐采用synchronized。在性能上synchronized在java6以后已经可以和ReentrantLock相平衡了,而且据官方据说,这一方面的性能未来还会加强,因为它属于jvm的内置属性,能执行一些优化,例如对线程封闭锁对象的锁消除优化,以及增加锁的颗粒度来消除锁同步等。这些在ReentrantLock是很难得到实现的。

锁优化

上述我们也了解过,多线程对资源竞争的时候会令其他没有竞争到的线性进行阻塞等待,而阻塞以及唤醒又要需要内核的调度,这对有限的cpu来说代价过于庞大,于是jvm就在锁优化上花费了大量的精力,以提高执行效率。

我们看看常见的锁优化方式。

c7bbbd4298bce2ef4e583a90c46d02a2.png

自旋锁

在共享数据锁定的状态下,有很多方法都是只会持有很短的一段时间,为了这么一小段时间而让线程挂起和恢复很不值得。那么jvm就让等待锁的线程稍等一下,但不放弃相应的执行时间。以此看等待的线程是否很快释放,如此就减少了线程调度的压力。如果锁被占用时间很短,这个效果就很好,如果时间过长,就白白浪费了循环的资源,而且会带来资源浪费。

自适应自旋锁

自旋锁无法依据锁被占用时间长短来处理,后续就引入了,自适应的自旋锁,自选的时间不再固定了,而是由前一次在同一个锁的自选时间以及拥有的状态来决定的。如此就会变的智能起来。

锁消除

锁消除是指jvm即时编译器在运行时候,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除。锁消除检测主要依据是来源于逃逸分析的数据支持,如果判断在一段代码中,堆上的所有数据都不会逃逸出去,就把他们当做栈上数据对待,认为是线程私有的,同步也就无需进行了。

锁粗化

编写代码的时候,总是推荐将同步块作用范围越小越好,如果一系列的操作都是对一个对象反复枷锁和解锁,甚至出现在循环体中,及时没有线程竞争,也会导致不必要的性能损耗。那么对于此种代码,jvm会扩大其锁的颗粒度,对这一部分代码只采用一个同步操作来进行。

轻量级锁

轻量级锁是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥产生的性能消耗。jvm中对象header分为两部分信息,第一部分用于存储对象自身的运行数据,称为”Mark Word”,它是实现轻量级锁的关键。另外一部分用于存储执行方法区对象类型数据的指针。当代码进入同步块的时候,如果此同步对象没有被锁定,则把“Mark world”中指向锁记录的指针,标记为“01”。

如果Mark Word更新成功,线程拥有了该对象的锁,则把执行锁标位的指针标记为”00”,如果更新失败,并且当前对象的Mark word 没有指向当前线程的栈帧,则说明锁对象已经被其他线程抢占了。如果有两条以上 线程争用同一个锁,那轻量级锁就不再奏效了,锁标记为”10“,膨胀为重量级锁。

轻量级锁是基于,绝大部分的锁定,在同步周期内是不存在竞争的,所以以此来减轻互斥产生的性能消耗。当然如果存在锁竞争,除了互斥还会避免使用互斥量的开销,还有额外产生同步修改标记位的操作。

偏向锁

偏向锁是在无竞争情况下把整个同步都消除了,连CAS更新操作也不做了,它会偏向第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步,当另外一个线程尝试获取这个锁的时候,则宣告偏向模式结束。

-----------------------------------------------------------------------------

想看更多有趣原创的技术文章,扫描关注公众号。

关注个人成长和游戏研发,推动国内游戏社区的成长与进步。

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

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

相关文章

Ubuntu时间显示不准确的解决方案

参考&#xff1a;解决ubuntu里面时间不正确的办法 作者&#xff1a;三速何时sub20 发布时间&#xff1a;2020-12-08 16:24:27 网址&#xff1a;https://blog.csdn.net/weixin_44234294/article/details/110875899?spm1001.2014.3001.5501 目录1、进入终端2、输入命令3、选择 A…

Ubuntu下软件的安装、卸载方法

参考&#xff1a;Ubuntu 如何使用命令卸载安装过的软件&#xff08;超级简单&#xff09; 作者&#xff1a;一只青木呀 发布时间&#xff1a;2020-08-04 09:19:01 网址&#xff1a;https://blog.csdn.net/weixin_45309916/article/details/107778981 参考&#xff1a;Ubuntu下软…

Ubuntu文件压缩、解压缩、打包解包(带软链接)、拷贝文件(带软链接)、拷贝文件夹

参考&#xff1a;Ubuntu 命令解压文件大全 作者&#xff1a;一只青木呀 发布时间&#xff1a; 2020-08-04 17:18:55 网址&#xff1a;https://blog.csdn.net/weixin_45309916/article/details/107791294 参考&#xff1a;打包和压缩的概念和区别 作者&#xff1a;不浪漫的罪名L…

Ubuntu开启FTP服务方法(Ubuntu和Windows之间互传文件需要开启——服务器端)

目录Ubuntu开启FTP服务步骤&#xff1a;Ubuntu开启FTP服务步骤&#xff1a; 工作中Ubuntu和Windows之间互传文件&#xff0c;需要服务器端&#xff08;Ubuntu&#xff09;开启FTP服务&#xff0c;客户端&#xff08;Windows&#xff09;安装FileZilla。平时自己学习电脑安装虚拟…

win7共享wifi

为什么80%的码农都做不了架构师&#xff1f;>>> 1.从开始菜单找到“命令提示符”&#xff0c;或直接键入cmd快速搜索&#xff0c;右键单击它&#xff0c;选择“以管理员身份运行” 2.运行以下命令启用虚拟无线网卡&#xff1a; netsh wlan set hostednetwork mod…

Ubuntu开启NFS、SSH服务(驱动开发用到、电脑端登录ARM板用到)

参考&#xff1a;Ubuntu下NFS服务的开启 作者&#xff1a;一只青木呀 发布时间&#xff1a;2020-08-04 14:06:58 网址&#xff1a;https://blog.csdn.net/weixin_45309916/article/details/107784877 目录NFS服务的开启1.安装NFS服务2.创建 linux 工作目录3.配置NFSUbuntu下SSH…

【整理】MySQL 之 autocommit

2019独角兽企业重金招聘Python工程师标准>>> mysql 默认是开启 auto commit 的。可以通过如下命令查看 session 级别和 global 级别的设置&#xff1a; mysql> select session.autocommit; ---------------------- | session.autocommit | ---------------------…

交叉编译链的安装

参考&#xff1a;嵌入式 交叉编译链的安装 作者&#xff1a;一只青木呀 发布时间&#xff1a;2020-08-04 18:13:13 网址&#xff1a;https://blog.csdn.net/weixin_45309916/article/details/107789879 目录什么是交叉编译器交叉编译器的下载交叉编译器的安装1.把下载的文件放到…

Ubuntu下安装VS Code以及C/C++插件(PS工作目录的创建)

参考&#xff1a;Visual Studio Code Ubuntu下安装 以及C/C插件大全 作者&#xff1a;一只青木呀 发布时间&#xff1a;2020-08-05 11:55:53 网址&#xff1a;https://blog.csdn.net/weixin_45309916/article/details/107811506 目录为何选择安装VS CodeVisual Studio Code 安装…

Common Lisp中调用R

2019独角兽企业重金招聘Python工程师标准>>> R是功能强大的统计软件&#xff0c;和Lisp一样也有一个交互式的命令行环境&#xff0c;还有众多的扩展库&#xff0c;可以用来进行专业的统计分析。要在Common Lisp中方便的调用R的功能&#xff0c;可以试用rcl这个库。安…

java生成pdf加密_java使用iText 生成PDF全攻略(表格,加密)

java使用iText 生成PDF全攻略,包括创建文档,设置字体,添加表格(PdfPTable),创建新页(newPage),设置布局,加密主要使用的jar包: itextpdf-5.4.2.jar,itext-pdfa-5.4.2.jar,itext-xtra-5.4.2.jar,如果用到中文&#xff0c;需要CJK字体的扩展包&#xff1a;itext-asian.jar如果用到…

恩智浦NXP I.MX6ULL芯片介绍下载官网资料

参考&#xff1a;NXP I.MX6ULL芯片介绍以及资料的获取 作者&#xff1a;一只青木呀 发布时间&#xff1a;2020-09-26 10:54:26 网址&#xff1a;https://blog.csdn.net/weixin_45309916/article/details/108808573 目录I.MX6ULL芯片介绍以及官网资料的获取I.MX6ULL芯片介绍以及…

判定点是否在不规则多边形内部的问题

2019独角兽企业重金招聘Python工程师标准>>> 问题如下&#xff1a; 话说在平面内有一个任意的不规则的封闭多边形&#xff0c;另外在这个平面内还有一个点&#xff0c;问题&#xff1a;如何高效的判定这个点是在这个多边形内部还是外部&#xff1f;补充&#xff1a…

Cortex-A7 MPCore 架构详细介绍(九种运行模式、内核寄存器组R0~R15,有特定的名字和功能)

目录0.ARM架构的历史简介1.Cortex-A7 MPCore(即多核) 简介2.Cortex-A 处理器九种运行模式3.Cortex-A 寄存器组&#xff08;内核寄存器&#xff09;3.1通用寄存器3.1.1未备份寄存器(R0~R7)3.1.2备份寄存器(R8~R12、SP指针R13、备份R14也叫LR)3.1.3程序计数器R15(PC)3.2程序状态寄…

Java开发中遇到具有挑战的事_Java并发编程的挑战:遇到的问题及如何解决

并发编程的目的是为了让程序运行得更快&#xff0c;但是&#xff0c;并不是启动更多的线程就能让程序最大限度地并发执行。在进行并发编程时&#xff0c;如果希望通过多线程执行任务让程序运行得更快&#xff0c;会面临非常多的挑战&#xff0c;比如上下文切换的问题、死锁的问…

树莓派(TCP客户端 )和Wemos(TCP服务端连接红外模块)通讯实现对红外设备的控制

参考&#xff1a;U如何用树莓派连接语音模块&#xff0c;红外模块来控制红外设备详解 作者&#xff1a;一只青木呀 发布时间&#xff1a;2020-08-12 17:14:10 网址&#xff1a;https://blog.csdn.net/weixin_45309916/article/details/107960066 目录硬件软件红外解码步骤1.连接…

java反编译微信小程序_教你如何一键反编译获取任何微信小程序源代码(图形化界面,傻瓜式操作)...

一键获取微信小程序源代码1 Tips&#xff1a;2   一键获取微信小程序源码, 使用了C#加nodejs制作 直接解压在D盘根目录下后就可以使用 将小程序文件放到 wxapkg目录下3 这个目录下有一些demo 可以先进行实验 使用正确 wxapkg exe这些文件应该在 D:CrackMinApp目录下4 然后打开…

PM2管理工具的使用

linux上PM2可以管理服务程序&#xff0c;防止程序无故关闭&#xff0c;具有程序守护功能&#xff0c;自动重启服务器程序&#xff0c;监控程序等好处&#xff0c;很方便&#xff0c;具体自己去体会! 官网地址&#xff1a; http://pm2.keymetrics.io/ 文档指南&#xff1a; ht…

C语言常用字符串操作函数大全详解(strstr,strtok,strrchr,strcat,strcmp,strcpy,strerror,strspn,strchr等)

参考&#xff1a;string.h中常用字符串操作函数说明&#xff08;strstr&#xff0c;strtok&#xff0c;strrchr&#xff0c;strcat&#xff0c;strcmp&#xff0c;strcpy&#xff0c;strerror&#xff0c;strspn&#xff0c;strchr等&#xff09; 作者&#xff1a;一只青木呀 发…

位运算(按位与、按位或、异或、取反)以及原码、反码、补码

参考&#xff1a;运算符的计算&#xff08;按位与 按位或 异或 取反&#xff09; 作者&#xff1a;一只青木呀 发布时间&#xff1a; 2020-07-23 18:13:55 网址&#xff1a;https://blog.csdn.net/weixin_45309916/article/details/107543919 参考&#xff1a;计算机原码&#…