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…

【洛谷P2680】运输计划

题目链接 题目大意&#xff1a; 一棵\(n\)个点的带边权的数&#xff0c;给定\(m\)条树上两点间的路径&#xff0c;现在你可以让树上任意一条边的权值变为零&#xff0c; 问如何选边使得\(m\)条路径中边权和最大的路径的边权和最小 \(\mathcal{solution}\) 这是\(NOIP2015\)的\(…

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下软…

webservice系统学习笔记9-使用契约优先的方式的一个服务端demo(隐式传Header信息)...

服务器端&#xff1a; 1、编写wsdl文件 <?xml version"1.0" encoding"UTF-8" standalone"no"?> <wsdl:definitions xmlns:soap"http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns"http://www.example.org/mywsdl/&qu…

java gzip 多个文件_Java Zip多文件压缩和 GZIP压缩

/*** 多文件压缩** author Administrator**/public class ZipCompress {public static void main(String args[]) {String[] filepaths { "D:\\zip1.txt", "D:\\zip2.txt" };try {FileOutputStream f new FileOutputStream("D://test.zip");//…

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

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

java 内存和实际内存_请问更改eclipse内存和更改jvm内存是一会事儿吗?

RSS列 表示&#xff0c; 程序占用了多少物理内存。 虚拟内存可以不用考虑&#xff0c;它并不占用实际物理内存。 (2). top 命令也可以 其中VIRT(或VSS)列 表示&#xff0c;程序占用了多少虚拟内存。 同 ps aux 中的 VSZ列 RES列 表示&#xff0c; 程序占用了多少物理内存。同 p…

thinkphp 编辑器kindeditor

首先&#xff0c;去官网下载最新版的kindeditor&#xff0c;然后把里面asp&#xff0c;jsp&#xff0c;net&#xff0c;example的全删除&#xff0c;然后改名为editor放进public&#xff08;最外层目录的public&#xff09;文件夹里面 在目录lib目录建立ORG文件夹&#xff08;个…

equation

equation 题目描述 有一棵n 个点的以 1 为根的树, 以及 n 个整数变量xi。树上 i 的父亲是 fi&#xff0c; 每条边(i,fi)有一个权值wi&#xff0c;表示一个方程 xi xfi wi&#xff0c;这 n-1个方程构成了一个方程组。 现在给出q 个操作&#xff0c;有两种类型: 1 u v s&#x…

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

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

舞伴配对问题java_舞伴配对问题

循环队列的应用——舞伴配对问题&#xff1a;在舞会上&#xff0c;男、女各自排成一队。舞会开始时&#xff0c;依次从男队和女队的队头各出一人配成舞伴。如果两队初始人数不等&#xff0c;则较长的那一队中未配对者等待下一轮舞曲。假设初始男、女人数及性别已经固定&#xf…

逆元

复习逆元…… 逆元 求法&#xff1a; 1&#xff0c;快速幂     根据费马小定理有\(a^{p - 1} \equiv 1 \quad (mod \quad p)\),把左边拆开一下得到     \[a \cdot a^{p - 2} \equiv 1 \quad (mod \quad p)\]     因此\(a^{p - 2}\)为\(a\)在\(mod \quad p\)意义下的…

java如何让线程等待_如何使Java线程等待另一个线程的输出?

我真的建议你经历一个教程&#xff0c;如Sun’s Java Concurrency&#xff0c;你开始在多线程的魔法世界。还有一些好书出来了(google for“Concurrent Programming in Java”&#xff0c;“Java Concurrency in Practice”)。要得到你的答案&#xff1a;在你必须等待dbThread的…

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 | ---------------------…

java某个类避免findbug检查_Findbugs能否在java中检测到捕获RuntimeException?

你能不能让我知道Findbugs可以在java中检测到catcing RuntimeException吗&#xff1f;有效的java建议我们不要捕获RuntimeException.所以我想知道Findbugs可以抓错了.另外,我已经检查过Klocwork JD.CATCH和checkstyle IllegalCatch是否适用于此目的.最佳答案 有点.在findbugs中…

交叉编译链的安装

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

Scrapy将爬取的段落整合为字符串

使用Scrapy框架爬取文章的时候&#xff0c;经常会遇到要爬取多个段落的问题&#xff0c;如果这个时候使用的是&#xff1a; text response.xpath("......").extract() 那么会发现爬取下来的文章是以段落为单位的list&#xff0c;不方便直接展示。 这个时候可以将lis…

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 安装…