并发编程专题-04共享模型-无锁(乐观锁)

1. 无锁和有锁解决线程安全问题

1.1 非线程安全实现

在多线程的环境下,对共享资源的访问存在线程安全问题

public class TestLock {
    public static void main(String[] args) {
        Account.demo(new AccountUnsafe(10000));
    }

}

interface Account{
    public void withdraw(Integer amount);

    public Integer getBalance();

    public static void demo(Account account){
        List<Thread> list = new ArrayList<>();
        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            list.add(new Thread(()->{
                account.withdraw(10);
            }));
        }
        list.forEach(Thread::start);
        list.forEach(t->{
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(account.getBalance() + " time:" + (end-start));
    }
}

class AccountUnsafe implements Account{

    private Integer balance;

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

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

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

    @Override
    public String toString() {
        return "AccountUnsafe{" +
                "balance=" + balance +
                '}';
    }
}

1.2 有锁保障线程安全

给 Account 对象加锁

public class TestLock {
    public static void main(String[] args) {
        Account.demo(new AccountSafe(10000));
    }

}

interface Account{
    public void withdraw(Integer amount);

    public Integer getBalance();

    public static void demo(Account account){
        List<Thread> list = new ArrayList<>();
        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            list.add(new Thread(()->{
                account.withdraw(10);
            }));
        }
        list.forEach(Thread::start);
        list.forEach(t->{
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(account.getBalance() + " time:" + (end-start));
    }
}

class AccountSafe implements Account{

    private Integer balance;

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

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

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

    @Override
    public String toString() {
        return "AccountUnsafe{" +
                "balance=" + balance +
                '}';
    }
}

1.3 无锁保障线程安全

使用JUC工具包下的原子整数类,实现无锁的线程安全保障

public class TestLock {
    public static void main(String[] args) {
        Account.demo(new  AccountSafe(10000));
    }

}

interface Account{
    public void withdraw(Integer amount);

    public Integer getBalance();

    public static void demo(Account account){
        List<Thread> list = new ArrayList<>();
        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            list.add(new Thread(()->{
                account.withdraw(10);
            }));
        }
        list.forEach(Thread::start);
        list.forEach(t->{
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(account.getBalance() + " time:" + (end-start));
    }
}

class AccountSafe implements Account{

    private AtomicInteger balance;

    public AccountSafe(Integer balance) {
        this.balance = new AtomicInteger(balance);
    }

    @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);
    }

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

    @Override
    public String toString() {
        return "AccountUnsafe{" +
                "balance=" + balance +
                '}';
    }
}

2. CAS原理

2.1 cas解决线程安全的方法图

image-20210618102235345.png

2.2 原理细节

  1. volatile

获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。CAS 必须借助 volatile 才能读取到共享变量的最新值来实现比较并交换的效果。

  1. CAS底层实现

CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证比较-交换的原子性。在多核状态下,某个核执行到带 lock 的指令时,CPU 会让总线锁住,当这个核把此指令执行完毕,再开启总线。这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的。

  1. 无锁效率高的原因

无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而 synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。

  1. CAS的特点

结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。

  • CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系
  • synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量
  • CAS 体现的是无锁并发、无阻塞并发
    • 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
    • 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响

3. 原子整数

例如:AtomicInteger,AtomicBoolean,AtomicLong

//API测试
public class TestLock {
    public static void main(String[] args) {
        AtomicInteger i = new AtomicInteger();
        System.out.println(i.get()); //0
        System.out.println(i.getAndIncrement()); //0
        System.out.println(i.incrementAndGet()); //2
        System.out.println(i.getAndDecrement()); //2
        System.out.println(i.decrementAndGet()); //0
        System.out.println(i.getAndAdd(5)); //0
        System.out.println(i.addAndGet(5)); //10
      //getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
        System.out.println(i.getAndUpdate(x->x+5)); //10
        System.out.println(i.updateAndGet(x->x+5)); //20
      //getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
        System.out.println(i.getAndAccumulate(10,(p,x)->p+x)); //20
        System.out.println(i.accumulateAndGet(10,(p,x)->p+x)); //40
    }
}

4. 原子引用

例如:AtomicReference,AtomicMarkableReference,AtomicStampedReference

4.1 AtomicReference

使用 AtomicReference 实现对大整数的无锁线程安全操作

//模拟银行取款的大整数无锁线程安全操作
public class TestLock {
    public static void main(String[] args) {
        DecimalAccount.demo(new DecimalAccountSafe(new BigDecimal(10000)));
    }
}

interface DecimalAccount{
    public void withdraw(BigDecimal amount);

    public BigDecimal getBalance();

    public static void demo(DecimalAccount account){
        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(new Thread(()->{
                account.withdraw(BigDecimal.TEN);
            }));
        }
        list.forEach(Thread::start);
        list.forEach(t->{
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(account.getBalance());
    }
}

class DecimalAccountSafe implements DecimalAccount{

    AtomicReference<BigDecimal> balance;

    public DecimalAccountSafe(BigDecimal balance) {
        this.balance = new AtomicReference<>(balance);
    }

    @Override
    public void withdraw(BigDecimal amount) {
        while (true){
            BigDecimal prev = balance.get();
            BigDecimal next = prev.subtract(amount);
            if (balance.compareAndSet(prev,next)){
                break;
            }
        }
    }

    @Override
    public BigDecimal getBalance() {
        return balance.get();
    }
}

4.2 ABA问题

  • 问题演示

如果使用普通的 AtomicReference 会出现仅能判定共享变量的值和初始值是否相同,但是不能感知到该值是否在此之前被修改后,又被修改回来初始值的情况

public class TestLock {
    private static AtomicReference<String> ref = new AtomicReference<>("A");

    public static void main(String[] args) throws InterruptedException {
        String prev = ref.get();
        other(); //其他线程修改该值
        TimeUnit.MICROSECONDS.sleep(1000);
        //依旧可以修改成功
        System.out.println("main: " + ref.compareAndSet(prev, "B")); 
    }

    private static void other() throws InterruptedException {
        new Thread(() -> {
            System.out.println("Thread1: " + ref.compareAndSet(ref.get(), "B"));
        }).start();
        TimeUnit.MICROSECONDS.sleep(1000);
        new Thread(() -> {
            System.out.println("Thread2:" + ref.compareAndSet(ref.get(), "A"));
        }).start();
    }
}
  • 使用 AtomicStampedReference 解决 ABA问题

AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,可以获得引用变量中途被更改了几次。

public class TestLock {
    private static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A",0);

    public static void main(String[] args) throws InterruptedException {
        String prev = ref.getReference();
        int stamp = ref.getStamp();
        other();
        TimeUnit.MICROSECONDS.sleep(1000);
        System.out.println("main: " + ref.compareAndSet(prev,"B",stamp,1));

    }

    private static void other() throws InterruptedException {
        new Thread(() -> {
            System.out.println("Thread1: " + ref.compareAndSet(ref.getReference(), "B",0,1));
        }).start();
        TimeUnit.MICROSECONDS.sleep(1000);
        new Thread(() -> {
            System.out.println("Thread2:" + ref.compareAndSet(ref.getReference(), "A",1,2));
        }).start();
    }
}
  • 使用 AtomicMarkableReference 解决 ABA问题

AtomicStampedReference 可以给原子引用加上版本号,但是有时候并不关心引用变量更改了几次,只是单纯的关心是否更改过,此时可以使用该类。

public class TestLock {
    private static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A",true);

    public static void main(String[] args) throws InterruptedException {
        String prev = ref.getReference();
        other();
        TimeUnit.MICROSECONDS.sleep(1000);
        System.out.println("main: " + ref.compareAndSet(prev,"B",true,false));

    }

    private static void other() throws InterruptedException {
        new Thread(() -> {
            System.out.println("Thread1: " + ref.compareAndSet(ref.getReference(), "B",true,false));
        }).start();
        TimeUnit.MICROSECONDS.sleep(1000);
        new Thread(() -> {
            System.out.println("Thread2:" + ref.compareAndSet(ref.getReference(), "A",false,false));
        }).start();
    }
}

5.原子数组

例如:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

//分别使用不安全和安全的数组,来保存累计数和
public class TestLock {
    public static void main(String[] args) {
        //不安全的数组
        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)
        );
    }

    //函数式编程
// supplier 提供者 无中生有 ()->结果
// function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
// consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)->void
    private static <T> void demo(
            Supplier<T> arraySupplier,
            Function<T, Integer> lengthFun,
            BiConsumer<T, Integer> putConsumer,
            Consumer<T> printConsumer
    ) {
        List<Thread> list = new ArrayList<>();
        T array = arraySupplier.get();
        int len = lengthFun.apply(array);
        for (int i = 0; i < len; i++) {
            list.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    putConsumer.accept(array, j % len);
                }
            }));
        }
        list.forEach(Thread::start);
        list.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        printConsumer.accept(array);
    }
}
  • result
[8239, 8236, 8257, 8248, 8226, 8250, 8262, 8250, 8232, 8246]
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]

6.字段更新器

例如:AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater利用

字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现 异常

//更新字段实例
public class TestLock {

    private volatile Integer fieldNumber;

    public static void main(String[] args) {
        AtomicReferenceFieldUpdater fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(TestLock.class,Integer.class,"fieldNumber");
        TestLock testLock = new TestLock();
        fieldUpdater.compareAndSet(testLock,null,10);
        System.out.println(testLock.fieldNumber);
        fieldUpdater.compareAndSet(testLock,10,20);
        System.out.println(testLock.fieldNumber);
        fieldUpdater.compareAndSet(testLock,10,30);
        System.out.println(testLock.fieldNumber);
    }
}
  • result
10
20
20

7.原子累加器

7.1 累加器性能比较

性能提升的原因是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加 Cell[1]... 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。

public class TestLock {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            demo(()->new AtomicLong(0),adder->adder.getAndIncrement());
        }
        System.out.println("+++++++++++++++++");
        for (int i = 0; i < 5; i++) {
            demo(()->new LongAdder(),adder->adder.increment());
        }
    }

    private static <T>void demo(Supplier<T> adderSupplier,Consumer<T> action){
        List<Thread> list = new ArrayList<>();
        T adder = adderSupplier.get();
        long start = System.nanoTime();
        // 40个线程,每个线程累加 5 万
        for (int i = 0; i < 40; i++) {
            list.add(new Thread(()->{
                for (int j = 0; j < 50000; j++) {
                    action.accept(adder);
                }
            }));
        }
        list.forEach(Thread::start);
        list.forEach(t->{
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println("time:"+ (end - start)/1000_000 + " ms");
    }
}
  • result
time:115 ms
time:78 ms
time:76 ms
time:75 ms
time:93 ms
+++++++++++++++++
time:26 ms
time:14 ms
time:24 ms
time:18 ms
time:18 ms

7.2 LongAdder原理

8.Unsafe类

Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得

//使用Unsafe完成CAS操作
public class TestLock {
    public static void main(String[] args) throws NoSuchFieldException {
        Unsafe unsafe = UnsafeAccessor.getUnsafe();
        // 获得成员变量
        Field id = Student.class.getDeclaredField("id");
        Field name = Student.class.getDeclaredField("name");
        // 获得成员变量的偏移量
        long idOffset = unsafe.objectFieldOffset(id);
        long nameOffset = unsafe.objectFieldOffset(name);
        Student student = new Student();
        // 使用 cas 方法替换成员变量的值
        unsafe.compareAndSwapInt(student,idOffset,0,10);
        unsafe.compareAndSwapObject(student,nameOffset,null,"yorick");
        System.out.println(student);
    }
}

@Data
class Student{
    volatile int id;
    volatile String name;
}

class UnsafeAccessor{
    private static Unsafe unsafe;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public static Unsafe getUnsafe() {
        return unsafe;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城老湿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值