美文网首页
线程安全问题

线程安全问题

作者: 叫我C30混凝土 | 来源:发表于2020-10-17 22:06 被阅读0次
    • 什么是线程安全?
      当一个类在多线程环境下被使用时,仍能表现出正确的行为;

    线程安全问题

    竞争条件

    • 原子性
    // 示例一:
    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: 实际上,在复杂程序中,这一点很难发现

    相关文章

      网友评论

          本文标题:线程安全问题

          本文链接:https://www.haomeiwen.com/subject/auifmktx.html