【juc学习之路第3天】原生原子操作以及JUC原子操作的实现

在实际的业务场景中为了保证数据的安全性,原子操作是必不可少的,比如一些秒杀场景、银行存取款、火车票抢购等业务。为了模拟一下原子操作的必要性,我们实现一个不安全的抢票代码:

public class Main {
    public static int ticket = 5;
    public static void main(String[] args) throws InterruptedException {
        for(int i = 0; i < 20; i ++) {
            new Thread(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (ticket > 0) {
                    System.out.println("用户" + Thread.currentThread().getName() +"抢购成功!余票:" + -- ticket);
                }
            }).start();
        }
        TimeUnit.MILLISECONDS.sleep(500);
    }
}

用户Thread-12抢购成功!余票:2
用户Thread-6抢购成功!余票:-1
用户Thread-10抢购成功!余票:2
用户Thread-13抢购成功!余票:4
用户Thread-7抢购成功!余票:1
用户Thread-19抢购成功!余票:0
用户Thread-2抢购成功!余票:3
用户Thread-5抢购成功!余票:-2
用户Thread-3抢购成功!余票:4

解释一下为什么会出现这样的情况?
因为在本代码中if判断以及 --ticket 不是一个原子的操作,这个操作可以分解成:

  • 判断余票
  • 读取ticket
  • ticket - 1
  • 存到ticket中

所以一些线程在读取ticket之后发现还有余票,那么即使这时候因为时间片轮转到其他线程抢票成功导致无票了,这个线程也会抢票成功。很明显,这样非常的不安全,他抢到票了到时候没地方坐,把列车员踢下去吗?

那么如何实现线程安全?就要使用synchronized实现整个不安全的代码为原子操作(即不可拆分的操作)。再详细的解释一下什么叫原子操作:

在世界上我们可以认为原子是最小的结构。(说夸克的给我爬,夸克不能被直接观测到,你要是杠那你说的对一个原子操作是不可再分解的,如果这个原子操作没执行完,其他的线程必须要等待这个原子操作结束后才可以被轮到执行。因此,在上述代码中只要把抢票的代码封装一下就可以实现抢票安全(注意这里说的不是线程安全,抛出一个小水包等后续博客再解释)了。

优化:实现抢票安全的代码

public class Main {
    public static int ticket = 5;
    public static void main(String[] args) throws InterruptedException {
        for(int i = 0; i < 20; i ++) {
            new Thread(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                getTicket();
            }).start();
        }
        TimeUnit.MILLISECONDS.sleep(500);
    }
    public static synchronized void getTicket() {
        if (ticket > 0) {
            System.out.println("用户" + Thread.currentThread().getName() +"抢购成功!余票:" + -- ticket);
        }
    }
}

用户Thread-2抢购成功!余票:4
用户Thread-8抢购成功!余票:3
用户Thread-14抢购成功!余票:2
用户Thread-15抢购成功!余票:1
用户Thread-7抢购成功!余票:0

可以看到每个线程抢到票的几率是相等的。

使用JUC提供的原子操作优化抢票方案

首先我们要明白为什么这样做?
上述使用的是synchronized关键字修饰的方法解决了方案,但我们都知道synchronized不仅效率低下而且会经常出现死锁的问题。在JUC里提供的方案是不急于synchronized实现的,而是基于原生底层实现的。

优化:使用JUC实现抢票安全(我这个例子不是太好因为不使用synchronized还是无法保证原子性,但你们懂的就行)

public class Main {
    public static AtomicInteger ticket = new AtomicInteger(5);
    public static void main(String[] args) throws InterruptedException {
        for(int i = 0; i < 20; i ++) {
            new Thread(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                getTicket();
            }).start();
        }
        TimeUnit.MILLISECONDS.sleep(500);
    }
    public static synchronized void getTicket() {
        if (ticket.get() > 0) {
            System.out.println("用户" + Thread.currentThread().getName() +"抢购成功!余票:" + ticket.decrementAndGet());
        }
    }
}

用户Thread-6抢购成功!余票:4
用户Thread-19抢购成功!余票:3
用户Thread-18抢购成功!余票:2
用户Thread-15抢购成功!余票:1
用户Thread-17抢购成功!余票:0
···

原子操作类并没有使用到传统的同步机制,而是通过了一种 CAS 的机制来完成的,那么CAS 是什么?后面会进行详细的描述,整个的原子类都采用了类似的实现机制。

由于在实际的项目开发中会牵扯到多种数据类型的使用,所以在java.util.concurrent.atomic包中提供了多种原子性的操作类支持,这些操作类可以分为四类:

  • 基本类型:Atomicinteger、Atomiclong.AtomicBoolean ;
  • 数组类型:AtomicintegerArray、AtomicLongArray、AtomicReferenceArray;
  • 引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference;
  • 对象的属性修改类型:AtomiclntegerFieldUpdater、AtomicLongFieldUpdater、 AtomicReferenceFieldUpdater。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

edanhuang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值