1. 程式人生 > >Java併發程式設計(9)-使用閉鎖測試併發執行緒安全性

Java併發程式設計(9)-使用閉鎖測試併發執行緒安全性

文章目錄


更多關於Java併發程式設計的文章請點選這裡:Java併發程式設計實踐(0)-目錄頁


本文將介紹什麼是閉鎖,在java中的閉鎖實現:CountDownLatch類及其常用方法等,最後給出了一個使用閉鎖模擬執行緒併發的demo,用以簡單地測試任務是否為執行緒安全。

一、什麼是閉鎖

閉鎖(Latch)是在併發程式設計中常被提及的概念。閉鎖是一種執行緒控制物件,它能讓所有的執行緒在某個狀態時終止工作並等待,直到閉鎖“開門”時,所有的執行緒在這一刻會幾乎同時執行工作,製造出一個併發的環境。

二、CountDownLatch類介紹

2.1、什麼是CountDownLatch

CountDownLatch,顧名思義,可以理解為計數(count)、減少(down)、閉鎖(Latch),即通過計數減少的方式來達到阻礙執行緒執行任務的一種閉鎖,這個類位於java.util.concurent併發包下,是java中閉鎖的最優實現。

2.2、構造方法

CountDownLatch(int count)
構造器中計數值(count)就是閉鎖需要等待的執行緒數量,這個值只能被設定一次。

2.3、主要方法

  • void await():
    使當前執行緒在鎖存器倒計數至零之前一直等待,除非執行緒被中斷。

  • boolean await(long timeout, TimeUnit unit):
    使當前執行緒在鎖存器倒計數至零之前一直等待,除非執行緒被中斷或超出了指定的等待時間。

  • void countDown():
    遞減鎖存器的計數,如果計數到達零,則釋放所有等待的執行緒。

  • long getCount():
    返回當前計數。

  • String toString():
    返回標識此鎖存器及其狀態的字串。

三、使用閉鎖完成併發測試

使用閉鎖完成併發測試的基本思路是,定義一個startLatch閉鎖,並且它的執行緒等待值設定為1;之後新建的每一個執行緒都需要在執行任務前都在這個startLatch下等待,等所有執行緒都已集合完畢後,釋放startLatch,讓所有的執行緒都執行任務。

  • 測試非執行緒安全的程式:
 private int count_unsafe = 0;

    class MyTask_Unsafe implements Runnable {


        @Override
        public void run() {
            count_unsafe++;
            System.out.println(Thread.currentThread().getName() + "讀數為" + count_unsafe);
        }

    }

        @Test
        public void testConcurrent() {
            //新建閉鎖
            CountDownLatch startLatch = new CountDownLatch(1);
            //模擬100個執行緒去併發執行任務
            for (int i = 0; i < 100; i++) {
                new Thread() {
                    public void run() {
                        try {
                            //執行緒工作到此處即停止
                            startLatch.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        //在閉鎖允許後,呼叫任務
                        new MyTask_Unsafe().run();
                    }
                }.start();
            }

            //100次迴圈後,100個執行緒已經建立並且在等待中,可以統一開始執行任務
            System.out.println("所有執行緒集合完畢,等待執行任務...");
            //開始執行任務
            startLatch.countDown();

        }
    }

執行結果為:在這裡插入圖片描述
可以發現,有3個執行緒輸出的數字都為7,原因在第一篇文章中已經講過,count_unsafe++這個操作並不是原子的,而是存在讀-改-寫三個過程,所以執行緒不安全,下面我們測試一下執行緒安全的任務:

 private AtomicInteger count = new AtomicInteger(0);

    class MyTask_Safe implements Runnable {

        //執行緒安全類
        @Override
        public void run() {
            count.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + "讀數為" + count);
        }

    }
        @Test
        public void testConcurrent() {
            //新建閉鎖
            CountDownLatch startLatch = new CountDownLatch(1);
            //模擬100個執行緒去併發執行任務
            for (int i = 0; i < 100; i++) {
                new Thread() {
                    public void run() {
                        try {
                            //執行緒工作到此處即停止
                            startLatch.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        //在閉鎖允許後,呼叫任務
                        new MyTask_Safe().run();
                    }
                }.start();
            }

            //100次迴圈後,100個執行緒已經建立並且在等待中,可以統一開始執行任務
            System.out.println("所有執行緒集合完畢,等待執行任務...");
            //開始執行任務
            startLatch.countDown();

        }
    }

在這裡插入圖片描述