一段代码如果在多线程情况下,执行的结果和预期的不符合,就是线程不安全。
为什么说线程是不安全的?话不多说直接上代码。
只有一个main线程时:
public class ThreadUnSafe {
private static int globalI = 0;
public static void main(String[] args) {
for (int i=0;i<100;i++){
globalI++;
}
System.out.println("非多线程:"+globalI);
}
}
输出结果:
非多线程:100
多线程情况下:
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++;
}
}
输出结果:99、100、98
结果是不是匪夷所思,你可以通过代码多尝试运行一下。结论:多线的确实是不安全的。
原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。
//上面示例中的 操作 globalI 时时非原子的。
globalI++;
//在java中实际执行三行代码 读取、+1操作、回写
int localVariable = globalI;
localVariable += 1;
globalI = localVariable;
1:java 中自增操作在多线程中就容易出现问题;通过上面示例就能说明这一点。
2:单例模式 多线程也有一定的问题。
//线程不安全 多个线程同时访问getInstance方法
public class SingletonObject {
private static SingletonObject INSTANCE = null;
public SingletonObject getInstance() {
if (INSTANCE == null) {
INSTANCE = new SingletonObject();
}
return INSTANCE;
}
public SingletonObject() {
//......
}
}
//线程安全写法
private volatile static SingletonObject INSTANCE = null;
public SingletonObject getInstance() {
if (INSTANCE == null) {
synchronized (SingletonObject.class) {
if (INSTANCE == null) {
INSTANCE = new SingletonObject();
}
}
}
return INSTANCE;
}
3:ConcurrentHashM ap
Map<String, Object> values = new ConcurrentHashMap<>();
public void unSafePut(String key){
//即使使用ConcurrentHashMap 如下判断也是线程不安全的。
if(!values.containsKey(key)){
values.put(key,calculateValue(key));
}
}
private Object calculateValue(String key){
return null;
}
values.put(key,calculateValue(key));
修改为:
values.putIfAbsent(key,calculateValue(key));
String,Integer,BigDecimal等,但是,Date对象是可变的 setTime方法
synchronized、Lock
底层通常是CAS,Compare And Swap。
原理:自旋,乐观锁,spin 如果和预期的结果不一致就不操作。
非多线程 | 多线程 |
---|---|
int | AtomicInteger |
long | AtomicInLong |
Array | AtomicInLongArray |
Object | AtomicReference |
HashMap | ConcurrentHashMap |
ArrayList | CopyOnWriteArrayList |
TreeMap | ConcurrentSkipListMap |
多线程的问题,也不是一定就会发生,也不一定不会发生。祈求老天保佑不会发生。
这种情况,如果发生,找问题就特别麻烦。
两个线程分别持有对方需要的资源,且还在等待对方持有的资源。
static Object lock1 = new Object();
static Object lock2 = new Object();
public static void main(String[] args) throws InterruptedException {
//Thread 线程持有lock2 等待lock1
new Thread(()->{
synchronized (lock2){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1){
System.out.println("等待资源lock1");
}
}
}).start();
// main线程持有lock1 等待lock2
synchronized (lock1){
Thread.sleep(500);
synchronized (lock2){
System.out.println("等待资源lock2");
}
}
}
查看jvm
"main" Id=1 BLOCKED on java.lang.Object@8778a2e owned by "Thread-2" Id=27
at com.lingyiwin.thread.Dielock.main(Dielock.java:29)
- locked java.lang.Object@1c4516b8
"Thread-2" Id=27 BLOCKED on java.lang.Object@1c4516b8 owned by "main" Id=1
at com.lingyiwin.thread.Dielock.lambda$main$0(Dielock.java:20)
- locked java.lang.Object@8778a2e
at com.lingyiwin.thread.Dielock$$Lambda$1/000000000000000000.run(Unknown Source)
at java.lang.Thread.run(Thread.java:823)
1:查看jvm。 jps + jstack
2:定时任务 + jstack
3:结合源代码
4:Object.wait() + Object.notify() / notifyAll()
5:Lock + Condition
所有的资源都以相同的顺序获取锁;
可能是多个线程分别获取各自的资源,造成死锁。