线程安全之原子性问题

原子操作

定义: 原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执
行其中的一部分

i++便不是原子操作

public class Counter {
    volatile int i = 0;

    public int getI() {
        return i;
    }

    public void add() {
        i++;
    }
}

测试代码

public class Demo1_CounterTest {
    public static void main(String[] args) throws InterruptedException {
        final Counter ct = new Counter();

        for (int i = 0; i < 6; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        ct.add();
                    }
                    System.out.println("done...");
                }
            }).start();
        }

        Thread.sleep(6000L);
        System.out.println(ct.getI());
    }
}

预计应该是打印i的值为60000
而实际上
线程安全之原子性问题_第1张图片
i++ 便存在竞态条件

竞态条件

当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件.换句话说,就是正确的结果要取决于运气.
也就是说,如果两个线程竞争同一资源时,如果对资源的访问顺序敏感, 就称存在竞态条件

解决办法

1.同步锁

示例代码:

public class CounterLock {
    volatile int i = 0;

    Lock lock = new ReentrantLock();

    public void add() {
        lock.lock();
        i++;
        lock.unlock();
    }
}

或者

public class CounterSync {
    volatile int i = 0;

    public synchronized void add() {
        i++;
    }
}

2.CAS

CAS(Compare and swap) 机制

1.CAS 属于硬件同步原语,处理器提供的内存操作指令,保证原子性。
2.CAS 操作需要两个参数,一个旧值和一个目标值,修改前先比较旧值是否改变,如果没变,将新值赋给变量,否则不做改变。
3.JAVA中sun.misc.Unsafe类提供了CAS机制

Unsafe使用示例

public class CounterUnsafe {

    int i = 0;

    public int getI() {
        return i;
    }

    //Unsafe可以修改对象的属性、可以修改数组等等
    private static Unsafe unsafe = null;

    //一个偏移量,代表了要修改的字段
    private static long valueOffset;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            // 暴力反射
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);

            //指定要修改的字段
            Field iFiled = CounterUnsafe.class.getDeclaredField("i");
            valueOffset = unsafe.objectFieldOffset(iFiled);

        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public void add() {
        while (true) {
            if (unsafe.compareAndSwapInt(this, valueOffset, i, i + 1)) {
                return;
            }
        }
    }

}

CAS存在的问题

1.不能用于多个变量来实现原子操作
2.循环CAS自旋让所有线程都处于高频运行,争抢CPU执行.如果长时间不成功,则会带来很大的CPU资源消耗
3.ABA问题 (在多线程环境中,使用CAS时,如果一个线程对变量修改2次,第2次修改后的值和第1次修改前的值相同,那么可能就会出现ABA问题
)

J.U.C包内的原子操作封装类

常用的数据类型的原子操作封装类:
AtomicBoolean: 原子更新布尔类型
AtomicInteger:原子更新整型
AtomicLong:原子更新长整型

如果将上面示例代码的int i = 0 改为原子操作封装类

public class CounterAtomic {
    AtomicInteger at = new AtomicInteger(0);

    public void add(){
        // CAS操作进行累加
        at.getAndIncrement();
    }

    public int getValue(){
        return at.get();
    }
}

线程安全之原子性问题_第2张图片
则可以打印出和预计值一样的60000

数组的原子操作封装类
AtomicIntegerArray:原子更新整型数组里的元素。
AtomicLongArray:原子更新长整型数组里的元素。
AtomicReferenceArray:原子更新引用类型数组里的元素。

更新器
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
AtomicLongFieldUpdater:原子更新长整型字段的更新器。
AtomicReferenceFieldUpdater:原子更新引用类型里的字段。

使用示例:

public class Demo_AtomicIntegerFieldUpdater {
    // 新建AtomicIntegerFieldUpdater对象,需要指明是哪个类中的哪个字段
    private static AtomicIntegerFieldUpdater atom =
            AtomicIntegerFieldUpdater.newUpdater(User.class, "id");

    public static void main(String[] args) {
        User user = new User(100, 100);
        atom.addAndGet(user, 50);
        System.out.println("atom.addAndGet(user, 50)值变为:" + user);
    }
}

class User {
    
    volatile int id;
    volatile int age;
    
    public User(int id, int age) {
        this.id = id;
        this.age = age;

    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                '}';
    }
}

原子更新引用类型
AtomicReference:原子更新引用类型。
AtomicStampedReference:原子更新带有版本号的引用类型
AtomicMarkableReference:原子更新带有标记位的引用类型。

原子计数器
DoubleAdder、 LongAdder:计数器增强版,高并发下性能更好
DoubleAccumulator、 LongAccumulator: 是计数器的增强版,可自定义累加规则

使用示例:

public class Demo_LongAccumulator {

    public static void main(String[] args) throws InterruptedException {

        LongAccumulator accumulator = new LongAccumulator(
                (x,y)->{
                    // 自定义计数规则
                    System.out.println("x:" + x);
                    System.out.println("y:" + y);
                    return  x+y;
                },
                0L);

        for (int i = 0; i < 3; i++) {
            accumulator.accumulate(1);
        }

        System.out.println(accumulator.get());
    }
}

你可能感兴趣的:(多线程)