性能调优——线程安全和锁优化

线程安全的概念

通过搜索引擎搜索出来的结果是:“一个变量可以安全的被多个线程同时使用,就是线程安全的”。这样的描述并不准确。有一个更恰当的定义

“当多线程访问同一个对象,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是安全的

Java中的线程安全

线程安全的前提是:存在多个线程之间对共享数据进行访问。如果不与其他线程共享数据,那他是串行执行或者多线程执行是没有区别的。

Java共享数据类型:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立

不可变对象一定是线程安全的。final修饰来声明不可变对象

java.lang.String是一个不可变对象,通过对字符串进行任何操作,构造出来的也是一个新字符串,而不会影响原来的值

不可变对象:java.lang.Number部分子类(Long和Double、BigDecimal和BigInteger)、枚举类型

线程安全的实现方法

互斥同步:最常见的一种并发正确性保障手段

“同步指在多个线程并发访问共享数据情况下,保障共享数据只能在同一时刻只被一条线程使用。互斥是实现同步的一种手段。互斥实现方式:临界区、互斥量和信号量

最基本的互斥同步是synchronized关键字。产生monitorenter和monitorexit两个字节码,需要一个reference类型参数锁定和解锁对象。如果没有指定类型参数,默认就是当前实例作为同步对象

synchronized执行原理:根据虚拟机规范,在执行monitorenter指令时,首先要去尝试获取对象的锁,如果对象并未锁定,或者当前线程已经拥有该对象的锁,把锁的计数器+1.相应的,monitorexit指令时将锁计数器减1,当计数器为0时,锁被释放。如果获取锁对象失败,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放

tips:synchronized同步块对同一条线程时可重入的,不会出现把自己锁死的情况。
同步块在已进入线程执行完之前,会阻塞后面其他线程的进入。
所以被synchronized修饰的方法getter/setter方法,消耗时间比较长,所以在Java中称为重量级锁。
除了重量级锁,可以引用java.util.concurrent中的轻量级锁ReentrantLock实现同步。都可以实现线程重入。但是lock()和unlock()需要配合try{}finally{}模块完成,还新增几个功能:等待可中断、实现公平锁以及锁绑定多个条件

等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情
公平锁:多个线程申请同一个锁时,必须按照申请锁的时间顺序来获取锁;而非公平锁不能保证,在锁被释放时,任何一个等待锁的线程都有机会获取锁。synchronized和ReentrantLock是非公平锁
锁绑定多个条件:一个ReentrantLock对象可以绑定多个Condition对象,多次调用newCondition()方法。而在synchronized中需要额外多加锁才能实现

非阻塞同步

非同步方案

可重入代码:可在代码的任何时刻中断,转而去执行另外一段代码。

如何判断代码是否具有可重入性:一个方法,返回结果是可预测的,只要输入相同的数据,返回相同的结果。具备可重入性线程就是安全的

线程本地存储

锁优化

常见的锁优化技术:适应式自旋、锁消除、锁粗化、轻量级锁和偏向锁等

自旋锁
JDK1.6默认开启自旋锁。或者通过-XX:+UseSpinning开启。自旋次数默认10次 可以通过 -XX: PreBlockSpin参数更改

锁消除
指在JIT运行时候,对一些代码要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除

锁消除主要依据:来源于逃逸分析的数据支持

锁粗化

轻量级锁

重量级锁