详解JUC之原子类概述

时间:2023-01-12 20:52:03

原子操作

在讲 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)
一个小细节引起的悲剧——线程执行顺序错误