浅析:线程安全

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"思考:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一共有"},{"type":"text","marks":[{"type":"strong"}],"text":"哪几类"},{"type":"text","text":"线程安全问题"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那些场景需要额外注意线程安全问题 "}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"什么是多线程带来的上下文切换?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"什么是多线程的上下文切换? "}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"线程安全"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"什么是线程安全"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/70/70f27f1bd67ef33c24ba0f9f13fff2e1.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,"},{"type":"text","marks":[{"type":"strong"}],"text":"都不需要额外做任何额外的处理"},{"type":"text","text":"(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"主要是两个问题"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"数据争用:数据读写由于同时写,会造成错误数据"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"竞争条件:即使不是同时写造成的错误数据,由于顺序原因依然会造成错误,例如在写入前就读取了"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"如何避免线程安全问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"运行结果错误:a++ 多线程下出现消失的请求现象"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"活跃性问题:死锁、活锁、饥饿"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对象发布和初始化的时候的安全问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"a++ 问题"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MultiThreadsError implements Runnable {\n\n int index = 0;\n static MultiThreadsError instance = new MultiThreadsError();\n\n public static void main(String[] args) throws InterruptedException {\n Thread thread1 = new Thread(instance);\n Thread thread2 = new Thread(instance);\n\n thread1.start();\n thread2.start();\n\n thread1.join();\n thread2.join();\n\n System.out.println(instance.index);\n }\n\n @Override\n public void run() {\n for (int i = 0; i < 10000; i++) {\n index++;\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"运行结果错误:没有原子性"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"a++"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d1/d17c428a9a1676d2b5cc6da1dc019bef.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"思考:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如何找到上一个案例中出错的值"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MultiThreadsError implements Runnable {\n\n int index = 0;\n static MultiThreadsError instance = new MultiThreadsError();\n\n // 原子计数器功能\n static AtomicInteger realIndex = new AtomicInteger();\n static AtomicInteger wrongIndex = new AtomicInteger();\n\n // 由于线程的执行的先后顺序无法确定,所以加入栅栏,让他们同时出发\n static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);\n // 同时释放\n static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);\n\n // 使用 boolean 数组标记到错误的值\n final boolean[] marked = new boolean[1000000];\n\n public static void main(String[] args) throws InterruptedException {\n\n Thread thread1 = new Thread(instance);\n Thread thread2 = new Thread(instance);\n\n thread1.start();\n thread2.start();\n\n thread1.join();\n thread2.join();\n\n System.out.println(\"表面上运行结果是 \" + instance.index);\n System.out.println(\"真正运行的次数 \" + realIndex.get());\n System.out.println(\"错误的次数 \" + wrongIndex.get());\n }\n\n @Override\n public void run() {\n marked[0] = true;\n for (int i = 0; i < 10000; i++) {\n\n try {\n cyclicBarrier1.await(); // 栅栏(当有两个线程执行过它,放行)\n } catch (Exception e) {\n e.printStackTrace();\n }\n\n index++;\n\n try {\n cyclicBarrier1.reset();\n cyclicBarrier2.await();\n } catch (Exception e) {\n e.printStackTrace();\n }\n\n realIndex.incrementAndGet();\n\n synchronized (instance) {\n if (marked[index] && marked[index - 1]) {\n System.out.println(\"发生了错误\" + index);\n wrongIndex.incrementAndGet();\n }\n }\n\n marked[index] = true;\n }\n }\n\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"死锁问题"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MultiThreadError implements Runnable {\n\n int flag = 1;\n\n static Object object1 = new Object();\n static Object object2 = new Object();\n\n public static void main(String[] args) {\n MultiThreadError r1 = new MultiThreadError();\n MultiThreadError r2 = new MultiThreadError();\n\n r1.flag = 1;\n r2.flag = 0;\n\n new Thread(r1).start();\n new Thread(r2).start();\n }\n\n @Override\n public void run() {\n System.out.println(\"flag = \" + flag);\n\n if (flag == 1) {\n synchronized (object1) {\n try {\n Thread.sleep(500);\n } catch (InterruptedException e) {\n e.printStackTrace();\n }\n synchronized (object2) {\n System.out.println(\"object 1\");\n }\n }\n }\n\n if (flag == 0) {\n synchronized (object2) {\n try {\n Thread.sleep(500);\n } catch (InterruptedException e) {\n e.printStackTrace();\n }\n synchronized (object1) {\n System.out.println(\"object 2\");\n }\n }\n }\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"对象发布和初始化的时候的安全问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"什么是发布"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"声明为 public"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"return 一个对象"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"把对象作为参数传递到其他类的方法中"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"什么是逸出"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"方法返回一个 private 对象(private 的本意是不让外部访问)"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MultiThreadsError3 {\n\n private Map states;\n\n public MultiThreadsError3() {\n states = new HashMap<>();\n states.put(\"1\", \"周一\");\n states.put(\"2\", \"周二\");\n states.put(\"3\", \"周三\");\n states.put(\"4\", \"周四\");\n }\n\n public Map getStates() {\n return states;\n }\n\n public Map getStatesImproved() {\n return new HashMap<>(states);\n }\n\n public static void main(String[] args) {\n MultiThreadsError3 multiThreadsError3 = new MultiThreadsError3();\n Map states = multiThreadsError3.getStates();\n\n System.out.println(states.get(\"1\"));\n states.remove(\"1\");\n System.out.println(states.get(\"1\"));\n\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"还未完成初始化(构造函数还没完全执行完毕)就把对象提供个外界"}]}]}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在构造函数中未初始化完毕就 this 赋值"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MultiThreadsError4 {\n\n static Point point;\n\n public static void main(String[] args) throws InterruptedException {\n new PointMaker().start();\n\n Thread.sleep(505);\n\n if (point != null) {\n System.out.println(point);\n }\n }\n\n}\n\nclass Point {\n\n private final int x, y;\n\n public Point(int x, int y) throws InterruptedException {\n this.x = x;\n MultiThreadsError4.point = this;\n Thread.sleep(100); // MultiThreadsError4 中会根据 sleep 的大于或小于的阻塞时间而变化\n this.y = y;\n }\n\n @Override\n public String toString() {\n return \"Point{\" +\n \"x=\" + x +\n \", y=\" + y +\n '}';\n }\n}\n\nclass PointMaker extends Thread {\n\n @Override\n public void run() {\n try {\n new Point(1, 1);\n } catch (Exception e) {\n e.printStackTrace();\n }\n }\n}"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隐式逸出 —— 注册监听事件(观察者模式)"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MultiThreadsError5 {\n\n int count;\n\n public MultiThreadsError5(MySource source) {\n source.registerListener(new EventListener() {\n @Override\n public void onEvent(Event e) {\n System.out.println(\"\\n我得到的数字是\" + count);\n }\n\n });\n for (int i = 0; i < 10000; i++) {\n System.out.print(i);\n }\n count = 100;\n }\n\n public static void main(String[] args) {\n MySource mySource = new MySource();\n new Thread(new Runnable() {\n @Override\n public void run() {\n try {\n Thread.sleep(10);\n } catch (InterruptedException e) {\n e.printStackTrace();\n }\n mySource.eventCome(new Event() {\n });\n }\n }).start();\n MultiThreadsError5 multiThreadsError5 = new MultiThreadsError5(mySource);\n }\n\n static class MySource {\n\n private EventListener listener;\n\n void registerListener(EventListener eventListener) {\n this.listener = eventListener;\n }\n\n void eventCome(Event e) {\n if (listener != null) {\n listener.onEvent(e);\n } else {\n System.out.println(\"还未初始化完毕\");\n }\n }\n }\n\n interface EventListener {\n\n void onEvent(Event e);\n }\n\n interface Event {\n\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"解决:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MultiThreadsError7 {\n\n int count;\n private EventListener listener;\n\n private MultiThreadsError7(MySource source) {\n listener = new EventListener() {\n @Override\n public void onEvent(MultiThreadsError5.Event e) {\n System.out.println(\"\\n我得到的数字是\" + count);\n }\n\n };\n for (int i = 0; i < 10000; i++) {\n System.out.print(i);\n }\n count = 100;\n }\n\n public static MultiThreadsError7 getInstance(MySource source) {\n MultiThreadsError7 safeListener = new MultiThreadsError7(source);\n source.registerListener(safeListener.listener);\n return safeListener;\n }\n\n public static void main(String[] args) {\n MySource mySource = new MySource();\n new Thread(new Runnable() {\n @Override\n public void run() {\n try {\n Thread.sleep(10);\n } catch (InterruptedException e) {\n e.printStackTrace();\n }\n mySource.eventCome(new MultiThreadsError5.Event() {\n });\n }\n }).start();\n MultiThreadsError7 multiThreadsError7 = new MultiThreadsError7(mySource);\n }\n\n static class MySource {\n\n private EventListener listener;\n\n void registerListener(EventListener eventListener) {\n this.listener = eventListener;\n }\n\n void eventCome(MultiThreadsError5.Event e) {\n if (listener != null) {\n listener.onEvent(e);\n } else {\n System.out.println(\"还未初始化完毕\");\n }\n }\n\n }\n\n interface EventListener {\n\n void onEvent(MultiThreadsError5.Event e);\n }\n\n interface Event {\n\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"构造函数中运行线程"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class MultiThreadsError6 {\n\n private Map states;\n\n public MultiThreadsError6() {\n new Thread(new Runnable() {\n @Override\n public void run() {\n states = new HashMap<>();\n states.put(\"1\", \"周一\");\n states.put(\"2\", \"周二\");\n states.put(\"3\", \"周三\");\n states.put(\"4\", \"周四\");\n }\n }).start();\n }\n\n public Map getStates() {\n return states;\n }\n\n public static void main(String[] args) throws InterruptedException {\n MultiThreadsError6 multiThreadsError6 = new MultiThreadsError6();\n // 造成时间不同执行不同\n Thread.sleep(1000); \n System.out.println(multiThreadsError6.getStates().get(\"1\"));\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如何解决逸出"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"副本"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"工厂模式"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"各种需要考虑线程安全的情况"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"访问共享变量或资源,会有并发风险,比如对象的属性、静态变量、共享缓存、数据库等"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"read-modify-writer 操作:一个线程读取了一个共享数据,并在此基础上更新该数据。该例子在上面的 a++ 已展示。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"check-then-act 操作:一个线程读取了一个共享数据,并在此基础上决定其下一个的操作"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不同的数据之间存在绑定关系的时候"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"IP 和端口号"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们使用其他类的时候,如果对方没有声明自己是线程安全的,那么大概率会存在并发问题"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"hashmap 没有声明知己是并发安全的,所以我们并发调用 hashmap 的时候会出错"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"多线程会导致的问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"什么是性能问题、性能问题有哪些体现?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"为什么多线程会带来性能问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"调度:上下文切换"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"协作:内存同步"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"调度:上下文切换"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"什么是上下文?:保存现场"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"缓存开销:缓存失效"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"何时会导致密集的上下文切换:抢锁、IO"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"参考"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://coding.imooc.com/class/362.html"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章