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

不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,都不需要额外做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全。
主要是两个问题
- 数据争用:数据读写由于同时写,会造成错误数据 
- 竞争条件:即使不是同时写造成的错误数据,由于顺序原因依然会造成错误,例如在写入前就读取了 
如何避免线程安全问题
运行结果错误:a++ 多线程下出现消失的请求现象
活跃性问题:死锁、活锁、饥饿
对象发布和初始化的时候的安全问题
a++ 问题
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++ 

思考:
如何找到上一个案例中出错的值
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;        }    }}
死锁问题
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");                }            }        }    }}对象发布和初始化的时候的安全问题
什么是发布
- 声明为 public 
- return 一个对象 
- 把对象作为参数传递到其他类的方法中 
什么是逸出
- 方法返回一个 private 对象(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 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"));    }}
- 还未完成初始化(构造函数还没完全执行完毕)就把对象提供个外界 
- 在构造函数中未初始化完毕就 this 赋值 
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();        }    }}- 隐式逸出 —— 注册监听事件(观察者模式) 
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 {    }}
解决:
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 {    }}
- 构造函数中运行线程 
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"));    }}
如何解决逸出
- 副本 
- 工厂模式 
各种需要考虑线程安全的情况
- 访问共享变量或资源,会有并发风险,比如对象的属性、静态变量、共享缓存、数据库等 
- 所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题 
- read-modify-writer 操作:一个线程读取了一个共享数据,并在此基础上更新该数据。该例子在上面的 a++ 已展示。 
- check-then-act 操作:一个线程读取了一个共享数据,并在此基础上决定其下一个的操作 
- 不同的数据之间存在绑定关系的时候 
- IP 和端口号 
- 我们使用其他类的时候,如果对方没有声明自己是线程安全的,那么大概率会存在并发问题 
- hashmap 没有声明知己是并发安全的,所以我们并发调用 hashmap 的时候会出错 
多线程会导致的问题
什么是性能问题、性能问题有哪些体现?
为什么多线程会带来性能问题
- 调度:上下文切换 
- 协作:内存同步 
调度:上下文切换
什么是上下文?:保存现场
缓存开销:缓存失效
何时会导致密集的上下文切换:抢锁、IO
参考
https://coding.imooc.com/class/362.html
 
 朱华
见自己,见天地,见众生。 2018.08.07 加入
还未添加个人简介
 
  
  
  
  
  
  
  
  
    
评论