原子性

Java中的原子性操作及案例分析
本文介绍了Java内存模型保证的原子性变量操作,如基本数据类型访问,还提及更大范围原子性保证可用synchronized关键字。通过电影院卖票案例说明非原子操作问题,如票数重复、出现不存在票数。同时列举了Java内存模型定义的8种原子操作,最后指出避免问题需用锁。

       原子性(Atomicity):由Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store、和write,我们大致可以认为基本数据类型的访问独享是具备原子性的(例外就是long和double的非原子性协定,读者只要知道这件事情就可以了,无需太过在意这些几乎都不会发生的例外情况)。
   
       如果应用场景需要一个更大范围的原子性保证(经常会遇到),Java内存模型还提供了lock和unlock操作来满足这种要求,尽管虚拟机未把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操作,这两个字节码指令反映到Java代码中就是同步块——synchornized关键字,因此在synchornized块之间的操作也具备原子性。

        简单来说,原子性就是指一个不可中断的操作,要么全部执行成功要么全部执行失败。在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。下面举个简单的案例:


电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “复联4”,本次电影的座位共100个
(本场电影只能卖100张票)。
我们来模拟电影院的售票窗口,实现多个窗口同时卖 “复联4”这场电影票(多个窗口一起卖这100张票)
需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟
 

Tickets类:

public class Tickets implements Runnable{

    int tickets = 100;

    @Override
    public void run() {

        while (true){
            
                if (tickets>0){
                    
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+"卖出第"+(tickets--)+"张票");
                }
        }
    }
}

 测试类:

public class demo1 {
    public static void main(String[] args) {
        Tickets2 tickets = new Tickets2();

        Thread tickets1 = new Thread(tickets);
        Thread tickets2 = new Thread(tickets);
        Thread tickets3 = new Thread(tickets);

        tickets1.start();
        tickets2.start();
        tickets3.start();
    }
}

测试结果部分出现现象:


可以看到结果有两个问题:

1. 相同的票数,比如12这张票被卖了3次。

2. 不存在的票,比如0票与-1票,是不存在的。

出现这种情况的原因是“tickets--”并不是一个原子性操作,其可以分为三步操作:1. 读取变量tickets的值;2:对tickets进行减一的操作;3.将计算后的值再赋值给变量tickets,而这三个操作无法构成原子操作。因为jvm中的线程是抢占式的,所以导致在其中一个线程已经执行到“tickets=0”时,其他线程并不知道而直接将tickets拿去又进行了一次减操作,所以就出现了-1或0的情况。

Java内存模型中定义了8钟操作都是原子的,不可再分的。

  1. lock(锁定):作用于主内存中的变量,它把一个变量标识为一个线程独占的状态;
  2. unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  3. read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便后面的load动作使用;
  4. load(载入):作用于工作内存中的变量,它把read操作从主内存中得到的变量值放入工作内存中的变量副本
  5. use(使用):作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作;
  6. assign(赋值):作用于工作内存中的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作;
  7. store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送给主内存中以便随后的write操作使用;
  8. write(操作):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

为了避免上述例子的情况,需要用到锁:

public class Tickets implements Runnable{

    int tickets = 100;

    Object o = new Object();

    @Override
    public void run() {

        while (true){

            synchronized (o) {

                if (tickets > 0) {

                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "卖出第" + (tickets--) + "张票");
                }
            }
        }
    }
}



 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值