原子操作定义
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可被打乱,也不可以被切割而执行其中的一部分(不可中断)。将整个操作视为一个整体是原子性的核心特征。
线程安全问题
首先看下面这样一段代码,两个线程都对count变量进行自加操作10000次
public class ThreadSecurityDemo {
private long count = 0;
public void test() throws InterruptedException {
for (int i = 2; i > 0; i--) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {
count++;
}
}).start();
}
Thread.sleep(1000);
System.out.println(count);
}
public static void main(String[] args) throws InterruptedException {
ThreadSecurityDemo demo = new ThreadSecurityDemo();
demo.test();
}
}
运行结果如下:
按理来说count的值应该是20000,但实际上却不是,这就出现了线程安全问题,为了解决这个问题,我们可以使用同步代码块。
test方法修改如下:
public void test() throws InterruptedException {
for (int i = 2; i > 0; i--) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {
synchronized (this)
{
count++;
}
}
}).start();
}
Thread.sleep(1000);
System.out.println(count);
}
实际上,java的J.U.C包内为我们提供了原子操作的封装类:
AtomicBoolean: 原子更新布尔类型
AtomicInteger:原子更新整型
AtomicLong:原子更新长整型
等等…
使用很简单,我们使用AtomicLong来解决上面的问题
import java.util.concurrent.atomic.AtomicLong;
public class ThreadSecurityDemo {
private AtomicLong atoacount= new AtomicLong(0L);
public void test() throws InterruptedException {
for (int i = 2; i > 0; i--) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {//自增
atoacount.incrementAndGet();
}
}).start();
}
Thread.sleep(1000);
System.out.println(atoacount);
}
public static void main(String[] args) throws InterruptedException {
ThreadSecurityDemo demo = new ThreadSecurityDemo();
demo.test();
}
}
虽然效果一样,但是这种方法的执行效率比同步代码块高。
jdk1.8后加入了LongAdder,DoubleAdder的计数器,在高并发情况下性能更好,同样能够解决这个问题。
LongAdder会将一个数据分成多个操作单元,不同的线程更新不同的单元,只要需要汇总的时候才计算所有单元的操作。
import java.util.concurrent.atomic.LongAdder;
public class ThreadSecurityDemo {
private LongAdder lacount = new LongAdder();
public void test() throws InterruptedException {
for (int i = 2; i > 0; i--) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {
lacount.increment();
}
}).start();
}
Thread.sleep(1000);
System.out.println(lacount.sum());
}
public static void main(String[] args) throws InterruptedException {
ThreadSecurityDemo demo = new ThreadSecurityDemo();
demo.test();
}
}
下面我们通过三者在两秒钟内执行的次数来比较执行效率
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
public class ThreadSecurityDemo {
private int count=0;
// 同步代码块的方式
public void testSync() throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 2000) { // 运行两秒
synchronized (this) {
++count;
}
}
long endtime = System.currentTimeMillis();
System.out.println("同步代码块执行时间:" + (endtime - starttime) + "毫秒," + "执行自加次数:" + count);
}).start();
}
}
// Atomic方式
private AtomicLong acount = new AtomicLong(0L);
public void testAtomic() throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 2000) {
acount.incrementAndGet();
}
long endtime = System.currentTimeMillis();
System.out.println("Atomic方式执行时间:" + (endtime - starttime) + "毫秒," + "执行自加次数:" + acount.incrementAndGet());
}).start();
}
}
// LongAdder 方式
private LongAdder lacount = new LongAdder();
public void testLongAdder() throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 2000) {
lacount.increment();
}
long endtime = System.currentTimeMillis();
System.out.println("LongAdder方式执行时间:" + (endtime - starttime) + "毫秒," + "执行自加次数:" + lacount.sum());
}).start();
}
}
public static void main(String[] args) throws InterruptedException {
ThreadSecurityDemo demo = new ThreadSecurityDemo();
demo.testSync();
demo.testAtomic();
demo.testLongAdder();
}
}
通过执行次数我们可以直观比较执行效率:
LongAdder方式 > AtomicLong方式 > 同步代码块方式