成员变量和静态变量是否线程安全
局部变量是否线程安全
局部变量是线程安全的
但局部变量引用的对象则未必
如果该对象没有逃离方法的作用访问,它是线程安全的
如果该对象逃离方法的作用范围,需要考虑线程安全
局部变量线程安全分析
1 2 3 4 public static void test1 () { int i = 10 ; i++; }
每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class ThreadUnsafe { ArrayList<String> list = new ArrayList <>(); 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 ); } } @Slf4j public class TestThreadUnsafe { static final int THREAD_NUMBER = 2 ; static final int LOOP_NUMBER = 200 ; public static void main (String[] args) { ThreadUnsafe test = new ThreadUnsafe (); for (int i = 0 ; i < THREAD_NUMBER; i++) { new Thread (() -> { test.method1(LOOP_NUMBER); }, "Thread" + i).start(); } } }
其中一种情况是,如果线程2 还未 add,线程1 remove 就会报错:
1 2 3 4 5 6 7 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 )
分析:
无论哪个线程中的 method2 引用的都是同一个对象中的 list 成员变量
method3 与 method2 分析相同
将 list 修改为局部变量,那么就不会有上述问题了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class ThreadSafe { public final void method1 (int loopNumber) { 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 ); } }
分析:
list 是局部变量,每个线程调用时会创建其不同实例,没有共享
而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象
method3 的参数分析与 method2 相同
方法访问修饰符带来的思考,如果把 method2 和 method3 的方法修改为 public 会不会代理线程安全问题?
情况1:有其它线程调用 method2 和 method3
情况2:在 情况1 的基础上,为 ThreadSafe 类添加子类,子类覆盖 method2 或 method3 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class ThreadSafe { public final void method1 (int loopNumber) { 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 ); } } class ThreadSafeSubClass extends ThreadSafe { @Override public void method3 (ArrayList<String> list) { new Thread (() -> { list.remove(0 ); }).start(); } }
从这个例子可以看出 private 或 final 提供【安全】的意义所在,请体会开闭原则中的【闭】
常见线程安全类
这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。也可以理解为
1 2 3 4 5 6 7 Hashtable table = new Hashtable ();new Thread (()->{ table.put("key" , "value1" ); }).start(); new Thread (()->{ table.put("key" , "value2" ); }).start();
它们的每个方法是原子的,但注意它们多个方法的组合不是原子的,见后面分析
线程安全类方法的组合
分析下面代码是否线程安全?
1 2 3 4 5 Hashtable table = new Hashtable ();if ( table.get("key" ) == null ) { table.put("key" , value); }
不可变类线程安全性
String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的
有同学或许有疑问,String 有 replace,substring 等方法【可以】改变值啊,那么这些方法又是如何保证线程安全的呢?