1. 线程安全性
1.1 定义
- 线程安全:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
- 线程不安全:如果一个类对象同时可以被多个线程访问,如果不做同步处理,可能表现出线程不安全现象(抛出异常、逻辑错误)。
1.2 原子性
1.2.1 定义
提供了互斥访问,同一时刻(时间段)只能有一个线程对它进行操作。避免脏读:数据读取过程中发生了写操作,数据发生了改变。
1.2.2 实现方法
1.2.2.1 synchronized
同一对象同时只能被 synchronized 一次。
- 修饰动态方法
- 修饰静态方法
- 修饰代码块,synchronized(this){}
- 修饰代码块,synchronized(引用类型){}
- 修饰代码块,synchronized(类.class){}
1.2.2.2 ReentrantLock
1.2.2.3 ReentrantReadWriteLock
1.2.2.4 StampedLock
1.2.2.4.1 功能
读写锁虽然实现了读和读的并发,但读写之间是冲突的。
1.2.2.4.2 使用步骤
- 主线程使用
new StampedLock()
方法创建 StampedLock 对象。 - 写前使用
stampedLock.writeLock()
方法加写锁。写锁不会阻塞 tryOptimisticRead() 方法,但会阻塞 readLock()方法,改变 stamp。 - 读前使用
long stamp = s1.tryOptimisticRead()
方法获取版本号,读取数据。 - 确定版本号是否合法(一致),如果是则直接返回结果。
- 如果不是,则将乐观锁升级为悲观锁,如果对象正在被修改,则本线程挂起。读取,释放读锁,返回结果。
1.2.2.5 CAS
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// var1 this
// var2 valueOffset
// var4 1
public final int getAndAddInt(Object var1, long var2, int var4) {
// var5 变量当前值
int var5;
//如果变量当前值
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
if (var4 == var1.get(var1,var2)) {
var4 = var5
return true;
} else {
return false;
}
1.3 可见性
1.3.1 定义
一个线程对主内存的修改可以及时被其他线程观察到,例如取款和余额查询业务。
1.3.2 实现方法
如果没有 violate ,可能当前线程看似完成了赋值操作(即运行到下一步),但数据没同步进主存。
1.4 有序性
1.4.1 定义
一个线程观察其他线程的执行,由于指令重排序的存在,该观察结果一般杂乱无序。
1.4.2 实现方法
1.4.2.1 happens-before 原则
- 同一线程内保证顺序性。
- 解锁(Lock、synchronized)操作 happened-before 加锁操作。
- violate 关键字修饰的变量在逻辑上但写操作,happened-before 随后对该变量的读操作。
- 实现方式:内存屏障。
2. 线程安全策略
2.1 不可变对象
2.1.1 final 关键字
- final 修饰的类不允许继承。
- final 修饰的方法不允许重写。
- final 修饰的变量在定义时必须初始化,后期不能修改,并且该变量在子类中也不能被修改,final 修饰的入参在函数中不能被修改,但 final 修饰对象的成员变量能被修改,修饰的容器内的元素能被修改。
- 针对引用对象,使用 final 定义对象和其成员变量保证不变性。
- 针对容器对象,有两种方式保证不变性:
2.2 线程封闭
2.2.1 ThreadLocal
2.2.1.1 定义
将对象封装到一个线程中,只有该线程能访问该对象。
2.2.1.2 实现步骤
public class Thread implements Runnable {
......
ThreadLocal.ThreadLocalMap threadLocals = null;
......
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
2.2.1.3 使用步骤
- 当Controller被调用时,调用RequestHolder的getId()静态方法,getId方法调用ThreadLocal<Long>对象的get方法。
- 定义Filter,调用RequestHolder的add方法将当前线程的id添加进ThreadLocal。
- 定义Interceptor,在完成请求响应后清除数据。
- 注入 Filter 和Interceptor。
- 好处:避免每次都从Request中取用户信息。
2.3 线程安全对象
2.3.1 定义
为了省略对于线程不安全类的并发访问需要进行的同步处理。
2.3.2 实现方法
2.3.2.1
- StringBuilder:线程不安全。
- StringBuffer:线程安全,方法使用synchronized关键字。
2.3.2.2
- SimpleDateFormat:线程不安全。
- DateTimeFormatter:线程安全,joda-time包。
2.3.2.3 AutomicXXX
线程安全,使用 CAS 实现。
2.4 被守护对象
2.4.1 定义
(lock、synchorized),对于共享变量的修改和读取使用锁或同步机制。
2.4.2 实现方法
参考本文章节 1.2.2。
参考资料
- Java并发编程与高并发解决方案
- Java8 读写锁的改进:StampedLock
- 并发策略-CAS算法
网友评论