共享模型之无锁

1 问题

一个账户取钱的例子

interface Account {
    // 获取余额
    Integer getBalance();

    // 取款
    void withdraw(Integer amount);

    // 测试方法
    static void demo(Account account) {
        List<Thread> threads = new ArrayList<>();

        // 1000个线程
        for (int i = 0; i < 10000; i++) {
            threads.add(new Thread(() -> {
                account.withdraw(10);
            }));
        }

        long start = System.nanoTime();

        threads.forEach(Thread::start);

        threads.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        long end = System.nanoTime();

        // 打印执行后的余额 和 花费时间
        System.out.println("balance: " + account.getBalance());
        System.out.println("cost: " + (end - start) / 1000_000 + "ms");
    }
}

1.1 不安全的实现

/**
 * 不使用锁安全的实现
 */
class AccountSafeNoLock implements Account {

    private Integer balance;

    public AccountSafeNoLock(Integer balance) {
        this.balance = balance;
    }

    @Override
    public Integer getBalance() {
        return this.balance;
    }

    @Override
    public synchronized void withdraw(Integer amount) {
        this.balance -= amount;
    }
}

1.2 有锁安全的实现

/**
 * 使用锁安全的实现
 */
class AccountSafeNoLock implements Account {

    private Integer balance;

    public AccountSafeNoLock(Integer balance) {
        this.balance = balance;
    }

    @Override
    public Integer getBalance() {
        return this.balance;
    }

    @Override
    public synchronized void withdraw(Integer amount) {
        this.balance -= amount;
    }
}

1.2 无锁安全的实现

/**
 * 不使用锁安全的实现
 */
class AccountSafeUseLock implements Account {

    private final AtomicInteger balance;

    public AccountSafeUseLock(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;
        }
    }
}

2 CAS 与 volatile

    @Override
    public void withdraw(Integer amount) {
        while (true) {
            int prev = balance.get();
            int next = prev - amount;
            // 比较并设值
            if (balance.compareAndSet(prev, next)) break;
        }
    }

2.1 工作方式

在这里插入图片描述
注意
CAS底层是 lock cmpxchg指令(X86架构),在单核CPU和多核CPU下都能抱枕【比较-交换】的原子性

  • 在多核状态下,某个核执行到带lock的指令时,CPU会让总线锁住,当 这个核把指令执行完毕,再离开总线。这个过程不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的。

2.2 volatile

源码上共享变量是被 volatile 修饰的,为了保证该变量的可见性

public class AtomicInteger extends Number implements java.io.Serializable {
    //...
    private volatile int value;
    //...
}

2.3 无锁效率

  • 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而synchronized会让没有获得锁的线程进入阻塞状态,发生上下文切换。
  • 无锁情况,线程要保持高速运行需要额外CPU的支持,如果是单核CPU,虽然不会进入阻塞,由于没有时间片,任然会从运行状态进入可运行状态,还是会导致上下文切换。

2.4 CAS特点

  • 适合线程数量少,多核CPU的情况下
  • CAS是基于乐观锁的思想:乐观的认为不会认为有其他线程来修改共享变量,改了我就重试
  • synchronized基于悲观锁的思想:悲观的认为共享资源一定会被人改,我修改的适合必上锁,我改完了被人才能动
  • CAS体现的是无锁并发,无阻塞并发
    • 因为没有使用synchronized,所以线程不会陷入阻塞,大大提升了工作效率
    • 如果竞争激烈,重试会频繁发生,反而效率更低

3 Atomic

3.1 原子整数

以 AtomicInteger 为例
在这里插入图片描述

AtomicInteger ai = new AtomicInteger(0);
 // 获取 ai 值: 0
 int i = ai.get();
 System.out.println("i: " + i);
 System.out.println("ai: " + ai.get());
 System.out.println("------------------");
 // i = ++ai : 1
 i = ai.incrementAndGet();
 System.out.println("i: " + i);
 System.out.println("ai: " + ai.get());
 System.out.println("------------------");
 // i = ai++ : 1
 i = ai.getAndIncrement();
 System.out.println("i: " + i);
 System.out.println("ai: " + ai.get());
 System.out.println("------------------");
 // i = ai-- : 2
 i = ai.getAndDecrement();
 System.out.println("i: " + i);
 System.out.println("ai: " + ai.get());
 System.out.println("------------------");
 // i = --ai : 0
 i = ai.decrementAndGet();
 System.out.println("i: " + i);
 System.out.println("ai: " + ai.get());
 System.out.println("------------------");

 // i = ai
 // ai += 8
 i = ai.getAndAdd(8);
 System.out.println("i: " + i);
 System.out.println("ai: " + ai.get());
 System.out.println("------------------");

 // ai += 8
 // i = ai
 i = ai.addAndGet(8);
 System.out.println("i: " + i);
 System.out.println("ai: " + ai.get());
 System.out.println("------------------");

 // ai = ai * 10
 // i = ai
 i = ai.updateAndGet(x -> x * 10);
 System.out.println("i: " + i);
 System.out.println("ai: " + ai.get());
 System.out.println("------------------");

 // i = ai
 // ai = ai * 10
 i = ai.getAndUpdate(x -> x / 10);
 System.out.println("i: " + i);
 System.out.println("ai: " + ai.get());
 System.out.println("------------------");

3.2 原子引用

简单来说就是里面 Atomic 存的是对象引用

3.2.1 AtomicReference

有一个狗对象,狗对象有一个属性是骨头,1000个人(线程)每人给狗一个骨头,无锁实现线程安全

class Dog {
    private int bone;

    public Dog(int bone) {
        this.bone = bone;
    }

    public int getBone() {
        return bone;
    }

    public void setBone(int bone) {
        this.bone = bone;
    }
}
AtomicReference<Dog> ar = new AtomicReference<>(new Dog(0));

List<Thread> threads = new ArrayList<>();

for (int i = 0; i < 1000; i++) {
    threads.add(new Thread(() -> {
        while (true) {
            Dog dog = ar.get();
            if (ar.compareAndSet(dog, new Dog(dog.getBone() + 1))) {
                break;
            }
        }
    }));
}

threads.forEach(Thread::start);

for (Thread thread : threads) {
    thread.join();
}

System.out.println(ar.get().getBone());

3.2.2 ABA 问题

主线程一开始拿到的值是 A,但是 other() 方法讲 A 修改成 B 又修改成 A
这对主线程来说,它完全不知道

    static AtomicReference<String> ar = new AtomicReference<>("A");

    public static void main(String[] args) throws InterruptedException {
        String prev = ar.get();
        String next = "B";

        other();

        TimeUnit.SECONDS.sleep(1);
        boolean b = ar.compareAndSet(prev, next);
        System.out.println("是否修改成功!" + b);
    }

    public static void other() {

        new Thread(() -> {
            String prev = ar.get();
            String next = "C";
            ar.compareAndSet(prev, next);
        }).start();

        new Thread(() -> {
            String prev = ar.get();
            String next = "A";
            ar.compareAndSet(prev, next);
        }).start();

    }

3.2.3 AtomicStampedReference

  • AtomicStampedReference 比 AtomicReference 多一个参数 stamp
  • stamp 相当于版本,每次修改的时候我们约定去修改它,这样我们就知道原引用是否被修改
    static AtomicStampedReference<String> ar = new AtomicStampedReference<>("A", 1);

    public static void main(String[] args) throws InterruptedException {
        String prev = ar.getReference();
        int stamp = ar.getStamp();
        String next = "B";

        other();

        TimeUnit.SECONDS.sleep(1);
        boolean b = ar.compareAndSet(prev, next, stamp, ++stamp);
        System.out.println("是否修改成功!" + b);
    }

    public static void other() {

        new Thread(() -> {
            String prev = ar.getReference();
            String next = "C";
            int stamp = ar.getStamp();
            ar.compareAndSet(prev, next, stamp, ++stamp);
        }).start();

        new Thread(() -> {
            String prev = ar.getReference();
            String next = "A";
            int stamp = ar.getStamp();
            ar.compareAndSet(prev, next, stamp, ++stamp);
        }).start();

    }

3.2.4 AtomicMarkableReference

  • AtomicMarkableReference比 AtomicReference 多一个参数 mark
  • mark 代表标记 布尔类型
public AtomicMarkableReference(V initialRef, boolean initialMark) {
    pair = Pair.of(initialRef, initialMark);
}

3.3 原子数组

  • AtomicIntegerArray 保证数组里面元素的修改是安全的
    在这里插入图片描述

3.4 字段更新器

  • 字段的更新
    狗对象 name属性
class Dog {
    volatile String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}
Dog dog = new Dog("旺财");

AtomicReferenceFieldUpdater<Dog, String> updater = AtomicReferenceFieldUpdater.newUpdater(Dog.class, String.class, "name");

updater.compareAndSet(dog, dog.name, "大黄");

System.out.println(dog);

3.5 原子累加器

效率更改执行累加操作的类
在这里插入图片描述

LongAdder longAdder = new LongAdder();
longAdder.increment();
System.out.println(longAdder.longValue());

4 LongAdder 源码


    /**
     * Table of cells. When non-null, size is a power of 2.
     * 累加单元数组,懒惰初始化
     */
    transient volatile Cell[] cells;

    /**
     * Base value, used mainly when there is no contention, but also as
     * a fallback during table initialization races. Updated via CAS.
     * 基础值,如果没有竞争,则用 cas 累加这个锁
     */
    transient volatile long base;

    /**
     * Spinlock (locked via CAS) used when resizing and/or creating Cells.
     * 在 cells 创建或扩容时,置为 1 ,表示加锁
     */
    transient volatile int cellsBusy;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值