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

线程是不安全

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

只有一个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
结果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 如果和预期的结果不一致就不操作。

非多线程多线程
intAtomicInteger
longAtomicInLong
ArrayAtomicInLongArray
ObjectAtomicReference
HashMapConcurrentHashMap
ArrayListCopyOnWriteArrayList
TreeMapConcurrentSkipListMap

听天由命式编程

多线程的问题,也不是一定就会发生,也不一定不会发生。祈求老天保佑不会发生。
这种情况,如果发生,找问题就特别麻烦。

死锁

两个线程分别持有对方需要的资源,且还在等待对方持有的资源。

简单的死锁 示例

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

避免原则

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EngineerForSoul

你的鼓励是我孜孜不倦的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值