首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

浅析:线程安全

思考:

  • 一共有哪几类线程安全问题
  • 那些场景需要额外注意线程安全问题
  • 什么是多线程带来的上下文切换?
  • 什么是多线程的上下文切换?

线程安全

什么是线程安全

不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,都不需要额外做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全。

主要是两个问题

  1. 数据争用:数据读写由于同时写,会造成错误数据
  2. 竞争条件:即使不是同时写造成的错误数据,由于顺序原因依然会造成错误,例如在写入前就读取了

如何避免线程安全问题

运行结果错误:a++ 多线程下出现消失的请求现象

活跃性问题:死锁、活锁、饥饿

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

a++ 问题

代码语言:javascript
复制

public class MultiThreadsError implements Runnable {

    int index = 0;
    static MultiThreadsError instance = new MultiThreadsError();

    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++;
        }
    }
}

运行结果错误:没有原子性

  • a++

思考:

如何找到上一个案例中出错的值

代码语言:javascript
复制

public class MultiThreadsError implements Runnable {

    int index = 0;
    static MultiThreadsError instance = new MultiThreadsError();

    // 原子计数器功能
    static AtomicInteger realIndex = new AtomicInteger();
    static AtomicInteger wrongIndex = new AtomicInteger();

    // 由于线程的执行的先后顺序无法确定,所以加入栅栏,让他们同时出发
    static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
    // 同时释放
    static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);

    // 使用 boolean 数组标记到错误的值
    final boolean[] marked = new boolean[1000000];

    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("错误的次数 " + wrongIndex.get());
    }

    @Override
    public void run() {
        marked[0] = true;
        for (int i = 0; i < 10000; i++) {

            try {
                cyclicBarrier1.await(); // 栅栏(当有两个线程执行过它,放行)
            } catch (Exception e) {
                e.printStackTrace();
            }

            index++;

            try {
                cyclicBarrier1.reset();
                cyclicBarrier2.await();
            } catch (Exception e) {
                e.printStackTrace();
            }

            realIndex.incrementAndGet();

            synchronized (instance) {
                if (marked[index] && marked[index - 1]) {
                    System.out.println("发生了错误" + index);
                    wrongIndex.incrementAndGet();
                }
            }

            marked[index] = true;
        }
    }

}

死锁问题

代码语言:javascript
复制

public class MultiThreadError implements Runnable {

    int flag = 1;

    static Object object1 = new Object();
    static Object object2 = 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 (object1) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (object2) {
                    System.out.println("object 1");
                }
            }
        }

        if (flag == 0) {
            synchronized (object2) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (object1) {
                    System.out.println("object 2");
                }
            }
        }
    }
}

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

什么是发布

  1. 声明为 public
  2. return 一个对象
  3. 把对象作为参数传递到其他类的方法中

什么是逸出

  1. 方法返回一个 private 对象(private 的本意是不让外部访问)
代码语言:javascript
复制

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 Map<String, String> getStatesImproved() {
        return new HashMap<>(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"));

    }
}
  1. 还未完成初始化(构造函数还没完全执行完毕)就把对象提供个外界
  • 在构造函数中未初始化完毕就 this 赋值
代码语言:javascript
复制

public class MultiThreadsError4 {

    static Point point;

    public static void main(String[] args) throws InterruptedException {
        new PointMaker().start();

        Thread.sleep(505);

        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);  // MultiThreadsError4 中会根据 sleep 的大于或小于的阻塞时间而变化
        this.y = y;
    }

    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

class PointMaker extends Thread {

    @Override
    public void run() {
        try {
            new Point(1, 1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 隐式逸出 —— 注册监听事件(观察者模式)
代码语言:javascript
复制

public class MultiThreadsError5 {

    int count;

    public MultiThreadsError5(MySource source) {
        source.registerListener(new EventListener() {
            @Override
            public void onEvent(Event e) {
                System.out.println("\n我得到的数字是" + count);
            }

        });
        for (int i = 0; i < 10000; i++) {
            System.out.print(i);
        }
        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 {

    }
}

解决:

代码语言:javascript
复制

public class MultiThreadsError7 {

    int count;
    private EventListener listener;

    private MultiThreadsError7(MySource source) {
        listener = new EventListener() {
            @Override
            public void onEvent(MultiThreadsError5.Event e) {
                System.out.println("\n我得到的数字是" + count);
            }

        };
        for (int i = 0; i < 10000; i++) {
            System.out.print(i);
        }
        count = 100;
    }

    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 MultiThreadsError5.Event() {
                });
            }
        }).start();
        MultiThreadsError7 multiThreadsError7 = new MultiThreadsError7(mySource);
    }

    static class MySource {

        private EventListener listener;

        void registerListener(EventListener eventListener) {
            this.listener = eventListener;
        }

        void eventCome(MultiThreadsError5.Event e) {
            if (listener != null) {
                listener.onEvent(e);
            } else {
                System.out.println("还未初始化完毕");
            }
        }

    }

    interface EventListener {

        void onEvent(MultiThreadsError5.Event e);
    }

    interface Event {

    }
}
  • 构造函数中运行线程
代码语言:javascript
复制

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"));
    }
}

如何解决逸出

  1. 副本
  2. 工厂模式

各种需要考虑线程安全的情况

  • 访问共享变量或资源,会有并发风险,比如对象的属性、静态变量、共享缓存、数据库等
  • 所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题
  • read-modify-writer 操作:一个线程读取了一个共享数据,并在此基础上更新该数据。该例子在上面的 a++ 已展示。
  • check-then-act 操作:一个线程读取了一个共享数据,并在此基础上决定其下一个的操作
  • 不同的数据之间存在绑定关系的时候
  • IP 和端口号
  • 我们使用其他类的时候,如果对方没有声明自己是线程安全的,那么大概率会存在并发问题
  • hashmap 没有声明知己是并发安全的,所以我们并发调用 hashmap 的时候会出错

多线程会导致的问题

什么是性能问题、性能问题有哪些体现?

为什么多线程会带来性能问题

  • 调度:上下文切换
  • 协作:内存同步

调度:上下文切换

什么是上下文?:保存现场

缓存开销:缓存失效

何时会导致密集的上下文切换:抢锁、IO

参考

https://coding.imooc.com/class/362.html

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/d332fb85d0487078dc2c89ea7
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券