记录 AtomicInteger实现线程同步时,输出结果错乱无序

一点浅见,希望有所帮助。
如有讹误也欢迎指正

项目场景:

AtomicInteger是Java中的一个原子整数类,用于在多线程环境中进行原子操作。

了解一个概念原子操作,什么是原子操作

原子操作:顾名思义,(一个线程)操作(共享数据)具有原子性,像原子一样不可分割,即使是在多个线程一起执行的时候,一个(线程)操作一旦开始,就不会被其他线程干扰。也即是所谓的“线程安全”

这正符合我们线程同步的要求。

现在利用AtomicInteger记录剩余票数模拟多个窗口卖票的情况,看看它能否顺利的实现线程的同步,代码如下:

import java.util.concurrent.atomic.AtomicInteger;

public class TcketAtomicTest {

    private static final int SLEEP = 0;

    //main 方法执行
    public static void main(String[] args) {
        RunnableDemoTest1 r = new RunnableDemoTest1();
        for (int i = 1; i <= 3; i++) {
            new Thread(r, "窗口" + i).start();
        }

    }

    /**
     * 通过runnable 方式实现多线程
     */
    static class RunnableDemoTest1
            implements Runnable {
        //设置票数
        private AtomicInteger ticket = new AtomicInteger(10);

        @Override
        public void run() {
            while (ticket.get() > 0) {
                saleTicket();
            }
        }

        /**
         * 实现卖票方法
         */
        public void saleTicket() {
            if (ticket.get() > 0) {
                try {
                    Thread.sleep(SLEEP);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket.decrementAndGet();
                System.out.println("threadName:"
                        + Thread.currentThread()
                        + "正在出票,余票剩余:" +
                        ticket.get()
                        + "张");
            }
        }
    }
}

运行结果

在这里插入图片描述

先来看图中最严重的问题:余票竟然出现了负数,并且竟然连续打印了三次,AtomincInteger不是线程安全的吗,怎么会出现这种情况?


原因分析:


原子类的方法虽然是原子性的,但是和其他方法一起完成的线程执行不保证是原子性的,像这里先睡眠+原子操作+打印线程安全遭到了严重破坏,就可能出现像上图的情况:
1)在余票=1的时候,线程窗口1、窗口2和窗口3通过"余票>0"的检查,
2)线程窗口1先完成了自减操作,余票-1=0,还没来得及打印;
3)线程窗口2在窗口1结果的基础上完成了自减操作,即余票-1-1=-1,还没来得及打印;
4)线程窗口2在窗口1结果的基础上完成了自减操作,即余票-1-1-1=-2;
5)等线程窗口1、窗口2和窗口3完成打印,就出现了线程窗口1、窗口2和窗口3打印结果均为“余票为-2”的情况

调整代码:

将线程睡眠时间调整为0,即:原子操作+打印

运行结果

在这里插入图片描述

观察图片发现,虽然已经没有余票为-1的打印信息,但是仍然有多次打印余票相同的情况。

原因和上次无二,很明显即便只是原子操作+打印也会破坏线程安全,所以,最好还是要采取以下方案:
1)加锁;
2)先定义一个变量接收原子操作后的值,防止被后续执行的线程的原子操作改变,再打印这个变量的值。


再次调整代码:

1)先定义一个变量接收原子操作后的值,防止被后续执行的线程的原子操作改变,再打印这个变量的值。

int t = ticket.decrementAndGet();
 System.out.println("threadName:"
          + Thread.currentThread()
          + "正在出票,余票剩余:" +
          t
          + "张");

运行结果

在这里插入图片描述
观察图片可以发现,已经没有有多次打印余票相同的情况,不过打印在控制台的线程执行结果是错乱无序,更确切的说是乱中有序,毕竟余票数是从9到0无误的。出现此种情况之为什呢?

原因分析:


这所谓有序,自然是得益于原子类原子操作的特性,在原子性不被破坏的情况下,对这个变量操作的一瞬,线程执行是one by one井然有序的,这才是能得到“余票数是从14到0无误的”结果。

这所谓,要从两个方面说起。
在这里插入图片描述

一方面,以4、5两行打印信息为切入点,明明窗口1线程先于窗口3线程创建和执行start(),可为何从余票数来看,却是窗口3线程先于窗口1线程运行呢?仔细了解Thread的重要方法可知,这是因为线程的线程体真正运行实在run()被调用之后,而调用start()只是将线程的状态有NEW变成了RUNNABLE而已,此时线程要等待cpu调度。而cpu调度是基于时间分片来回切换的,不同的jvm有不同的调度算法,线程核实会被调度是未知的,这就意味着先执行start()的线程并一定先执行run()。所以,出现窗口2线程先于窗口1线程运行这种情况就不奇怪了。
在这里插入图片描述

另一方面,以1、2两行打印信息为切入点,从余票数来看,窗口1线程先于窗口2线程完成了对票数的自减操作,可为什么窗口2线程的结果却先出现在控制台上呢?这是因为虽然AtomicInteger的操作是原子性的,能保证one by one井然有序的,但打印操作却有快有慢,所以后完成操作的线程是可能先一步将结果打印在控制台。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值