2.4.2-线程安全性
优质
小牛编辑
134浏览
2023-12-01
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对象的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算法