详解JUC之原子类概述

18 篇文章 1 订阅
8 篇文章 0 订阅

原子操作

在讲 JUC (java.util.concurrent) 的原子类之前呢,我得先介绍一下原子操作

在化学变化中,原子(Atom)是不能再分的一种粒子(在物理上还可以分为质子和电子),计算机中的原子操作是指不能分割成若干部分的操作,表示这个操作在执行过程中,是不能被中断的。

比如说int i=0;是一个原子操作。而i++则可以分成两步操作,先是计算i+1的值然后第二步是将第一步的计算结果赋值给i

非原子操作都会存在线程安全问题,比如下面这个例子。下面这个例子我开启了1000条线程去将一个count变量做++运算,按照理想的状态最后输出的值应该是1000,但是最终的结果都是小于1000(反正我测试了很多次都没有到达1000的)。

public class Main {

    public static void main(String[] args) {
        Counter counter = new Counter();
        // 开启1000条线程让counter的count++,理想结果应该是1000
        for (int i = 0; i < 1000; i++) {
            new Thread() {
                public void run() {
                    try {
                        // 线程睡眠1毫秒可以更容易让线程切换造成问题
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    counter.inc();
                };
            }.start();
        }

        try {
            // 主线程睡眠1秒,等1000个线程执行完毕,只是权宜之计,并不优雅
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(counter.getCount());
    }

}

class Counter {
    private int count;

    public void inc() {
        count++;
    }

    public int getCount() {
        return count;
    }
}


这个问题呢其实不难解释,上面我已经说到i++是一个可分割的操作,即非原子操作,在这个例子中count++也是如此,那之所以输出的值比1000小,就在于count++的两步操作中,又混入了其它线程对count操作。

比如说某一时刻,count的值为3,然后线程A读取了count的值并将其加1,注意第二步的赋值操作还没执行即count值还是3,又有一个线程B读取了count的值(此时还是为3),对count的值加1然后赋值回给count变量(此时count的值为4),而又切换成线程A执行赋值操作了,线程A将4赋值给count变量,最终count变量的值为4,可以看到两个线程分别都对count执行了自加操作,但是count最终值是等于count+1的值而不是count+2。

有些人妄图用volatile关键字来解决这个问题,就是用volatile关键字修饰count变量,结果肯定是不行的,具体原因可以看看这里java中volatile关键字的含义

其实可以用synchronized关键字解决这个问题,就是让inc()成为同步方法,不过我要讲的重点不是synchronized关键字,就不在这介绍了。还有一个比较好的办法就是使用我们今天的主角——原子类

我只需要把Counter类修改一下就好了,把count变量的类型从int修改成AtomicInteger类(后面我就会介绍这些原子类),这个就是一个原子类。它有个方法int getAndIncrement(),意思就是获取值然后增加,其实就是像count++操作,相应还有int incrementAndGet()方法,就是对应++count操作。

下面是修改代码

class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    public void inc() {
        count.getAndIncrement();
    }

    public int getCount() {
        return count.intValue();
    }
}

然后再运行程序会输出我们预期的结果1000

原子类

并发包java.util.concurrent的原子类都存放在java.util.concurrent.atomic下,这些原子类可以操作一些数据类型的值,而且这些操作都是原子操作。

根据操作的数据类型,可以将JUC包中的原子类分为4类

  • 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
  • 数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
  • 引用类型: AtomicReference, AtomicStampedRerence, AtomicMarkableReference ;
  • 对象的属性修改类型: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater

在后面的文章我会相继介绍到其它原子类的使用和它们的实现原理,拜拜先了,要去上课了


在写这篇文章的时候,我在准备演示第一个例子的时候犯了一个错误,我感觉犯这个错误有点冤枉,希望你们能去看看这篇文章,不要走我趟过的泥路 (T-T)
一个小细节引起的悲剧——线程执行顺序错误

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值