jvm(13)-线程安全与锁优化
【0】README
0.1)本文部分文字转自“深入理解jvm”, 旨在学习 线程安全与锁优化 的基础知识;
0.2)本文知识对于理解 java并发编程非常有用,个人觉得,所以我总结的很详细;
【1】概述
【2】线程安全
1)线程安全定义:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的;(干货——线程安全定义)


2)绝对线程安全
2.1)在java API中标注自己是线程安全的类,大多数都不是绝对的线程安全
2.2)java.util.Vector 是一个线程安全的容器,因为它的add()方法,get()方法,size() 方法 这些方法都是被 synchronized修饰的,尽管效率低下,但确实是安全的;对Vector的测试如下:
// 对线程安全的容器 Vector的测试
public class VectorTest {
private static Vector<Integer> vector = new Vector<>(); public static void main(String[] args) {
while(true) {
for (int i = 0; i < 100; i++) {
vector.add(i);
} Thread removeThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
}
}); Thread printThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
System.out.println(vector.get(i));
}
}
}); removeThread.start();
printThread.start(); // 不要同时产生过多的线程,否则会导致os 假死
while(Thread.activeCount() > 20);
}
}
}

对以上代码的分析(Analysis):
A1)运行结果: 作者说会抛出异常(但我的运行结果却没有抛出异常),按理说应该是会抛出异常的;
A2)抛出异常的原因:因为如果另一个线程恰好在错误的时间里删除了一个元素,导致序号i 已经不再可用的话,再用i 访问数组就会抛出一个 ArrayIndexOutOfBoundsException。
A3)如果要保证这段代码能够正确执行下去,修改后的代码为:
// 对线程安全的容器 Vector的测试(修改后的代码)
public class ModifiedVectorTest {
private static Vector<Integer> vector = new Vector<>(); public static void main(String[] args) {
while(true) {
for (int i = 0; i < 100; i++) {
vector.add(i);
} Thread removeThread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (vector) { // 添加同步块,this line
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
}
}
}); Thread printThread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (vector) { // 添加同步块,this line
for (int i = 0; i < vector.size(); i++) {
System.out.println(vector.get(i));
}
}
}
}); removeThread.start();
printThread.start(); // 不要同时产生过多的线程,否则会导致os 假死
while(Thread.activeCount() > 20);
}
}
}
3.1)上述 VectorTest.java 和 ModifiedVectorTest.java 就是相对线程安全的案例;
4.1)线程兼容定义:线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用;

case1)等待可中断:指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情,可中断特性对处理执行时间非常长的同步块很有帮助;1.6.3)关于synchronized 和 ReentrantLock 性能的分析:

对上图的分析(Analysis):
A1)多线程环境下 synchronized的吞吐量下降得非常严重,而 ReentrantLock 则能基本保持在同一个比较稳定的水平上;与其说ReentrantLock性能好,还不如说 synchronized还有非常大的优化余地;
A2)虚拟机在未来的性能改进中肯定也会更加偏向于原生的 synchronized,所以还是提倡在 synchronized能实现需求的情况下,优先考虑使用 synchronized 来进行同步;(干货——同步方式推荐使用synchronized)
2.4)如何使用CAS 操作来避免阻塞同步,看个荔枝:(测试incrementAndGet 方法的原子性)
// Atomic 变量自增运算测试(incrementAndGet 方法的原子性)
public class AtomicTest {
public static AtomicInteger race = new AtomicInteger(0); public static void increase() {
// 输出正确结果,一切都要归功于 incrementAndGet 方法的原子性
race.incrementAndGet();
} public static final int THREADS_COUNT = 20; public static void main(String[] args) throws Exception {
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
increase();
}
}
});
threads[i].start();
} while(Thread.activeCount() > 1) {
Thread.yield();
} System.out.println(race);
} /**
* incrementAndGet() 方法的JDK 源码
* Atomically increment by one the current value.
* @return the updated value
*/
public final int incrementAndGet() {
for(;;) {
int current = get();
int next = current + 1;
if(compareAndSet(current,next)) {
return next;
}
}
}
}
2.5)CAS操作(比较并交换操作)的ABA问题:如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就说它的值没有被其他线程改变过了吗? 如果在这段期间它的值曾经被改为了B,之后又改回了A,那CAS操作就会误认为它从来没有被改变过,这个漏洞称为 CAS操作的 ABA问题;
2.6)解决方法:J.U.C 包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的version 来保证CAS的正确性。不过目前来说这个类比较鸡肋, 大部分cases 下 ABA问题 不会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效;(干货——CAS操作(比较并交换操作)的ABA问题及其解决方法)

public class LockEliminateTest {
// raw code
public String concatString(String s1, String s2, String s3) {
return s1 + s2 + s3;
}
// javac 转化后的字符串连接操作
public String concatString(String s1, String s2, String s3) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}
}
对以上代码的分析(Analysis):
A1)对于 javac 转化后的字符串连接操作代码: 使用了同步,因为StringBuffer.append() 方法中都有一个同步块,锁就是sb对象。虚拟机观察变量sb,很快就会发现他的动态作用域被限制在 concatString() 方法内部;也就是所 sb 的所有引用都不会逃逸到方法之外;
A2)所以,虽然这里有锁,但是可以被安全地消除掉,在即时编译之后,这段代码就会忽略掉所有的同步而直接执行了;
// javac 转化后的字符串连接操作
public String concatString(String s1, String s2, String s3) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}


jvm(13)-线程安全与锁优化的更多相关文章
- jvm(13)-线程安全与锁优化(转)
0.1)本文部分文字转自“深入理解jvm”, 旨在学习 线程安全与锁优化 的基础知识: 0.2)本文知识对于理解 java并发编程非常有用,个人觉得,所以我总结的很详细: [1]概述 [2]线程安全 ...
- 深入理解JVM(7)——线程安全和锁优化
Java中的线程安全 按照线程安全的“安全程度”由强至弱来排序,可以将Java语中各种操作共享的数据分为以下5类:不可变. 绝对线程安全. 相对线程安全. 线程兼容和线程对立. 1.不可变 不变的对象 ...
- JVM(8) 线程安全与锁优化
面向过程编程:程序编写以算法为核心,程序员会把数据和过程分别作为独立的部分来考虑,数据代表问题空间的客体,程序代码则用于处理这些数据.这种思维方式直接站在计算机的角度去抽象问题和解决问题,称为面向过程 ...
- 深入理解JVM - 线程安全与锁优化 - 第十三章
线程安全 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对 ...
- 《深入理解Java虚拟机》-----第13章 线程安全与锁优化
概述 在软件业发展的初期,程序编写都是以算法为核心的,程序员会把数据和过程分别作为独立的部分来考虑,数据代表问题空间中的客体,程序代码则用于处理这些数据,这种思维方式直接站在计算机的角度去抽象问题和解 ...
- 深入理解java虚拟机-第13章-线程安全与锁优化
第十三章 线程安全与锁优化 线程安全 java语言中的线程安全 1 不可变.Immutable 的对象一定是线程安全的 2 绝对线程安全 一个类要达到不管运行时环境如何,调用者都不需要额外的同步措施, ...
- JVM之java并发 ——线程安全与锁优化
概述 人们很难想象现实中的对象在一项工作进行期间,会被不停地中断和切换,对象的属性(数据)可能会在中断期间被修改和变“脏”,而这些事情在计算机世界中则是很正常的事情.有时候,良好的设计原则不得不向现实 ...
- 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化
<深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...
- JVM-并发-线程安全与锁优化
线程安全与锁优化 1.线程安全 (1)当多个线程访问一个对象时,如果不考虑这些线程在执行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获 ...
随机推荐
- Docker解析及轻量级PaaS平台演练(一)--Docker简介与安装
Container技术: 传统的虚拟化技术: 通过对硬件层模拟,从而实现了能够在一套硬件上面运行多个操作系统,因为通过硬件虚拟化,使得操作系统认为在它之下就是硬件层 但是实际情况是这样的:虚拟机中的O ...
- hadoop2.2.0集群安装和配置
hadoop2.0已经发布了稳定版本了,增加了很多特性,比如HDFS HA.YARN等. 注意:apache提供的hadoop-2.2.0的安装包是在32位操作系统编译的,因为hadoop依赖一些C+ ...
- FTP服务器需要开几个端口
原文: https://blog.csdn.net/houbin0912/article/details/72578688 -------------------------------------- ...
- BZOJ 4174 tty的求助 莫比乌斯反演
题目大意:求∑Nn=1∑Mm=1∑m−1k=0⌊nk+xm⌋ mod 998244353 如果n和m都已经确定了.如今要求这坨玩应: ∑m−1k=0⌊nk+xm⌋ =∑m−1k=0(⌊nk%m+xm⌋ ...
- spring常用的一些注解以及注解注入总结
常用的spring注解有如下几种: @Controller@Service@Autowired@RequestMapping@RequestParam@ModelAttribute@Cacheable ...
- float过后 高度无法自适应的解决方法
float过后 高度无法自适应的解决方法: 在float层最外面包一层div即可.
- php中的 file_get_contents(‘php://input’)用法
php中的 file_get_contents('php://input')用法: file_get_contents 获取php页面中input内容的值: eg: php: 页面提交了usernam ...
- MATLAB读取黑白图像显示却是黑色,24位深转8位深黑白图像解决方法
1.24位深转8位深: ps将24位深原图.png保存为GIF图256即为8位,再将8位gif图转为需要的.png,即转为8位深png图. 2.MATLAB读取黑白图像显示几乎全为黑色: 这是最近处理 ...
- CStdioFile类学习笔记<转>
本文转自:http://www.cnblogs.com/JiMuStudio/archive/2011/07/17/2108496.html CStdioFile类的声明保存再afx.h头文件中. ...
- 【MyBatis学习09】高级映射之一对多查询
上一篇博文总结了一下一对一的映射,本文主要总结一下一对多的映射,从上一篇文章中的映射关系图中可知,订单项和订单明细是一对多的关系,所以本文主要来查询订单表,然后关联订单明细表,这样就有一对多的问题出来 ...