Java线程安全与锁优化
线程安全的严谨定义:
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交题执行,也不需要进行额外的同步,或者调用方法进行其他任何操作,调用这个对象的行为都可以或者正确的结果,那么这个对象是线程安全的!
java共享数据分类(5类)
1)不可变
2)绝对线程安全:不管运行环境如何,调用者都不需要任何额外的同步措施,java api中标注自己是线程安全的类,都不是绝对线程安全的
3)相对线程安全:就是我们通常意义上讲的线程安全,需要保证对这个对象的单独操作是线程安全的,我们在调用的时候不需要做额外的保障措施,但对于一些特定顺序的连续调用就需要调用端使用额外的保障措施,比如vector的线程安全的容器,其add,get,size方法都被synchronized修饰,但是当另外一个线程恰好在错误的时间删除一个元素,导致该元素已经不再可用的话,就会产生异常,余姚对删除元素操作锁定一下
4)线程兼容:指对象本身不是线程安全的,但是可以通过在调用端正确的使用同步手段保证对象在多线程环境下是线程安全的,我们平常说的一个类不是线程安全的,通常指的就是这种情况
5)线程对立:无论是否采取同步措施,都无法并发执行,比如两个不同的线程同时持有一个线程对象,一个尝试去中断线程,一个尝试去恢复线程,这种情况无论是否采取同步措施,都无法并发执行,还存在死锁的风险
线程安全的实现方法:
1.互斥同步(悲观锁,最大的问题就是线程阻塞和唤醒带来的性能问题)
1.1最基本的互斥同步就是synchroized关键字(可重入锁,非公平锁)(重量级锁,线程阻塞唤醒开销大)
一点优化:在通知系统阻塞线程前加入一段自旋等待过程,避免频繁切换到核心态
1.2 ReentrantLock(也是重入锁,默认下非公平锁),需要lock,unlock方法配合try/finally完成操作,相比于synchronized,ReentrantLock增加了3个高级功能:可中断,可实现公平锁,以及锁可以绑定多个条件
注意:多线程环境下,synchronized的吞吐量下降得厉害,而ReentrantLock则能基本保证在一个稳定水平,是因为synchronized还要很多优化的余地!!
2.非阻塞同步(乐观锁,基于冲突检测的乐观并发策略)
通俗的说,就是先进行操作,如果没有其他线程竞争共享数据,那操作就成功了,如果共享数据有争用,产生了冲突,就是采取其他措施(最常见的就是不断的重试,直到成功,CAS机制),这种措施不需要挂起线程
从硬件方面保证操作和冲突检测具备原子性(unsafe类)
重量级锁的锁优化技术(Synchronized):
1.自旋锁:某个线程占用了共享数据,本线程先不挂起,而是处于自旋等待状态,不断重试
自旋的次数有限制,不然一直自旋会消耗系统资源
自适应自旋:如果某个锁,自旋很少成功获得,那么以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源
2.锁消除:指JVM运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除
3.锁粗化:如果一系列的连续操作都对同一个对象反复加锁解锁,甚至是加锁操作出现在循环体中,那即使没有竞争,频繁的进行互斥同步操作也会导致不必要的性能消耗,这个时候将锁的范围扩展,变成一个锁,就是锁的粗化
4.轻量级锁:与Mark Word和CAS机制有关
轻量级锁能提供性能的依据:对于绝大部分,在整个同步周期内是不存在竞争的,这是一个经验数据
Mark Word的组成:
轻量级锁的加锁过程:
1)在代码进入同步块的时候,如果同步对象锁为无锁状态(锁标志位为01状态),虚拟机首先在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储旧的Mark Work的拷贝(锁记录解锁的时候用到)
2)虚拟机使用CAS机制尝试将对象的Mrak Word更新为轻量级锁的标志位和指向锁记录的指针
3)如果更新操作成功,那么线程就拥有了该对象的锁
4)如果这个更新操作失败,虚拟机首先会检查当前线程是否已经有了这个对象的锁,如果已经有了,就进入同步代码块继续执行,如果没有就说明该对象的锁被其他线程占用了,一旦这样,轻量级锁就膨胀成为重量级锁(比如synchronized),Mark Work中存储的就指向重量级锁的指针,后面等待锁的线程也会进入等待状态
轻量级锁的解锁过程:
1).通过CAS机制尝试将当前线程栈帧中的锁记录替换当前的Mark Word
2).如果替换成功,那么整个同步过程就完成了
3).如果替换失败,则说明有其他线程尝试获取过该锁,但失败了,导致轻量级锁变成了重量级锁,那么要在释放锁的同时,唤醒被挂起的线程
总结:轻量级锁就是在无竞争的情况下使用CAS操作区消除同步使用的互斥量
5.偏向锁:在无竞争的情况下,把整个同步过程都消除掉,连CAS操作都不做
偏向锁的依据:锁总是同一个线程持有,很少发生竞争
偏向锁偏向于第一个获得它的线程,如果在接下来的指向过程中,该锁没有被其他线程获取,则持有偏向锁的线程永远不需要进行同步
做法:只需要在锁第一个被拥有的时候,记录下偏向线程ID,这样偏向线程就一直持有着锁,直到竞争发生才释放锁,以后每次同步,检查锁的偏向线程ID是否与当前线程ID一致,如果一致直接进入同步,退出同步也无需每次加锁解锁都去CAS更新Mark Word,如果不一致则意味着发生了竞争,锁已经不总是偏向于一个线程了,这时候锁膨胀为重量级锁才能保证线程公平竞争锁
分析:引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行,因为轻量级锁的释放和获取依赖多次的CAS操作,而偏向锁只需要在置换线程ID的时候依赖一次CAS(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS消耗),轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时提高性能!
偏向锁加锁过程:
偏向锁加锁发生在偏向线程第一次进入同步块的时候,CAS操作尝试更新对象的Mrak Word(锁标志位为1,记录偏向线程的ID)
撤销偏向锁等待过程:
当有另外一共线程来竞争锁时,就需要将偏向锁膨胀为重量级,竞争线程尝试CAS更新Mark Work失败,会等到安全局点(此时不会执行任何代码)撤销偏向锁
Java线程安全与锁优化的更多相关文章
- Java线程安全与锁优化,锁消除,锁粗化,锁升级
线程安全的定义 来自<Java高并发实战>"当多个线程访问一个对象的时候,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法的时候进行任何 ...
- Java虚拟机--线程安全和锁优化
Java虚拟机--线程安全和锁优化 线程安全 线程安全:当多线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象 ...
- 深入理解java虚拟机-第13章-线程安全与锁优化
第十三章 线程安全与锁优化 线程安全 java语言中的线程安全 1 不可变.Immutable 的对象一定是线程安全的 2 绝对线程安全 一个类要达到不管运行时环境如何,调用者都不需要额外的同步措施, ...
- 《深入理解Java虚拟机》-----第13章 线程安全与锁优化
概述 在软件业发展的初期,程序编写都是以算法为核心的,程序员会把数据和过程分别作为独立的部分来考虑,数据代表问题空间中的客体,程序代码则用于处理这些数据,这种思维方式直接站在计算机的角度去抽象问题和解 ...
- JVM之java并发 ——线程安全与锁优化
概述 人们很难想象现实中的对象在一项工作进行期间,会被不停地中断和切换,对象的属性(数据)可能会在中断期间被修改和变“脏”,而这些事情在计算机世界中则是很正常的事情.有时候,良好的设计原则不得不向现实 ...
- 深入理解Java虚拟机(第三版)-14. 线程安全与锁优化
14. 线程安全与锁优化 1. 什么是线程安全? 当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替进行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个 ...
- 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化
<深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...
- JVM-并发-线程安全与锁优化
线程安全与锁优化 1.线程安全 (1)当多个线程访问一个对象时,如果不考虑这些线程在执行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获 ...
- jvm(13)-线程安全与锁优化(转)
0.1)本文部分文字转自“深入理解jvm”, 旨在学习 线程安全与锁优化 的基础知识: 0.2)本文知识对于理解 java并发编程非常有用,个人觉得,所以我总结的很详细: [1]概述 [2]线程安全 ...
随机推荐
- C# 利用SharpPcap实现网络包捕获嗅探
本文是利用SharpPcap实现网络包的捕获的小例子,实现了端口监控,数据包捕获等功能,主要用于学习分享. 什么是SharpPcap? SharpPcap 是一个.NET 环境下的网络包捕获框架,基于 ...
- 章节四、1-if条件语句
package introduction5; public class ConditionalStatement { public static void main(String[] args) { ...
- 使用C#+PowerShell进行Windows系统间文件传输
新的winserver2016支持了一种nano模式,像以前的core模式,只能远程管理,只支持x64,只有610M,不让CentOS mini版独美. 这个nano版,默认只开启WinRM,所以只能 ...
- 学习用Node.js和Elasticsearch构建搜索引擎(6):实际项目中常用命令使用记录
1.检测集群是否健康. curl -XGET 'localhost:9200/_cat/health?v' #后面加一个v表示让输出内容表格显示表头 绿色表示一切正常,黄色表示所有的数据可用但是部分副 ...
- kali linux源大全
输入leafpad /etc/apt/sources.list进入 #官方源 deb http://http.kali.org/kali kali main non-free contr ...
- [20190214]11g Query Result Cache RC Latches补充.txt
[20190214]11g Query Result Cache RC Latches补充.txt --//上午测试链接:http://blog.itpub.net/267265/viewspace- ...
- Oracle EBS FORM lov
存在一种情况: 一个LOV的值当前有效,因此填入保存.但突然无效后,当查询该界面时就会弹出LOV框使其修改. 解决方案: 1. 非常粗暴,不设置校验,在LOV对应的item强行将校验设置为NO. 2. ...
- 回顾:Linux环境 Mysql新建用户和数据库并授权
回顾:Linux环境 Mysql新建用户和数据库并授权 一.新建用户 //登录Mysql @>mysql -u root -p @>密码 //创建用户 mysql> insert i ...
- Django基础笔记
1.准备工作 .虚拟环境设置 python3 pip install virtualenv python -m venv env(虚拟环境文件名) env\Scripts\activate pip i ...
- Mybatis 学习---${ }与#{ }获取输入参数的区别、Foreach的用法
一.Mybatis中用#{}和${}获取输入参数的区别 1.“#{}“和“${}”都可以从接口输入中的map对象或者pojo对象中获取输入的参数值.例如 <mapper namespace=&q ...