JDK併發包中的線程池(一)

不使用線程池時的一些問題

public class NonePool {
    public static void main(String[] args) {
        new Thread(){
            public void run() {
                //TODO
            };
        }.start();
    }
}

上面的代碼新建了一個線程,在線程結束的時候,線程會自動被回收
在生產環境中可能需要創建若干個線程來完成系統的功能,當需要的線程數量很多的時候就會需要分配大量的線程,此時還是使用這種方法就會有以下問題:
1. 創建大量的線程需要消耗大量的內存個CPU
2. 線程本身也需要佔據大量的內存和CPU
3. 線程執行完後需要回收,回收的時候需要大量的GC操作
綜上,在系統中,線程的數量並不是越多就越會提升系統的性能,需要有一個限度

線程池

爲了避免頻繁的創建線程和銷燬線程,我們可以讓創建的線程進行復用,類似於數據庫連接池。

JDK中支持的線程池

  1. public static ExecutorService newFixedThreadPool(int nThreads)
    含有固定數量爲nThreads的線程的線程池,提交任務的時候,如果有線程則立即執行,沒有的話則先將任務暫存在一個任務隊列中
  2. public static ExecutorService newSingleThreadExecutor()
    只有一個線程的線程池,若有多餘的任務被提交,那麼先將任務保存在一個隊列中,待線程空閒,按照FIFO的策略執行下一個線程
  3. public static ExecutorService newCachedThreadPool()
    返回一個可以根據實際情況調整線程數量的線程池。線程池的數量是不確定的,有空閒線程可以複用的話,那麼就優先使用這些線程。如果沒有空閒的線程,那麼會創建新的線程處理任務,線程執行完畢後,將返回線程池進行服用,默認的情況下如果一個線程超過60秒沒有被使用,那麼就會被回收
  4. public static ScheduledExecutorService newSingleThreadScheduledExecutor()
    線程池中線程的數量爲1,不過返回的是ScheduledExecutorService對象,意味着可以在指定的時間執行任務
    5.public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
    類似於第四種,不過可以指定線程中線程的數量

線程池案例分析

newFixedThreadPool

public class FixedThreadPool {
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        for(int i=0;i<10;i++){
            fixedThreadPool.submit(new MyTask(i));
        }
    }
}

class MyTask implements Runnable{
    private int i;
    public MyTask(int i) {
        super();
        this.i = i;
    }
    @Override
    public void run() {
        System.out.println(new Date().getTime()+"___"+Thread.currentThread().getId()+"___"+i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
1497167121692___13___4
1497167121692___10___1
1497167121692___11___2
1497167121692___12___3
1497167121692___9___0
1497167122692___13___5
1497167122692___9___6
1497167122693___10___7
1497167122694___11___8
1497167122694___12___9

分析:
可以看出第一批執行的時間是1497167121692,第二批的五個是1497167122692,並且前5個的執行的線程的id與後5個是一樣的,證明的確創建了5個線程,並且可以被複用

newScheduledThreadPool

scheduleAtFixedRate

public class ScheduledThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
System.out.println(Thread.currentThread().getId()+"\tbegin\t"+System.currentTimeMillis());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getId()+"\tend\t"+System.currentTimeMillis());
            }
        }, 0, 2, TimeUnit.SECONDS);
    }
}

public ScheduledFuture

9   begin   1497178292150
9   end     1497178293151
9   begin   1497178294151
9   end     1497178295152
11  begin   1497178296151
11  end     1497178297151
9   begin   1497178298151
9   end     1497178299151

注意
如果每個任務需要耗費的時間大於period,怎麼辦?
將上面的任務的休眠1s改爲休眠8s,結果如下:

9   begin   1497178511226
9   end     1497178519227
9   begin   1497178519227
9   end     1497178527228
11  begin   1497178527229
11  end     1497178535229
9   begin   1497178535229
9   end 1497178543229

可以看出此時執行後續第一個任務(第一次執行不算)的時間是initialDelay+8s,執行後續第二個任務的時間是initialDelay+2*8s,也就是說在同一時刻只會有一個任務在實行,並且執行的週期爲任務需要花費的時間與週期的大小之間去大值

scheduleWithFixedDelay

同樣的上面的代碼,如果將scheduledAtFixedRate改成scheduleWithFixedDelay,就會變成後續的任務會在前面的任務執行完畢後period時間執行,上面程序的運行結果就是

9   begin   1497178896324
9   end     1497178897324
9   begin   1497178899324
9   end     1497178900325
11  begin   1497178902326
11  end     1497178903326
9   begin   1497178905327
9   end     1497178906327

可以看出每一個任務的開始都是在上一個任務結束就的2秒鐘才執行的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章