1. 程式人生 > >【java併發】執行緒併發庫的使用

【java併發】執行緒併發庫的使用

1. 執行緒池的概念

  在java5之後,就有了執行緒池的功能了,在介紹執行緒池之前,先來簡單看一下執行緒池的概念。假設我開了家諮詢公司,那麼每天會有很多人過來諮詢問題,如果我一個個接待的話,必然有很多人要排隊,這樣效率就很差,我想解決這個問題,現在我僱幾個客服,來了一個諮詢的,我就分配一個客服去接待他,再來一個,我再分配個客服去接待……如果第一個客服接待完了,我就讓她接待下一個諮詢者,這樣我僱的這些客服可以迴圈利用。這些客服就好比不同的執行緒,那麼裝這些執行緒的容器就稱為執行緒池。

2. Executors的使用

  Executors工具類是用來建立執行緒池的,這個執行緒池可以指定執行緒個數,也可以不指定,也可以指定定時器的執行緒池,它有如下常用的方法:

方法名 作用
newFixedThreadPool(int nThreads) 建立固定數量的執行緒池
newCachedThreadPool() 建立快取的執行緒池
newSingleThreadExecutor() 建立單個執行緒
newScheduledThreadPool(int corePoolSize) 建立定時器執行緒池

2.1 固定數量的執行緒池

  先來看下Executors工具類的使用:

public class ThreadPool {
//執行緒池的概念與Executors類的使用
    public
static void main(String[] args) { //固定執行緒池:建立固定執行緒數去執行執行緒的任務,這裡建立三個執行緒 ExecutorService threadPool = Executors.newFixedThreadPool(3); for (int i = 1; i <= 10; i++) {//向池子裡扔10個任務 final int task = i; threadPool.execute(new Runnable() {//execute方法表示向池子中扔任務,任務即一個Runnable
@Override public void run() { for (int j = 1; j <= 5; j++) { try { Thread.sleep(20); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " looping of " + j + " for task of " + task); } } }); } System.out.println("all of 10 tasks have committed!"); threadPool.shutdown(); //執行完任務後關閉 // threadPool.shutdownNow(); //立即關閉 } }

  從程式碼中可以看出,有了Executors工具類,我們建立固定數量的執行緒數就方便了,這些執行緒都去做同樣的任務。threadPool.execute表示從池子裡取出一個執行緒去執行任務,上面定義了三個執行緒,所以每次會取三個任務去讓執行緒執行,其他任務等待,執行完後,再從池子裡取三個任務執行,執行完,再取三個任務,最後一個任務三個執行緒有一個會搶到執行。所以定義了執行緒數量的話,每次會執行該數量的任務,因為一個執行緒一個任務,執行完再執行其他任務。
  因為這個執行結果有點多,就不貼結果了,反正每次三個任務一執行,直到執行完10個任務為止。

2.2 快取執行緒池

  所謂快取執行緒池,指的是執行緒數量不固定,一個任務來了,我開啟一個執行緒為其服務,兩個任務我就開啟兩個,N個任務我就開啟N個執行緒為其服務。如果現在只剩1個任務了,那麼一段時間後,就把多餘的執行緒給幹掉,保留一個執行緒為其服務。所以可以改寫一下上面的程式碼:

public class ThreadPool {
//執行緒池的概念與Executors類的使用
    public static void main(String[] args) {
        //快取的執行緒池
        //自動根據任務數量來設定執行緒數去服務,多了就增加執行緒數,少了就減少執行緒數
        //這貌似跟一般情況相同,因為一般也是一個執行緒執行一個任務,但是這裡的好處是:如果有個執行緒死了,它又會產生一個新的來執行任務
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 1; i <= 10; i++) {//扔5個任務
            final int task = i;
            threadPool.execute(new Runnable() {//向池子中扔任務,任務即一個Runnable

                @Override
                public void run() {
                    for (int j = 1; j <= 5; j++) {
                        try {
                            Thread.sleep(20);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + " looping of " + j + " for task of " + task);
                    }
                }
            });
        }
        System.out.println("all of 10 tasks have committed!");
        threadPool.shutdown(); //執行完任務後關閉
    }
}

  使用快取執行緒池的時候,會自動根據任務數量來產生執行緒數,即執行緒跟著任務走。執行結果也不貼了,有點多。
  那麼建立單個執行緒池newSingleThreadExecutor()就寫了,把上面那個方法改掉就行了,就只有一個執行緒去執行10個任務了,但是這跟我們平常直接new一個執行緒還有啥區別呢?它還有個好處就是,如果執行緒死了,它會自動再生一個,而我們自己new的就不會了。如果執行緒死了還要重新產生一個,也就是說要保證有一個執行緒在執行任務的話,那麼newSingleThreadExecutor()是個很好的選擇。

3. 執行緒池啟動定時器

  我們可以用靜態方法newScheduledThreadPool(int corePoolSize)來定義一個定時器執行緒池,可以指定執行緒個數。然後再呼叫schedule方法,傳進去一個Runnable和定時時長即可,見程式碼:

public class ThreadPool {

    public static void main(String[] args) {
        Executors.newScheduledThreadPool(3).schedule(new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " bombing");
            }
        }, 2, TimeUnit.SECONDS);        
    }
}

  定義了三個執行緒,會有一個率先搶到任務執行在2秒後執行。這只是建立了一個任務,如果我們要使用這個執行緒池去執行多個任務咋辦呢?schedule中只能傳入一個Runnable,也就是說只能傳入一個任務,解決辦法跟上面那些程式一樣,先拿到建立的執行緒池,再迴圈多次執行schedule,每次都傳進去一個任務即可:

public class ThreadPool {

    public static void main(String[] args) {
        //拿到定時器執行緒池
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);
        for(int i = 1; i <= 5; i ++) { //執行5次任務
            threadPool.schedule(new Runnable() {

                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " bombing");
                }
            }, 2, TimeUnit.SECONDS);
        }   
    }
}

  因為執行緒池中只有3個執行緒,但是有5個任務,所以會先執行3個任務,剩下兩個任務,隨機2個執行緒執行,看下結果:

pool-1-thread-3 bombing
pool-1-thread-2 bombing
pool-1-thread-1 bombing
pool-1-thread-2 bombing
pool-1-thread-3 bombing

  如果我想5秒後執行一個任務,然後每個2秒執行一次該怎麼辦呢?我們可以呼叫另一個方法scheduleAtFixedRate,這個方法中傳進去一個Runnable,一個定時時間和每次重複執行的時間間隔,如下:

public class ThreadPool {

    public static void main(String[] args) {
        Executors.newScheduledThreadPool(3).scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " bombing");
            }
        }, 5, 2, TimeUnit.SECONDS);     
    }
}

  這樣就可以5秒後執行,並且以後每隔2秒執行一次了。這個方法有個瑕疵,就是無法設定指定時間點執行,官方JDK提供的解決辦法是data.getTime()-System.currentTimeMillis()來獲取相對時間,然後放到上面方法的第二個引數即可。
  執行緒併發庫的使用就總結這麼多吧~

文末福利:“程式設計師私房菜”,一個有溫度的公眾號~
程式設計師私房菜