线程安全分析这小节我们将会介绍线程安全分析
变量线程安全问题首先我们来思考成员变量和静态变量的安全性:
- 如果它们没有共享 , 则线程安全
- 如果它们被共享了:如果只有读操作,则线程安全
- 如果它们被共享了:如果有读写操作,则这段代码是临界区,需要考虑线程安全
- 局部变量是线程安全的
- 局部变量引用的对象:如果该对象没有逃离方法的作用访问,它是线程安全的
- 局部变量引用的对象:如果该对象逃离方法的作用范围,需要考虑线程安全
/*局部变量*/ // 源代码展示:public static void test1() {int i = 10;i++;}// 我们查看底层代码:每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享public static void test1(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=0 0: bipush 10 2: istore_0 3: iinc 0, 1 6: return LineNumberTable: line 10: 0 line 11: 3 line 12: 6 LocalVariableTable: Start Length Slot Name Signature 340iI/*成员变量*/// 源代码展示:static final int THREAD_NUMBER = 2;static final int LOOP_NUMBER = 200;public static void main(String[] args) {// 一个test对象ThreadUnsafe test = new ThreadUnsafe();// 两个线程去操纵for (int i = 0; i < THREAD_NUMBER; i++) {// 均执行method1方法new Thread(() -> {test.method1(LOOP_NUMBER);}, "Thread" + i).start();}}class ThreadUnsafe {// 这里的list是属于对象的,创建在堆中,属于线程共同操纵对象ArrayList<String> list = new ArrayList<>();// 不断调用method2,3方法200次public void method1(int loopNumber) {for (int i = 0; i < loopNumber; i++) {// { 临界区, 会产生竞态条件method2();method3();// } 临界区}}private void method2() {list.add("1");}private void method3() {list.remove(0);}}// 运行结果的其中一种:如果线程2 还未 add,线程1 remove 就会报错Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(ArrayList.java:657) at java.util.ArrayList.remove(ArrayList.java:496) at cn.itcast.n6.ThreadUnsafe.method3(TestThreadSafe.java:35) at cn.itcast.n6.ThreadUnsafe.method1(TestThreadSafe.java:26) at cn.itcast.n6.TestThreadSafe.lambda$main$0(TestThreadSafe.java:14) at java.lang.Thread.run(Thread.java:748) /*成员变量局部优化*/// 源代码展示static final int THREAD_NUMBER = 2;static final int LOOP_NUMBER = 200;public static void main(String[] args) {// 一个test对象ThreadUnsafe test = new ThreadUnsafe();// 两个线程去操纵for (int i = 0; i < THREAD_NUMBER; i++) {// 均执行method1方法new Thread(() -> {test.method1(LOOP_NUMBER);}, "Thread" + i).start();}}class ThreadSafe {public final void method1(int loopNumber) {// 这里将list变为局部变量,每个线程独自在自己的栈中创建,就不会产生安全问题ArrayList<String> list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {method2(list);method3(list);}}private void method2(ArrayList<String> list) {list.add("1");}private void method3(ArrayList<String> list) {list.remove(0);}}/*方法public化缺陷*/// 源代码展示:class ThreadSafe {public final void method1(int loopNumber) {ArrayList<String> list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {method2(list);method3(list);}}public void method2(ArrayList<String> list) {list.add("1");}public void method3(ArrayList<String> list) {list.remove(0);}}class ThreadSafeSubClass extends ThreadSafe{@Overridepublic void method3(ArrayList<String> list) {new Thread(() -> {list.remove(0);}).start();}}// 如果我们将method2,3改为public,就可能会导致其他线程直接调用method2,3导致安全性问题// 同时甚至可能出现其他子类继承父类导致修改原方法,同时创建一个线程导致多线程问题出现
常见线程安全类我们在下面介绍一下我们常用的线程安全类:- String
- Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent 包下的类
- String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的
- 这里需要注意:String的各类修改方法都是直接创建一个新的String类型而不是在String本体进行增删
- 线程安全类的每个方法是原子的
- 但注意它们多个方法的组合不是原子的!
/*下述的单个方法都是原子且安全性的*/ Hashtable table = new Hashtable();new Thread(()->{table.put("key", "value1");}).start();new Thread(()->{table.put("key", "value2");}).start();/*但当这些方法组合起来,就无法保证其线程安全性*/ Hashtable table = new Hashtable();// 线程1,线程2都执行时,可能出现下述情况// 线程1get==null,线程2get==null , 线程2put,线程1put;导致线程安全性错误出现if( table.get("key") == null) { table.put("key", value);}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Seata Server 1.5.2 源码学习
- 2022极端高温!机器学习如何预测森林火灾?? 万物AI
- 1.nginx学习
- 常用Python库整理
- 图学习参考资料 词向量word2vec
- 五 RK3568开发笔记:在虚拟机上使用SDK编译制作uboot、kernel和ubuntu镜像
- realme笔记本最新消息_realme笔记本即将发布
- JUC学习笔记——进程与线程
- Seata 1.5.2 源码学习
- 第4版 高性能MySQL 第一章 MySQL架构 读书笔记