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