1.竞态条件与临界区
public class Demo{
public int i=0;
public void incr(){
i++;
}
}
多个线程访问了相同的资源,向这些资源做了写操作时,对执行顺序有要求。
- 临界区:incr方法内部就是临界区域,关键部分代码的多线程并发执行,会对执行结果产生影响。
- 竞态条件:可能发生再临界区域内的特殊条件。多线程执行incr方法中的I++关键代码时,产生了竞态条件。
- 共享资源
- 如果一段代码是线程安全的,则不它不包含竞态条件。只有当多个线程更新共享资源时,才会发生竞态条件。
- 栈封闭时,不会再线程之间共享的变量,都是线程安全的。
- 局部对象引用本身不共享,因为引用的对象存储再共享堆中。如果方法内创建的对象,只是再方法中传递,并且不对其他线程可用,那么也是线程安全的。
- 判断规则:如果创建、使用和处理资源,永远不会逃脱单个线程的控制,该资源的使用是线程安全的
- 不可变对象
public class Demo{
private int value=0;
public Demo(int value){
this.value=value;
}
public int getValue(){
return this.value;
}
}
创建不可变的共享对象来保证对象在线程间共享时不会被修改,从而实现线程安全。实例被创建,value变量就不能再被修改,这就是不可变性。
- 示例demo
public class LockDemo {
int i=0;
public void add() {
i++;
}
public static void main(String[] args) throws InterruptedException {
LockDemo lockDemo=new LockDemo();
for (int i = 0; i < 2; i++) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {
lockDemo.add();
}
}).start();
}
Thread.sleep(2000L);
System.out.println(lockDemo.i);
}
}
i++在多线程访问就产生了竞态条件,而且i++是三个步骤,读取,自增,赋值,即使用volatile修饰也只能保证可见性,但是多线程可能读取值相同,所以无法保证最终结果正确
- 原子操作定义
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可被打乱,也可以被切割而只执行其中的一部分(不可中断性)。将整个操作视作一个整体是原子性的核心特征。方式:CAS,锁,同步。
//同步小案例
public class LockDemo {
int i = 0;
public void add() {
synchronized (this) {
i++;
}
}
public static void main(String[] args) throws InterruptedException {
LockDemo lockDemo = new LockDemo();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
lockDemo.add();
}
}).start();
}
Thread.sleep(2000L);
System.out.println(lockDemo.i);
}
}
-
CAS机制
Compare and swap比较和交换。属于硬件同步原语,处理器提供了基本内存操作的原子性保证。CAS操作需要输入两个数值,一个旧值A(期望操作前的值)和一个新值B,在操作期间先比较下旧值有没有发生变化,如果没有发生变化,才会交换成新值,发生变化了则不交换。java提供了compareAndSwapInt()和compareAndSwapLong()等几个方法实现CAS。
1.png
//unsafe方式实现cas,但是这时只是说明原理,一般情况下不这么写,JUC提供有如下下一条代码
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class LockDemo {
//volatile也可没有
volatile int value = 0;
static Unsafe unsafe;//直接操作内存,修改对象,数组内存,很强大的API
private static long valueOffset;
static {
try {
//利用反射技术获取unsafe值
Field field=Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);//设置之后操作进行后续操作,不然报错
unsafe= (Unsafe) field.get(null);
valueOffset=unsafe.objectFieldOffset(LockDemo.class.getDeclaredField("value"));
} catch (Exception e) {
e.printStackTrace();
}
}
public void add() {
//CAS+循环重试,因为CAS可能失败
int current;
do {
//现在只是简单的数据自增,如果很多线程同时进行,而且此处是耗时操作,那么线程就会占用大量的CPU执行时间
current=unsafe.getIntVolatile(this,valueOffset);
} while (!unsafe.compareAndSwapInt(this,valueOffset,current,current+1));
}
public static void main(String[] args) throws InterruptedException {
LockDemo lockDemo = new LockDemo();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
lockDemo.add();
}
}).start();
}
Thread.sleep(2000L);
System.out.println(lockDemo.value);
}
}
- JUC包内的原子操作封装类
-
AtomicInteger:原子更新整型
-
AtomicLong:原子更新长整型
-
AtomicBoolean:原子更新布尔类型
-
AtomicIntegerArray:原子更新整型数组里的元素
-
AtomicLongArray:原子更新长整型数组里的元素
-
AtomicReferenceArray:原子更新引用类型数组里的元素
-
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
-
AtomicLongFieldUpdater:原子更新长整型的字段的更新器
-
AtomicReferenceFieldUpdater:原子更新引用类型的字段的更新器
-
AtomicReference:原子更新引用类型
-
AtomicStampedReference:原子更新带有版本号引用类型
-
AtomicmARKABLEReference:原子更新带有标记位引用类型
public class LockDemo {
AtomicInteger value = new AtomicInteger(0);
public void add() {
//其实内部就是上面unsafe的实现,可跟踪代码查看
value.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
LockDemo lockDemo = new LockDemo();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
lockDemo.add();
}
}).start();
}
Thread.sleep(2000L);
System.out.println(lockDemo.value);
}
}
- 1.8更新
- 更新器:DoubleAccumulator、LongAccumulator
- 计数器:DoubleAdder、LongAdder
- 计数器增强版,高并发下性能更好
频繁更细但不太频繁读取的汇总统计信息时使用,分成多个操作单元,不同线程更新不同的单元
只有需要汇总(sum()方法)的时候才计算所有单元的操作

public class LongAdderDemo {
public LongAdder longAdder=new LongAdder();
public static void main(String[] args) {
LongAdderDemo longAdderDemo=new LongAdderDemo();
longAdderDemo.testLongAddr();
}
public void testLongAddr() {
for (int i = 0; i < 3; i++) {
new Thread(()->{
long starttime=System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 2000) {
longAdder.increment();
}
long endtime=System.currentTimeMillis();
System.out.println(endtime-starttime+":"+longAdder.sum());
}).start();
}
}
}
如果写其他同步,或者1.8前cas写法,会发现现在的方法效率更高
//增强计算版本,可以自定义计算
//此处就是取得最大值输出999
public class LongAdderDemo {
public static void main(String[] args) throws InterruptedException {
LongAccumulator accumulator=new LongAccumulator(new LongBinaryOperator() {
@Override
public long applyAsLong(long left, long right) {
//返回最大值,这里就是自定义计算,根据自己需求可以修改
return left>right?left:right;
}
},0);
for (int i = 0; i < 1000; i++) {
int finall=i;
new Thread(()->{
//此处实际是执行上面的自定义操作
accumulator.accumulate(finall);
}).start();
}
Thread.sleep(2000L);
System.out.println(accumulator.longValue());//999
}
}
- CAS的三个问题
- 循环+CAS:自旋的实现让所有线程都处于高频运行,争抢CPU执行时间的状态。如果操作长时间不成功,会带来很大的CPU资源消耗
- 仅针对单个变量的操作,不能用于多个变量来实现原子操作
-
ABA问题:如果只是简单的数据加减等,ABA实际没有任何问题,但是如果针对复杂操作,如果通过判断变量出现ABA问题会导致很多问题;甚至有些是监听变化来触发操作,虽然可能多线程,但是实际上可能出现单CPU执行,如果单CPU则即使多线程同一时间也只有一个执行,如果比较巧合的话,刚好需要监听的线程因为ABA不知道实际被监听的已经变化了,则ABA问题也可能导致没法触发
3.png
网友评论