【Kill Thread Part.1-8】双刃剑:多线程会导致的问题

【Kill Thread Part.1-8】双刃剑:多线程会导致的问题

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

一、线程安全问题

1、什么是线程安全

image-20220126145016874

通俗的讲:

image-20220126145213378

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

①出错代码

/**
 * 描述: 第一种:运行结果出错
 * 演示:计数不准确(减少),找出具体出错的位置
 */
public class MultiThreadsError implements Runnable{
    static MultiThreadsError instance = new MultiThreadsError();
    int index = 0;
    @Override
    public void run() {
        //while (index < 10000) {
        //    index++;
        //}
        for (int i = 0; i < 10000; i++) {
            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);
    }
}

运行之后,index的值每次都不一样。

②原理分析

线程安全问题

线程1执行了i+1的操作,还没有写回去,此时线程2访问i,i还是1,加一操作还是2,最后两个线程写入i,i还是2,发生了两次操作,只有一次生效的结果。

③代码升级

/**
 * 描述:     第一种:运行结果出错。 演示计数不准确(减少),找出具体出错的位置。
 */
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);
                    wrongCount.incrementAndGet();
                }
                marked[index] = true;
            }
        }
    }
}

image-20220126152440786

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

①死锁

/**
 * 描述:     第二章线程安全问题,演示死锁。
 */
public class MultiThreadError implements Runnable {

    int flag = 1;
    //类锁
    static Object o1 = new Object();
    static Object o2 = new Object();

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

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

两个线程一直等待对方释放锁

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

①什么是对象发布

让一个对象可以在超过这个类的范围之外去其他类使用,比如:

  • 一个对象被声明为public
  • 一个方法的return是一个对象
  • 一个类的对象作为参数,传递到某一个方法中

②什么是逸出

image-20220126153959411

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

运行结果:

image-20220126154832426

我们本意是,不允许线程修改states,但是在多线程的情况下,如果有一个线程把这个逸出的states给修改了,其他线程都会收到影响,危害很大。

④在构造函数中未初始化完毕就this赋值

/**
 * 描述: 初始化未完毕,就this赋值
 */
public class MultiThreadsError4 {
    static Point point;

    public static void main(String[] args) throws InterruptedException {
        new PointMaker().start();
        //Thread.sleep(10); //结果为1,0
        Thread.sleep(1000); //结果为1,1
        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();
        }
    }
}

⑤观察者模式,匿名内部类注册监听器

/**
 * 描述:     观察者模式
 */
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 {

    }
}

分析:

image-20220126160301416

⑥构造函数中新建线程

import java.util.HashMap;
import java.util.Map;

/**
 * 描述:     构造函数中新建线程
 */
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"));
    }
}

在初始化过程中新建线程,如果初始化没有完毕,去取值的话,会报空指针异常。

image-20220126161138070

如果给主线程加个休眠的话,初始化完毕,就可以避免异常,这就造成了程序的稳定性很差。

二、解决多线程逸出问题

1、返回副本

  • 解决private的问题

image-20220126161541842

2、工厂模式使得初始化完毕

/**
 * 描述:     用工厂模式修复刚才的初始化问题
 */
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 {

    }
}

通过使用工厂方法模式将初始化工作和注册监听器工作封装在一起。

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

image-20220126162351272

四、多线程会导致的性能问题

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

image-20220126162639475

2、调度:上下文切换

image-20220126164857706

上下文:保存现场

上下文切换:

  • 挂起一个线程,把这个线程的状态保存下来(这个状态就是上下文)
    • 包括我这个线程执行到哪一个指令了?变量的值是什么状态?

缓存开销:缓存失效

  • 上下文切换之后,CPU需要重新进行缓存,寄存器的状态

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

3、协作:Java内存模型(下个章节)

编译器或者JVM会对我们的代码优化,对某些锁进行优化,内存方面的优化,如果缓存失效问题发生,也会造成开销。

想要多线程写锁,把多个线程进行调度,使得频繁上下文切换

五、总结

image-20220126163645373

面试问题

  • 你知道哪些线程不安全的情况?
  • 平时哪些情况下需要额外注意线程安全问题
  • 什么是多线程的上下文切换
posted @ 2022-01-26 16:53  DarkerG  阅读(53)  评论(0编辑  收藏  举报