变量分为
成员变量和静态变量
- 没有共享,线程安全
- 共享了,只是读,则线程安全
- 共享了,有读有写,则线程不安全
局部变量是否线程安全
- 局部变量线程安全
- 局部变量的引用,未必
- 该对象没有逃离方法的作用范围,则安全
- 该对象逃离了方法的作用范围,则不安全
局部变量
局部变量的范围逃逸
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 36 37 38 39 40
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
@Slf4j(topic = "c.Test") public class Test {
public static void main(String[] args) { ThreadUnsafe threadUnsafe = new ThreadUnsafe(); for (int i = 0; i < 5; i++) { new Thread(() -> { threadUnsafe.method(5); }).start(); } } }
@Slf4j(topic = "c.Room") class ThreadUnsafe {
ArrayList<String> list = new ArrayList<>();
public void method(int loop) { for (int i = 0; i < loop; i++) { method2(); method3(); } }
private void method2() { list.add("1"); }
private void method3() { list.remove(0); }
}
|
上面可能会导致数组越界。因为很可能出现,重复写入下标为 0
的值,但是,移除了两次。
局部变量 暴漏引用
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 36 37 38 39 40
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
@Slf4j(topic = "c.Test") public class Test {
public static void main(String[] args) { ThreadSafe threadSafe = new ThreadSafe(); for (int i = 0; i < 5; i++) { new Thread(() -> { threadSafe.method(5); }).start(); } } }
@Slf4j(topic = "c.Room") class ThreadSafe {
public void method(int loop) { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < loop; i++) { method2(list); method3(list); } }
public void method2(ArrayList<String> list) { list.add("1"); }
public void method3(ArrayList<String> list) { list.remove(0); }
}
|
上面的代码的 list
,由于是局部变量所以,不会出错,但是,如果将引用暴漏,则有可能出错。
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
@Slf4j(topic = "c.Test") public class Test {
public static void main(String[] args) { ThreadUnSafe threadSafe = new ThreadUnSafe(); for (int i = 0; i < 500; i++) { new Thread(() -> { threadSafe.method(5); }).start(); } } }
@Slf4j(topic = "c.Room") class ThreadSafe {
public void method(int loop) { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < loop; i++) { method2(list); method3(list); } }
public void method2(ArrayList<String> list) { list.add("1"); }
public void method3(ArrayList<String> list) { list.remove(0); }
}
class ThreadUnSafe extends ThreadSafe { @Override public void method3(ArrayList<String> list) { new Thread(() -> { list.remove(0); }).start(); } }
|
对 method3
进行重写。然后再开启一个线程进行操作,则可能会出现不安全现象。
对于,不可变类,我们通常会设置为 final
,来让继承类不能更改方法,要是更改了,就有可能导致上面的问题。
线程安全类
java
中提供了一些线程安全类
- String
- Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent 包下的类
这里的线程安全,指的是,多个线程调用同一个实例的某个方法时,线程是安全的。
线程安全类组合不一定安全
1 2 3 4 5 6
| Hashtable table = new Hashtable();
// 线程一,线程二 if(table.get("key") == null){ table.put("key",value) }
|
get
和 put
操作组合在一起并不是原子操作。
不可变类的线程安全
String
、Integer
等,都是不可变类,因为内部的状态不可以改变,所以,他们的方法都是线程安全的。
但是,String
有 replace
、substring
这些方法,改变 String
。这是因为,每次调用这些方法,都会创建新的字符串。
spring boot 的常见不安全写法
有一种切面写法,比如,算出一个方法执行多长时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Aspect @Component public class MyAspect{ private long start = 0L;
@Befor("execution(* *(..))") public void before(){ start = System.nanoTime(); }
@After("execution(* *(..))") public void afer(){ long end = System.nanoTime(); System.out.println(end - start); } }
|
这是线程不安全的,因为,注解后,对于 spring
来说就是单例模式。单例模式下,就会出现线程不安全的情况。