线程安全
在多线程操作的过程中,无需为控制这个对象的线程安全做更多的操作。但有时候会退一步讲,在单个操作中。
Java线程安全
按照安全程度强到弱划分为五种
- 不可变
一次赋值不会在被改变,对于基本数据类型int,double等本身不会再改变,对于对象,需要保证对象的行为不会影响其状态,最简单的方法就是对象中的值成员也是final修饰的。
例如,integer
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;
/**
* Constructs a newly allocated {@code Integer} object that
* represents the specified {@code int} value.
*
* @param value the value to be represented by the
* {@code Integer} object.
*/
public Integer(int value) {
this.value = value;
}
java里String,java.lang.Number, Long, Double,BigInteger,BigDecimal.
反例,Number的子类型,Atom类
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
Integer不能自增,自增操作是新建一个对象并赋值,如果此时Integer是final修饰便不能完成。但是AtomInterger是可以的。
- 绝对线程安全
就是最开始我们定义的非常严格的线程安全,但是连我们一直说的线程安全的Vector都做不到,比如一个线程删除,一个线程取用,就会有越界异常,除非在处理的时候给整个vector加锁,那样的话效率十分低。。这就违背了绝对线程安全的本意。
- 相对线程安全
即我们通常说的线程安全,在单个操作中保证安全。vector,hashtable,和synchronizedCollection里都是。
- 线程兼容
在多线程通过一定手段保证线程安全,ArrayList,HashMap
- 线程对立
很少见,一般是控制线程的或者系统级的
线程安全的实现方法
- 互斥(阻塞)同步
系统层面:临界区(critical section),信号量(semaphore),互斥量(mutex)
互斥是因,同步时果,互斥是方法,同步是目的
悲观锁,synchronized关键字,可重入锁ReentrantLock,会有上下文的
类中的synchronized方法
这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)
ReentrantLock三种功能:
- 等待可中断,较长时间wait可放弃
- 公平锁,FIFO可通过参数设置
- 锁的多种条件,newConditoin()方法
性能对比:
在1.6之前RL多线程吞吐量好于Syn,1.6后进行了优化,差不多了,可以说性能不是选择两者的关键,一般可以用syn实现。
- 非阻塞同步
阻塞同步会挂起线程,造成切换开销。
随着硬件指令集的发展,基于冲突检测的乐观并发策略,先进行操作,如果没有竞争,成功;有冲突就采取补偿措施(如不断询问),许多措施都不需要挂起线程,这就是非阻塞同步。
CAS:compare and swap
/**
* 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;
}
}
但是要注意会带来ABA的问题,就在一通操作之后变回原值。可以通过传统的互斥锁解决。
无同步方案
- 可重入代码,不依赖堆,自然不存在同步问题
- 线程本地存储,web中一个请求对应一个线程,java中ThreadLocal
- https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
锁优化
为了提高锁的效率做的优化
自旋锁和自适应锁
请求锁的进程自旋PreBlockSpin来进行,超过这个次数,也要挂起。自适应锁,就是会根据之前这个锁的获得情况来智能决定自旋时间或者干脆直接挂起。
锁消除
JVM观察到变量不会被其他线程访问,进行锁消除
锁粗化
本来锁粒度细了性能会好一些,但是如果锁在循环体里的化,还不如直接把锁加在外面,便是锁粗化
轻量级锁
原理是不存在竞争时使用非阻塞的CAS,如果存在竞争还要再syn。所以在一定存在竞争的情况下反而慢。
偏向锁
偏向于第一个获得他的进程,连CAS都省了,但随着并发的增加会产生和轻量级锁一样的问题。