03 Java多线程及并发 为什么说线程是不安全的,怎么排查死锁问题

Java多线程及并发

  • 线程是不安全
  • 线程的安全问题(竞争条件)
    • 原子性 (Atomicity)
    • 逻辑上没问题的代码,在多线程的环境下却暗藏杀机
  • 如何处理线程的安全问题
    • 不可变对象
    • 加各种锁
    • 并发工具包
    • 听天由命式编程
  • 死锁
    • 简单的死锁 示例
    • 排查解决
    • 避免原则

线程是不安全

一段代码如果在多线程情况下,执行的结果和预期的不符合,就是线程不安全。
为什么说线程是不安全的?话不多说直接上代码。

只有一个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
03 Java多线程及并发 为什么说线程是不安全的,怎么排查死锁问题_第1张图片
结果2
结果是不是匪夷所思,你可以通过代码多尝试运行一下。结论:多线的确实是不安全的。

线程的安全问题(竞争条件)

原子性 (Atomicity)

原子性是指在一个操作中就是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

避免原则

所有的资源都以相同的顺序获取锁;
可能是多个线程分别获取各自的资源,造成死锁。

你可能感兴趣的:(Java,Thread,ThreadPool,多线程,并发编程,为什么说线程是不安全的,死锁的排查)