- 什么是线程安全?
当一个类在多线程环境下被使用时,仍能表现出正确的行为;
线程安全问题
竞争条件
// 示例一:
public class ThreadUnsafe {
private static int globalI = 0;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->inc()).start();
}
System.out.println(globalI);
}
private static void inc(){
globalI++;
//其中 globalI++;等价于:
//int localVaribale = globalI;
// localVaribale += 1;
// globalI = localVaribale ;
}
}
打印结果:99
// 示例二,单例模式
// 虽然问题不大,但在多线程环境中,依旧需要付出多次的创建成本;
public class SingletonObject {
//创建成本很高,无法直接new出来
private static SingletonObject INSTANCE;
public SingletonObject getInstance() {
if (null == INSTANCE) {
INSTANCE = new SingletonObject();
} //check-then-act
return INSTANCE;
}
private SingletonObject() {
//... expensive operations
}
}
//示例二,鬼畜的解决方法
//由JVM来保证,整个JVM中只存在一份即严格的单例模式;而且能保证代码整洁;
public enum SingletonObject {
INSTANCE;
}
// 示例三
private Map<String, Object> values = new ConcurrentHashMap<>();
// 即使是ConcurrentHashMap,也是不安全的
// 原因unsafePut()不是原子操作,ConcurrentHashMap只能保证每一步操作的原子性,而不是两步;
public void unsafePut(String key){
if(!values.containsKey(key)){
values.put(key, calculateValue(key));
}
}
private Object calculateValue(String key){
return new Object();
}
// 解决办法:
java.util.concurrent.ConcurrentMap#putIfAbsent
private Map<String, Object> values = new ConcurrentHashMap<>();
public void safePut(String key){
values.putIfAbsent(key, calculateValue(key));
}
private Object calculateValue(String key){
return new Object();
}
- 看上去无害的程序在多线程环境下可能暗藏杀机
- 如何解决?
不可变对象:
String,Integer,BigDecimal等;
反例:
java.util.Date#setTime
可以修改已经存在的Date对象,所以不是线程安全的;
synchronized,Lock
注意:
volatile类型的变量保证了可见性,但是不能保证原子性.在进行自增等非原子性操作的时候依然会出现并发问题。
- 并发工具包(底层通常是CAS->Compare and Swap)
int/long -> AtomicInteger/AtomicLong
[] (array) -> AtomicLongArray/AtomicIntegerArray/AtomicDoubleArray
Object -> AtomicReference
HashMap -> ConcurrentHashMap
ArrayList -> CopyOnWriteArrayList
TreeMap -> ConcurrentSkipListMap(跳表)
其中:
java.util.concurrent.atomic.AtomicInteger#getAndUpdate
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next)); //如果更新不成功,会一直循环即自旋spin/乐观锁
return prev;
}
// 类似于哲学家吃饭问题;
public class Main {
static Object lock1 = new Object();
static Object lock2 = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (lock2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("");
}
}
}).start();
synchronized (lock1) {
Thread.sleep(500);
synchronized (lock2) {
System.out.println("");
}
}
}
}
- 死锁排查
- jps+jstack
分析jstack压缩包
https://fastthread.io/
- 定时任务+jstack
- 结合源代码
- Object.wait() + Object.notify()/notifyAll()
- Lock/Condition
- 如何避免死锁?
- 所有资源都以相同的顺序获得锁
ps: 实际上,在复杂程序中,这一点很难发现
网友评论