浅析:线程安全
思考:
一共有哪几类线程安全问题
那些场景需要额外注意线程安全问题
什么是多线程带来的上下文切换?
什么是多线程的上下文切换?
线程安全
什么是线程安全
不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,都不需要额外做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全。
主要是两个问题
数据争用:数据读写由于同时写,会造成错误数据
竞争条件:即使不是同时写造成的错误数据,由于顺序原因依然会造成错误,例如在写入前就读取了
如何避免线程安全问题
运行结果错误: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 加入
还未添加个人简介
评论