Java并发编程的共享模型之无锁

共享模型之无锁

  • CAS volatile
  • 原子整数
  • 原子引用
  • 原子累加器
  • Unsafe

1 问题提出

有如下需求,保证 account.withdraw 取款方法的线程安全
        package cn.itcast;
        import java.util.ArrayList;
        import java.util.List;
        interface Account {
            // 获取余额
            Integer getBalance();
            // 取款
            void withdraw(Integer amount);
            /**
             * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
             * 如果初始余额为 10000 那么正确的结果应当是 0
             */
            static void demo(Account account) {
                List<Thread> ts = new ArrayList<>();
                long start = System.nanoTime();
                for (int i = 0; i < 1000; i++) {
                    ts.add(new Thread(() -> {
                        account.withdraw(10);
                    }));
                }
                ts.forEach(Thread::start);
                ts.forEach(t -> {
                    try {
                        t.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                long end = System.nanoTime();
                System.out.println(account.getBalance()
                        + " cost: " + (end-start)/1000_000 + " ms");
            }
        }
原有实现并不是线程安全的
 
        class AccountUnsafe implements Account {
            private Integer balance;
            public AccountUnsafe(Integer balance) {
                this.balance = balance;
            }
            @Override
            public Integer getBalance() {
                return balance;
            }
            @Override
            public void withdraw(Integer amount) {
                balance -= amount;
            }
        }
执行测试代码
        public static void main(String[] args) {
            Account.demo(new AccountUnsafe(10000));
        }

无锁形式保证安全:


        class AccountSafe implements Account {
            private AtomicInteger balance;
            public AccountSafe(Integer balance) {
                this.balance = new AtomicInteger(balance);
            }
            @Override
            public Integer getBalance() {
                return balance.get();
            }
            @Override
            public void withdraw(Integer amount) {
                while (true) {
                    int prev = balance.get();
                    int next = prev - amount;
                    if (balance.compareAndSet(prev, next)) {
                        break;
                    }
                }
                // 可以简化为下面的方法
                // balance.addAndGet(-1 * amount);
            }
        }

CAS使用链接

原子整数

J.U.C 并发包提供了:
  • AtomicBoolean
  • AtomicInteger
  • AtomicLong
AtomicInteger 为例

        AtomicInteger i = new AtomicInteger(0);
        // 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++
        System.out.println(i.getAndIncrement());
        // 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++i
        System.out.println(i.incrementAndGet());
        // 自减并获取(i = 2, 结果 i = 1, 返回 1),类似于 --i
        System.out.println(i.decrementAndGet());
        // 获取并自减(i = 1, 结果 i = 0, 返回 1),类似于 i--
        System.out.println(i.getAndDecrement());
        // 获取并加值(i = 0, 结果 i = 5, 返回 0)
        System.out.println(i.getAndAdd(5));
        // 加值并获取(i = 5, 结果 i = 0, 返回 0)
        System.out.println(i.addAndGet(-5));
        // 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        System.out.println(i.getAndUpdate(p -> p - 2));
        // 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        System.out.println(i.updateAndGet(p -> p + 2));
        // 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        // getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
        // getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
        System.out.println(i.getAndAccumulate(10, (p, x) -> p + x));
        System.out.println(i);//10
        // 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
        // 其中函数中的操作能保证原子,但函数需要无副作用
        System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));

原子引用

为什么需要原子引用类型?
  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference
有如下方法:
        public interface DecimalAccount {
            // 获取余额
            BigDecimal getBalance(); 
            // 取款
            void withdraw(BigDecimal amount);
            /**
             * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
             * 如果初始余额为 10000 那么正确的结果应当是 0
             */
            static void demo(DecimalAccount account) {
                List<Thread> ts = new ArrayList<>();
                for (int i = 0; i < 1000; i++) {
                    ts.add(new Thread(() -> {
                        account.withdraw(BigDecimal.TEN);
                    }));
                }
                ts.forEach(Thread::start);
                ts.forEach(t -> {
                    try {
                        t.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                System.out.println(account.getBalance());
            }
        }
试着提供不同的 DecimalAccount 实现,实现安全的取款操作
 
 

不安全实现

        class DecimalAccountUnsafe implements DecimalAccount {
            BigDecimal balance;
            public DecimalAccountUnsafe(BigDecimal balance) {
                this.balance = balance;
            }
            @Override
            public BigDecimal getBalance() {
                return balance;
            }
            @Override
            public void withdraw(BigDecimal amount) {
                BigDecimal balance = this.getBalance();
                this.balance = balance.subtract(amount);
            }
        }

安全实现-使用锁

        class DecimalAccountSafeLock implements DecimalAccount {
            private final Object lock = new Object();
            BigDecimal balance;
            public DecimalAccountSafeLock(BigDecimal balance) {
                this.balance = balance;
            }
            @Override
            public BigDecimal getBalance() {
                return balance;
            }
            @Override
            public void withdraw(BigDecimal amount) {
                synchronized (lock) {
                    BigDecimal balance = this.getBalance();
                    this.balance = balance.subtract(amount);
                }
            }
        }

安全实现-使用 CAS

        class DecimalAccountSafeCas implements DecimalAccount {
            AtomicReference<BigDecimal> ref;
            public DecimalAccountSafeCas(BigDecimal balance) {
                ref = new AtomicReference<>(balance);
            }
            @Override
            public BigDecimal getBalance() {
                return ref.get();
            }
            @Override
            public void withdraw(BigDecimal amount) {
                while (true) {
                    BigDecimal prev = ref.get();
                    BigDecimal next = prev.subtract(amount);
                    if (ref.compareAndSet(prev, next)) {
                        break;
                    }
                }
            }
        }

测试:

DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal("10000")));
DecimalAccount.demo(new DecimalAccountSafeLock(new BigDecimal("10000")));
DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal("10000")));

测试结果:

4310 cost: 425 ms
0 cost: 285 ms
0 cost: 274 ms

ABA 问题及解决

ABA 问题

        static AtomicReference<String> ref = new AtomicReference<>("A");
        public static void main(String[] args) throws InterruptedException {
            log.debug("main start...");
            // 获取值 A
            // 这个共享变量被它线程修改过?
            String prev = ref.get();
            other();
            sleep(1);
            // 尝试改为 C
            log.debug("change A->C {}", ref.compareAndSet(prev, "C"));
        }
        private static void other() {
            new Thread(() -> {
                log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
            }, "t1").start();
            sleep(0.5);
            new Thread(() -> {
                log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
            }, "t2").start();
        }

输出:

11:29:52.325 c.Test36 [main] - main start... 
11:29:52.379 c.Test36 [t1] - change A->B true 
11:29:52.879 c.Test36 [t2] - change B->A true 
11:29:53.880 c.Test36 [main] - change A->C true
主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,如果主线程
希望:
只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号

AtomicStampedReference


        static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
        public static void main(String[] args) throws InterruptedException {
            log.debug("main start...");
            // 获取值 A
            String prev = ref.getReference();
            // 获取版本号
            int stamp = ref.getStamp();
            log.debug("版本 {}", stamp);
            // 如果中间有其它线程干扰,发生了 ABA 现象
            other();
            sleep(1);
            // 尝试改为 C
            log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
        }
        private static void other() {
            new Thread(() -> {
                log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
                        ref.getStamp(), ref.getStamp() + 1));
                log.debug("更新版本为 {}", ref.getStamp());
            }, "t1").start();
            sleep(0.5);
            new Thread(() -> {
                log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
                        ref.getStamp(), ref.getStamp() + 1));
                log.debug("更新版本为 {}", ref.getStamp());
            }, "t2").start();
        }
输出为
15:41:34.891 c.Test36 [main] - main start... 
15:41:34.894 c.Test36 [main] - 版本 0 
15:41:34.956 c.Test36 [t1] - change A->B true 
15:41:34.956 c.Test36 [t1] - 更新版本为 1 
15:41:35.457 c.Test36 [t2] - change B->A true 
15:41:35.457 c.Test36 [t2] - 更新版本为 2 
15:41:36.457 c.Test36 [main] - change A->C false
AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: A -> B -> A -> C ,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。
但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了
AtomicMarkableReference

AtomicMarkableReference

        class GarbageBag {
            String desc;
            public GarbageBag(String desc) {
                this.desc = desc;
            }
            public void setDesc(String desc) {
                this.desc = desc; }
            @Override
            public String toString() {
                return super.toString() + " " + desc;
            }
        }

 

 


        @Slf4j
        public class TestABAAtomicMarkableReference {
            public static void main(String[] args) throws InterruptedException {
                GarbageBag bag = new GarbageBag("装满了垃圾");
                // 参数2 mark 可以看作一个标记,表示垃圾袋满了
                AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);
                log.debug("主线程 start...");
                GarbageBag prev = ref.getReference();
                log.debug(prev.toString());
                new Thread(() -> {
                    log.debug("打扫卫生的线程 start...");
                    bag.setDesc("空垃圾袋");
                    while (!ref.compareAndSet(bag, bag, true, false)) {}
                    log.debug(bag.toString());
                }).start();
                Thread.sleep(1000);
                log.debug("主线程想换一只新垃圾袋?");
                boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾袋"), true, false);
                log.debug("换了么?" + success);
                log.debug(ref.getReference().toString());
            }
        }

输出:

2019-10-13 15:30:09.264 [main] 主线程 start... 
2019-10-13 15:30:09.270 [main] cn.itcast.GarbageBag@5f0fd5a0 装满了垃圾
2019-10-13 15:30:09.293 [Thread-1] 打扫卫生的线程 start... 
2019-10-13 15:30:09.294 [Thread-1] cn.itcast.GarbageBag@5f0fd5a0 空垃圾袋
2019-10-13 15:30:10.294 [main] 主线程想换一只新垃圾袋?
2019-10-13 15:30:10.294 [main] 换了么?false 
2019-10-13 15:30:10.294 [main] cn.itcast.GarbageBag@5f0fd5a0 空垃圾袋
6.5 原子数组
  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray
有如下方法
        /**
        参数1,提供数组、可以是线程不安全数组或线程安全数组
        参数2,获取数组长度的方法
        参数3,自增方法,回传 array, index
        参数4,打印数组的方法
        */
        // supplier 提供者 无中生有 ()->结果
        // function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
        // consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)->
        private static <T> void demo(
                Supplier<T> arraySupplier,
                Function<T, Integer> lengthFun,
                BiConsumer<T, Integer> putConsumer,
                Consumer<T> printConsumer ) {
            List<Thread> ts = new ArrayList<>();
            T array = arraySupplier.get();
            int length = lengthFun.apply(array);
            for (int i = 0; i < length; i++) {
                // 每个线程对数组作 10000 次操作
                ts.add(new Thread(() -> {
                    for (int j = 0; j < 10000; j++) {
                        putConsumer.accept(array, j%length);
                    }
                }));
            }
            ts.forEach(t -> t.start()); // 启动所有线程
            ts.forEach(t -> {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }); // 等所有线程结束
            printConsumer.accept(array);
        }

测试:

        //不安全
        demo(
                ()->new int[10],
                (array)->array.length,
                (array, index) -> array[index]++,
                array-> System.out.println(Arrays.toString(array))
        );

        //原子安全
        demo(
                ()-> new AtomicIntegerArray(10),
                (array) -> array.length(),
                (array, index) -> array.getAndIncrement(index),
                array -> System.out.println(array)
        );

结果:

SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
[8870, 8868, 8880, 8864, 8865, 8858, 8830, 8859, 8810, 8866]
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]

 字段更新器

  • AtomicReferenceFieldUpdater // 字段
  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
利用字段更新器,可以针对对象的某个域( Field )进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现 异常
Exception in thread "main" java . lang . IllegalArgumentException : Must be volatile type
 
    public static void main(String[] args) throws InterruptedException{

        Student stu = new Student();
        AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");

        updater.compareAndSet(stu,null,"张三");
        System.out.println(stu);

}

Student类:

    static class Student{
        volatile String name;

        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

原子累加器


        private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
            T adder = adderSupplier.get();
            long start = System.nanoTime();
            List<Thread> ts = new ArrayList<>();
            // 4 个线程,每人累加 50 万
            for (int i = 0; i < 40; i++) {
                ts.add(new Thread(() -> {
                    for (int j = 0; j < 500000; j++) {
                        action.accept(adder);
                    }
                }));
            }
            ts.forEach(t -> t.start());
            ts.forEach(t -> {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            long end = System.nanoTime();
            System.out.println(adder + " cost:" + (end - start)/1000_000);
        }

测试:

for (int i = 0; i < 5; i++) {
 demo(() -> new LongAdder(), adder -> adder.increment());
}
for (int i = 0; i < 5; i++) {
 demo(() -> new AtomicLong(), adder -> adder.getAndIncrement());
}

输出:

1000000 cost:43 
1000000 cost:9 
1000000 cost:7 
1000000 cost:7 
1000000 cost:7 
1000000 cost:31 
1000000 cost:27 
1000000 cost:28 
1000000 cost:24 
1000000 cost:22

Unsafe

 
概述
Unsafe 对象提供了非常底层的,操作内存、线程的方法, Unsafe 对象不能直接调用,只能通过反射获得
 
        public class UnsafeAccessor {
            static Unsafe unsafe;
            static {
                try {
                    Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
                    theUnsafe.setAccessible(true);
                    unsafe = (Unsafe) theUnsafe.get(null);
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    throw new Error(e);
                }
            }
            static Unsafe getUnsafe() {
                return unsafe;
            }
        }

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值