线程安全性(一)
参考
线程安全性总结
CountDownLatch
CountDownLatch
可以阻塞线程并保证线程在满足某种特定条件下继续执行 (T从3减少1后T_A才能继续执行)
适合线程执行完之后进行其他处理
CountDownLatch.png
Semaphore
阻塞进程,控制同一时间的并发量 适合控制同时并发的线程数

线程安全性
定义:当多个线程访问某个类时,不管运行时采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么久称这个类时线程安全的
线程安全性的体现
- 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作
- 可见性:一个线程对主内存的修改可以及时的被其他线程观察到
- 有序性:一个线程观察到其他线程中的指令执行顺序,由于指令 重排序的存在,该观察结果一般杂乱无序
Atomic
实现时使用了unsafe
的类
CAS的实现原理

count.incrementAndGet();
操作的底层是getAndAddInt(Object var1, Long var2, int var4)
第一个参数是传递进来的对象, 比如count.IncrementAndGet()
中的count
对象, 第二个参数是传递进来的值,比如说要执行2+1的操作,var2就是2, var4就是1,compareAndSwapInt(var1,var2,var5,var5+var4)
中 var5是通过调用底层this.getIntVolatile(var1,var2);
得到的底层的值,如果没有别的线程来处理count
这个变量,它正常返回的值应该是2,因此,compareAndSwapInt()
中的var1
就是count
这个对象,var2
就是当前值2,var5
是从底层传过来的2,var5+var4
就是从底层传回来的值加上想增加的量1
compareAndSwapInt()
达到的效果: 如果当前的值和底层的值相同的话就更新值 (底层相当于是主内存, count是工作内存)
在一个死循环内不断刷新当前值,如果当前竞争不激烈的话修改成功的概率很高,否则的话修改失败的概率很高

AtomicLong
比LongAdder
更准确,在并发度不高的情况下使用较好
AtomicIntegerFileUpdater<>
指定更新某个类的某个字段的值,而这个字段必须是通过volatile
修饰,同时不能是static
的字段才可以
example:
public class AtomicExample5 {
private static AtomicIntegerFieldUpdater<AtomicExample5> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");
public volatile int count = 100;
public int getCount(){
return count;
}
private static AtomicExample5 example5 = new AtomicExample5();
public static void main(String[] args) {
if (updater.compareAndSet(example5, 100, 120)) {
System.out.println("update success 1 "+ example5.getCount());
}
if (updater.compareAndSet(example5, 100, 120)) {
System.out.println("update success 2 "+ example5.getCount());
} else {
System.out.println("update failed "+ example5.getCount());
}
}
}
Output:
update success 1 120
update failed 120
原因:第一次更新时已经将结果由100更新到了120,所以第二次会失败
CAS的ABA问题及AtomicStampReference
解决
问题定义: 是指在CAS操作的时候,其他线程将变量的值A变成了B但是又改回了A,而本线程在进行比较的时候发现变量A没有变,于是 CAS就进行数据更新操作,但这与设计思想不符合,因此ABA问题的解决思路是对变量进行更新的时候把变量的版本号+1,这个时候只要变量被修改过,变量的版本号就发生变化
AtomicLongArray:
多了对索引值进行更新
AtomicBoolean:
@ThreadSafe
public class AtomicExample6 {
private static AtomicBoolean isHappened = new AtomicBoolean(false);
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程总数
public static int threadTotal = 200;
// public static AtomicLong count = new AtomicLong(0);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i <clientTotal ; i++) {
executorService.execute(()->{
try {
semaphore.acquire(); // 同时200个线程进行操作
test();
semaphore.release();
} catch (Exception e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await(); // 所有线程执行任务完毕才打印count
executorService.shutdown();
System.out.println("isHappened "+ isHappened.get());
}
private static void test(){
if(isHappened.compareAndSet(false,true)){
System.out.println("execute");
}
}
}