一.竟态
1.竟态的概念
竟态指计算结果的正确性依赖相对时间顺序和线程的交错,通俗的说就是计算结果与时间有关,对于一个同样的输入,有时候结果正确,有时候结果不正确。
竟态不一定会导致结果错误,只是说有这种导致结果出错的可能性。
2.模拟竟态的产生
下面有一个模拟请求Id生成器,让多个线程随机生成请求id.
public final class RequestIdGenerator { private final static RequestIdGenerator INSTANCE = new RequestIdGenerator(); private final static short SEQ_UPPER_LIMIT = 999; private short sequence = -1; private RequestIdGenerator() { } public static RequestIdGenerator getInstance (){ return INSTANCE; } public short nextSequence() { if (sequence >= SEQ_UPPER_LIMIT) { sequence = 0; } else { sequence++; } return sequence; } public String nextId() { SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss"); String timeStamp = sdf.format(new Date()); DecimalFormat df =new DecimalFormat("000"); short sequenceNo = nextSequence(); return "0049"+timeStamp+df.format(sequenceNo); } }
测试类:
public class RaceConditionDemo { public static void main(String[] args) { int numberOfThreads = Runtime.getRuntime().availableProcessors(); Thread[] workThreads = new Thread[numberOfThreads]; System.out.println(numberOfThreads); for (int i = 0; i< numberOfThreads; i++) { workThreads[i] = new WorkerThread(i, 10); } for (Thread t : workThreads) { t.start(); } } static class WorkerThread extends Thread { private final int requestCount; public WorkerThread (int id, int requsetCount) { super("worker-"+id); this.requestCount = requsetCount; } @Override public void run() { int i =requestCount; RequestIdGenerator generator = RequestIdGenerator.getInstance(); while (i-- > -1) { String requestID = generator.nextId(); processRequest(requestID); } } private void processRequest(String requestID) { Tools.randomPause(50); System.out.println(Thread.currentThread().getName()+" "+requestID); } } }
按理说每个线程不同时间生成的请求id应该都是不同的,但是多次执行会发现有时候请求id是相同的。
这也就是说执行结果正确与否与时间相关,即出现了竟态。
3.竟态结果分析
分析可见nextSequence()这个方法导致了不同的线程拿到了相同的requestId,因为RequestIdGenerator这个类中有一个共享的全局变量sequence,多个线程并发的读取更新
sequence导致了竟态的出现。即一个线程对sequence所做的更新可能被其它线程的更新而覆盖掉,导致数据出现脏读。
4.竟态的两种模式
①read-modify-write(读-改-写)
即读取一个共享变量的值(read),然后根据值做一些计算(modify),最后更新该变量的值(write).
例如sequence++就是如此,过程指令如下:
1.从内存中将squence的值读取到寄存器r1中
2.r1的值加1
3.将r1的值更新到sequence变量的内存空间中
在多线程环境下,某个线程执行完1后,可能其他线程已经更新了sequence的值,但是该线程却仍然使用r1未更新的值去操作2,3指令,造成丢失更新和脏读。
②check-then-act(检测而后行动)
以下面这段代码为例:
public short nextSequence() { if (sequence >= SEQ_UPPER_LIMIT) { //步骤1 sequence = 0; //步骤2.1 } else { sequence++; //步骤2.2 } return sequence; }
检测而后行动指读取某个共享变量的值,根据该值做一些判断,然后根据该判断结果去条件执行一些操作。
在多线程环境下,可能当某个线程执行完步骤1后,其它线程更新了sequence的值,此时该线程仍然根据之前的判断去做一些操作,也会造成丢失数据更新和脏读。