美文网首页一些收藏
Java并发编程 线程安全

Java并发编程 线程安全

作者: 香沙小熊 | 来源:发表于2020-12-24 16:56 被阅读0次

    前言

    什么是线程安全?

    《Java Concurrency In Partice》的作者 Brian Goetz 对 “线程安全” 有一个比较恰当的定义:“当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。”。

    什么情况下会出现线程安全问题

    • 运行结果错误:a++多线程下出现消失的请求现象
    • 活跃性问题:死锁、活锁、饥饿
    • 对象发布和初始化的时候的安全问题

    1.演示计数不准确(减少)

    public class MultiThreadErrorExample implements Runnable{
        static MultiThreadErrorExample instance = new MultiThreadErrorExample();
        int index;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(instance);
            Thread thread2 = new Thread(instance);
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            System.out.println("表面上结果是" + instance.index);
    
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                index++;
            }
        }
    }
    
    表面上结果是18287
    
    探测出错位置
    public class MultiThreadsError implements Runnable {
    
        static MultiThreadsError instance = new MultiThreadsError();
        int index = 0;
        static AtomicInteger realIndex = new AtomicInteger();
        static AtomicInteger wrongCount = new AtomicInteger();
        static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
        static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
    
        final boolean[] marked = new boolean[10000000];
    
        public static void main(String[] args) throws InterruptedException {
    
            Thread thread1 = new Thread(instance);
            Thread thread2 = new Thread(instance);
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            System.out.println("表面上结果是" + instance.index);
            System.out.println("真正运行的次数" + realIndex.get());
            System.out.println("错误次数" + wrongCount.get());
    
        }
    
        @Override
        public void run() {
            marked[0] = true;
            for (int i = 0; i < 10000; i++) {
                try {
                    cyclicBarrier2.reset();
                    cyclicBarrier1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                index++;
                try {
                    cyclicBarrier1.reset();
                    cyclicBarrier2.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                realIndex.incrementAndGet();
                synchronized (instance) {
                    if (marked[index] && marked[index - 1]) {
                        System.out.println("发生错误index" + index);
                        wrongCount.incrementAndGet();
                    }
                    marked[index] = true;
                }
            }
        }
    }
    
    发生错误index11889
    表面上结果是19999
    真正运行的次数20000
    错误次数1
    
    当index发生碰撞时,当前marked[index] 由前一个线程设置为true。

    2.死锁

    public class MultiThreadError implements Runnable {
    
        int flag = 1;
        static Object o1 = new Object();
        static Object o2 = new Object();
    
        public static void main(String[] args) {
            MultiThreadError r1 = new MultiThreadError();
            MultiThreadError r2 = new MultiThreadError();
            r1.flag = 1;
            r2.flag = 0;
            new Thread(r1).start();
            new Thread(r2).start();
        }
    
        @Override
        public void run() {
            System.out.println("flag = " + flag);
            if (flag == 1) {
                synchronized (o1) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (o2) {
                        System.out.println("1");
                    }
                }
            }
            if (flag == 0) {
                synchronized (o2) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (o1) {
                        System.out.println("0");
                    }
                }
            }
        }
    }
    
    flag = 1
    flag = 0
    

    发生死锁

    3.对象发布和初始化的时候的安全问题

    3.1 什么是发布

    https://www.cnblogs.com/CreateMyself/p/12459141.html

    3.2 什么是逸出
    1.方法返回一个private对象
    /**
     * 描述:     发布逸出
     */
    public class MultiThreadsError3 {
    
        private Map<String, String> states;
    
        public MultiThreadsError3() {
            states = new HashMap<>();
            states.put("1", "周一");
            states.put("2", "周二");
            states.put("3", "周三");
            states.put("4", "周四");
        }
    
        public Map<String, String> getStates() {
            return states;
        }
        public static void main(String[] args) {
            MultiThreadsError3 multiThreadsError3 = new MultiThreadsError3();
            Map<String, String> states = multiThreadsError3.getStates();
            System.out.println(states.get("1"));
            states.remove("1");
            System.out.println(states.get("1"));
        }
    }
    

    输出

    周一
    null
    
    发现 private的states 本意是不允许被外部程序修改,却被修改了
    2.还未完成初始化,构造函数还没完全执行完毕,就把对象提供给外界 如 :
    • 在构造函数中未初始化完毕就this赋值
    public class MultiThreadsError4 {
    
        static Point point;
    
        public static void main(String[] args) throws InterruptedException {
            new PointMaker().start();
            Thread.sleep(10);
    //        Thread.sleep(105);
            if (point != null) {
                System.out.println(point);
            }
        }
    }
    
    class Point {
    
        private final int x, y;
    
        public Point(int x, int y) throws InterruptedException {
            this.x = x;
            MultiThreadsError4.point = this;
            Thread.sleep(100);
            this.y = y;
        }
    
        @Override
        public String toString() {
            return x + "," + y;
        }
    }
    
    class PointMaker extends Thread {
    
        @Override
        public void run() {
            try {
                new Point(1, 1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    main方法中 Thread.sleep(10);时 输出结果

    1,0
    

    main方法中 Thread.sleep(105);时 输出结果

    1,1
    
    • 隐式逸出 --- 注册监听事件
    public class MultiThreadsError5 {
    
        int count;
    
        public MultiThreadsError5(MySource source) {
            source.registerListener(new EventListener() {
                @Override
                public void onEvent(Event e) {
                    System.out.println("\n我得到的数字是" + count);
                }
    
            });
    
            /***
             * 模拟执行业务逻辑 后再赋值count
             */
            try {
                TimeUnit.MILLISECONDS.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count = 100;
        }
    
        public static void main(String[] args) {
            MySource mySource = new MySource();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    mySource.eventCome(new Event() {
                    });
                }
            }).start();
            MultiThreadsError5 multiThreadsError5 = new MultiThreadsError5(mySource);
        }
    
        static class MySource {
    
            private EventListener listener;
    
            void registerListener(EventListener eventListener) {
                this.listener = eventListener;
            }
    
            void eventCome(Event e) {
                if (listener != null) {
                    listener.onEvent(e);
                } else {
                    System.out.println("还未初始化完毕");
                }
            }
    
        }
    
        interface EventListener {
    
            void onEvent(Event e);
        }
    
        interface Event {
    
        }
    }
    
    我得到的数字是0
    

    我们期待是100 结果出现0

    • 构造函数中运行线程
    /**
     * 描述:     构造函数中新建线程
     */
    public class MultiThreadsError6 {
    
        private Map<String, String> states;
    
        public MultiThreadsError6() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    states = new HashMap<>();
                    states.put("1", "周一");
                    states.put("2", "周二");
                    states.put("3", "周三");
                    states.put("4", "周四");
                }
            }).start();
        }
    
        public Map<String, String> getStates() {
            return states;
        }
    
        public static void main(String[] args) throws InterruptedException {
            MultiThreadsError6 multiThreadsError6 = new MultiThreadsError6();
    //        Thread.sleep(1000);
            System.out.println(multiThreadsError6.getStates().get("1"));
        }
    }
    
    Exception in thread "main" java.lang.NullPointerException
        at com.kpioneer.thread.background.MultiThreadsError6.main(MultiThreadsError6.java:33)
    
    

    接下来 我们Thread.sleep(1000); 再去执行 System.out.println(multiThreadsError6.getStates().get("1")); 结果输出

    周一
    

    因为调用时间不同,结果不同,这样的程序是不安全的。

    3.3 如何解决逸出
    • 返回"副本" 解决逸出行为1
    public class MultiThreadsError3 {
    
        private Map<String, String> states;
    
        public MultiThreadsError3() {
            states = new HashMap<>();
            states.put("1", "周一");
            states.put("2", "周二");
            states.put("3", "周三");
            states.put("4", "周四");
        }
    
        public Map<String, String> getStatesImproved() {
            return new HashMap<>(states);
        }
    
        public static void main(String[] args) {
            MultiThreadsError3 multiThreadsError3 = new MultiThreadsError3();
            System.out.println(multiThreadsError3.getStatesImproved().get("1"));
            multiThreadsError3.getStatesImproved().remove("1");
            System.out.println(multiThreadsError3.getStatesImproved().get("1"));
    
        }
    }
    
    周一
    周一
    
    • 工厂模式解决逸出行为2构造函数未初始化
    public class MultiThreadsError7 {
    
        int count;
        private final EventListener listener;
    
        private MultiThreadsError7(MySource source) {
            listener = new EventListener() {
                @Override
                public void onEvent(Event e) {
                    System.out.println("\n我得到的数字是" + count);
                }
    
            };
            /***
             * 模拟执行业务逻辑 后再赋值count
             */
            try {
                TimeUnit.MILLISECONDS.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count = 100;
        }
    
        /**
         * 工厂方法
         *
         * @param source
         * @return
         */
        public static MultiThreadsError7 getInstance(MySource source) {
            MultiThreadsError7 safeListener = new MultiThreadsError7(source);
            source.registerListener(safeListener.listener);
            return safeListener;
        }
    
        public static void main(String[] args) {
    
    
            MySource mySource = new MySource();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    mySource.eventCome(new Event() {
                    });
                }
            }).start();
    
            MultiThreadsError7 instance = MultiThreadsError7.getInstance(mySource);
    
        }
    
        interface EventListener {
    
            void onEvent(Event e);
        }
    
        interface Event {
    
        }
    
        static class MySource {
    
            private EventListener listener;
    
            void registerListener(EventListener eventListener) {
                this.listener = eventListener;
            }
    
            void eventCome(Event e) {
                if (listener != null) {
                    listener.onEvent(e);
                } else {
                    System.out.println("还未初始化完毕");
                }
            }
    
        }
    }
    
    
    还未初始化完毕
    
    注意:在实际开发中我们不会这么明显的犯错,但是也可能会被动犯错,比如调用数据库连接池(框架会自己开启线程初始化),如果我们过早调用就可能出错。

    4. 四种需要考虑线程安全的情况

    遇到以下四种需要考虑线程安全的情况,需要注意:

    1. 访问共享的变量或资源, 会有并发风险, 比如对象的属性, 静态变量, 共享缓存, 数据库等
      例如此文提到的例子, 用共享变量进行++操作
    2. 所有依赖时序的操作, 即使每一步操作都是线程安全的, 还是存在并发的问题.
      read-modify-write: 先读取, 再修改. check-then-act 先检查, 再执行.
      实际上本质是一样的, 一个线程先获取数据, 再进行下一步的操作. 主要可能的问题是, 数据读取后, 还有可能被其他线程修改. 所以在这种依赖时序的情况下, 可以用synchronized锁等操作.
    3. 不同的数据之间存在绑定关系的时候
      例如IP与端口号. 只要修改了IP就要修改端口号, 否则IP也是无效的. 因此遇到这种操作的时候, 要警醒原子的合并操作. 要么全部修改成功, 要么全部修改失败.
    4. 使用其他类的时候, 如果该类的注释声明了不是线程安全的, 那么就不应该在多线程的场景中使用, 而应该考虑其对应的线程安全的类,或者对其做一定处理保证线程安全,
      例如HashMap就不是线程安全的, 而ConcurrentHashMap则是线程安全的.
      悟空

    相关文章

      网友评论

        本文标题:Java并发编程 线程安全

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