在前两节的程序中都需要包含一组原子方式执行(或者说不可分割)的操作,要避免竟态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量。从而确保其他线程只能在修改操作执行之前或者完成之后读取和修改状态,而不是在修改过程中。
如果递增操作是原子操作,那么竟态条件将不会发生,且递增操作在每次执行时都会把计数器增加1。为了确保线程安全性,“先检查后执行”(例如延迟初始化)和“读取+修改+写入”等操作必须是原子的,我们将“先检查后执行”和“读取+修改+写入”等操作的原子形式成为复合操作:包含一组必须以原子方式执行的操作以确保线程安全。在这边我们仅介绍使用现有的线程安全类来完成改原子操作(当然还有其它方式)。其中的同步方法synchronzied是下一节的内容,暂时可以先不看。
package com.zy.test;
import java.util.concurrent.atomic.AtomicLong;
public class CountingFactorizer {
private final AtomicLong count = new AtomicLong(0);
private long countOld = 0;
public long getCount() {
return count.get();
}
public void service() {
count.incrementAndGet();
System.out.println("递增是原子操作:" + count.get());// 打印结果会不准确,原因在于并没有把打印操作封装进复合操作中
}
public synchronized void service1() {
countOld++;
System.out.println("递增+打印都是原子操作:" + countOld);// 打印结果会准确,因为整个方法所代表的操作已经是原子性的了
}
public static void main(String[] args) {
CountingFactorizer countingFactorizer = new CountingFactorizer();
RequestDemo1 requestDemo = new RequestDemo1(countingFactorizer);
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(requestDemo);
thread.start();
}
}
}
class RequestDemo1 implements Runnable {
CountingFactorizer countingFactorizer;
RequestDemo1(CountingFactorizer countingFactorizer) {
this.countingFactorizer = countingFactorizer;
}
@Override
public void run() {
countingFactorizer.service();
countingFactorizer.service1();
}
}
结果:
递增是原子操作:1
递增+打印都是原子操作:1
递增是原子操作:2
递增+打印都是原子操作:2
递增是原子操作:3
递增+打印都是原子操作:3
递增是原子操作:5
递增+打印都是原子操作:4
递增是原子操作:4
递增+打印都是原子操作:5
递增是原子操作:6
递增是原子操作:7
递增+打印都是原子操作:6
递增是原子操作:8
递增+打印都是原子操作:7
递增+打印都是原子操作:8
递增是原子操作:9
递增+打印都是原子操作:9
递增是原子操作:10
递增+打印都是原子操作:10
递增是原子操作:11
递增+打印都是原子操作:11
递增是原子操作:12
递增+打印都是原子操作:12
递增是原子操作:13
递增+打印都是原子操作:13
递增是原子操作:14
递增+打印都是原子操作:14
递增是原子操作:15
递增+打印都是原子操作:15
递增是原子操作:16
递增+打印都是原子操作:16
递增是原子操作:17
递增+打印都是原子操作:17
递增是原子操作:18
递增+打印都是原子操作:18
递增是原子操作:19
递增+打印都是原子操作:19
递增是原子操作:20
递增+打印都是原子操作:20
在java.util.concurrent.atomic包中包含了一些原子变量类,用于实现在数值和对象引用上的原子状态转换。通过AtomicLong来代替long类型的计数器,能够确保所有对计数器状态的访问操作都是原子的。
在实际情况中,应尽可能的使用现有的线程安全对象(例如AtomicLong)来管理类的状态,与非线程安全的对象相比,判断线程安全对象的可能状态及其状态转换情况要更为容易,从而也更容易维护和验证线程安全性。