6.线程安全的原子操作

线程安全的分辨

如果一段代码是线程安全的,那么它不存在竞态条件。只有当多个线程更新共享资源时,才会发生竞态条件。

原子性问题的演示

// 两个线程,对 i 变量进行递增操作
public class LockDemo {
    
    
	//volatile只能保证线程的可见性,不能保证线程的原子性.
    volatile int i = 0;

    public void add() {
    
    
         i++;// 三个步骤
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        LockDemo ld = new LockDemo();

        for (int i = 0; i < 2; i++) {
    
    
            new Thread(() -> {
    
    
                for (int j = 0; j < 10000; j++) {
    
    
                    ld.add();
                }
            }).start();
        }
        Thread.sleep(2000L);
        System.out.println(ld.i);
    }
}

CAS方式去解决

public class LockDemo1 {
    
    
    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);

            // 获取到 value 属性偏移量(用于定于value属性在内存中的具体地址)
            valueOffset = unsafe.objectFieldOffset(LockDemo1.class
                    .getDeclaredField("value"));

        } catch (Exception ex) {
    
    
            ex.printStackTrace();
        }
    }

    public void add() {
    
    
        // TODO xx00
        // i++;// JAVA 层面三个步骤
        // 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 {
    
    
        LockDemo1 ld = new LockDemo1();

        for (int i = 0; i < 2; i++) {
    
    
            new Thread(() -> {
    
    
                for (int j = 0; j < 10000; j++) {
    
    
                    ld.add();
                }
            }).start();
        }
        Thread.sleep(2000L);
        System.out.println(ld.value);
    }
}

使用atomic工具类

// 两个线程,对 i 变量进行递增操作
public class LockDemo {
    
    
    // volatile int i = 0;
    AtomicInteger i = new AtomicInteger(0);


    public void add() {
    
    
        // TODO xx00
        // i++;// 三个步骤
        i.incrementAndGet();
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        LockDemo ld = new LockDemo();

        for (int i = 0; i < 2; i++) {
    
    
            new Thread(() -> {
    
    
                for (int j = 0; j < 10000; j++) {
    
    
                    ld.add();
                }
            }).start();
        }
        Thread.sleep(2000L);
        System.out.println(ld.i);
    }
}

Jdk新的计数器

// 测试用例: 同时运行2秒,检查谁的次数最多
public class LongAdderDemo {
    
    
    private long count = 0;

    // 同步代码块的方式
    public void testSync() throws InterruptedException {
    
    
        for (int i = 0; i < 3; i++) {
    
    
            new Thread(() -> {
    
    
                long starttime = System.currentTimeMillis();
                while (System.currentTimeMillis() - starttime < 2000) {
    
     // 运行两秒
                    synchronized (this) {
    
    
                        ++count;
                    }
                }
                long endtime = System.currentTimeMillis();
                System.out.println("SyncThread spend:" + (endtime - starttime) + "ms" + " v" + count);
            }).start();
        }
    }

    // Atomic方式
    private AtomicLong acount = new AtomicLong(0L);

    public void testAtomic() throws InterruptedException {
    
    
        for (int i = 0; i < 3; i++) {
    
    
            new Thread(() -> {
    
    
                long starttime = System.currentTimeMillis();
                while (System.currentTimeMillis() - starttime < 2000) {
    
     // 运行两秒
                    acount.incrementAndGet(); // acount++;
                }
                long endtime = System.currentTimeMillis();
                System.out.println("AtomicThread spend:" + (endtime - starttime) + "ms" + " v-" + acount.incrementAndGet());
            }).start();
        }
    }

    // LongAdder 方式
    private LongAdder lacount = new LongAdder();
    public void testLongAdder() throws InterruptedException {
    
    
        for (int i = 0; i < 3; i++) {
    
    
            new Thread(() -> {
    
    
                long starttime = System.currentTimeMillis();
                while (System.currentTimeMillis() - starttime < 2000) {
    
     // 运行两秒
                    lacount.increment();
                }
                long endtime = System.currentTimeMillis();
                System.out.println("LongAdderThread spend:" + (endtime - starttime) + "ms" + " v-" + lacount.sum());
            }).start();
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        LongAdderDemo demo = new LongAdderDemo();
        demo.testSync();
        demo.testAtomic();
        demo.testLongAdder();
    }
}
  • 执行结果
SyncThread spend:2000ms v21103457
SyncThread spend:2000ms v21103457
SyncThread spend:2000ms v21103457
AtomicThread spend:2000ms v-87109275
LongAdderThread spend:2000ms v-194372483
AtomicThread spend:2000ms v-87919296
LongAdderThread spend:2000ms v-194854387
AtomicThread spend:2000ms v-88120307
LongAdderThread spend:2000ms v-197359712

LongAdder的增强版

// LongAdder增强版,处理累加之外,可以自行定义其他计算
public class LongAccumulatorDemo {
    
    
    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);

        // 1000个线程
        for (int i = 0; i < 1000; i++) {
    
    
            int finalI = i;
            new Thread(() -> {
    
    
                accumulator.accumulate(finalI); // 此处实际就是执行上面定义的操作
            }).start();
        }

        Thread.sleep(2000L);
        System.out.println(accumulator.longValue()); // 打印出结果
    }

}

ABA问题的案例分析与解决方法

  • ABA问题
// aba 问题
// 重复操作 / 过时操作。
public class AbaDemo {
    
    
    // 模拟充值
    // 有3个线程在给用户充值,当用户余额少于20时,就给用户充值20元。
    // 有100个线程在消费,每次消费10元。用户初始有9元
    static AtomicInteger money = new AtomicInteger(19);

    public static void main(String[] args) throws InterruptedException {
    
    
        for (int i = 0; i < 3; i++) {
    
    
            new Thread(() -> {
    
    
                int current;
                do {
    
    
                    current = money.get();// 获取当前内存中的值
                } while (!money.compareAndSet(current, current + 20)); // CAS
            }).start();
        }

        new Thread(() -> {
    
    

            for (int i = 0; i < 100; i++) {
    
    
                while (true) {
    
    
                    Integer m = money.get();
                    if (m > 10) {
    
    
                        if (money.compareAndSet(m, m - 10)) {
    
    
                            System.out.println("消费10元,余额:" + money.get());
                            break;
                        }
                    } else {
    
    
                        break;
                    }
                }
                try {
    
    
                    Thread.sleep(100);
                } catch (Exception e) {
    
    
                    // TODO: handle exception
                }
            }

        }).start();
    }
}
  • 展示结果
消费10元,余额:69
消费10元,余额:59
消费10元,余额:49
消费10元,余额:39
消费10元,余额:29
消费10元,余额:19
消费10元,余额:9
  • ABA问题的解决
public class AbaDemo1 {
    
    
    // 模拟充值
    // 有3个线程在给用户充值,当用户余额少于20时,就给用户充值20元。
    // 有100个线程在消费,每次消费10元。用户初始有9元
    static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 0);

    public static void main(String[] args) throws InterruptedException {
    
    
        for (int i = 0; i < 3; i++) {
    
    
            final int timestamp = money.getStamp();
            new Thread(() -> {
    
    
                while (true) {
    
    
                    while (true) {
    
    
                        Integer m = money.getReference();
                        if (m < 20) {
    
    
                            if (money.compareAndSet(m, m + 20, timestamp,
                                    timestamp + 1)) {
    
    
                                System.out.println("充值成功,余额:"
                                        + money.getReference());
                                break;
                            }
                        } else {
    
    
                            break;
                        }
                    }
                }
            }).start();
        }

        new Thread(() -> {
    
    

            for (int i = 0; i < 100; i++) {
    
    
                while (true) {
    
    
                    int timestamp = money.getStamp();
                    Integer m = money.getReference();
                    if (m > 10) {
    
    
                        if (money.compareAndSet(m, m - 10, timestamp,
                                timestamp + 1)) {
    
    
                            System.out.println("消费10元,余额:"
                                    + money.getReference());
                            break;
                        }
                    } else {
    
    
                        break;
                    }
                }
                try {
    
    
                    Thread.sleep(100);
                } catch (Exception e) {
    
    
                    // TODO: handle exception
                }
            }

        }).start();
    }
}
  • 案例结果
充值成功,余额:39
消费10元,余额:29
消费10元,余额:19
消费10元,余额:9

猜你喜欢

转载自blog.csdn.net/xuehongyou/article/details/109394505