多线程编程之竟态

一.竟态

  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);
    }
}
View Code

    测试类:

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);
            }
            
      }
}
View Code

    

    按理说每个线程不同时间生成的请求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的值,此时该线程仍然根据之前的判断去做一些操作,也会造成丢失数据更新和脏读。

 

    

     

    

posted @ 2018-07-22 22:35  Goxcheer  阅读(1181)  评论(0编辑  收藏  举报