天天看點

四大線程池詳解

new Thread 的弊端

首先看一段代碼:

/**
 * Created by Zero on 2017/8/30.
 */
public class ThreadTest {
    public static void main(String[] args) {
        while (true) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread());
                }
            }).start();
        }
    }
}                
Thread[Thread-0,5,main]
Thread[Thread-4,5,main]
Thread[Thread-3,5,main]
Thread[Thread-6,5,main]
Thread[Thread-7,5,main]
Thread[Thread-2,5,main]
Thread[Thread-1,5,main]
Thread[Thread-8,5,main]
Thread[Thread-9,5,main]
Thread[Thread-5,5,main]
Thread[Thread-10,5,main]
Thread[Thread-11,5,main]
Thread[Thread-12,5,main]
Thread[Thread-13,5,main]
... ...                

先說一下此處的列印,第一個參數是目前線程名稱,由于線程之間是異步執行,有的還沒建立好,有的後來居上就執行完了,導緻列印線程的名稱會這樣,第二個參數是優先級,預設都是5,第三個參數是線程組名稱。

如果不停止程式,這段代碼會不斷建立和銷毀線程,直到當機或者OOM,更尴尬的是此處的線程,還無法主動去中斷。

上述的線程啟動方式在日常開發中經常看到的,但是從性能和優化的角度來說,問題還真不小。

  1. 每次都建立,性能較差(這個線上程池原理中有詳細講解)。
  2. 線程缺乏統一管理,可能無限制的建立線程,互相競争,會帶來一些不必要的麻煩。
  3. 可控性太差,比如定時定期執行,比如線程中斷機制。

線程池的優點

Java提供了四大線程池,主要針對new Thread的弊端講述優點:

  1. 降低資源消耗,不需要每次都是建立和銷毀,性能得到了提高。
  2. 統一管理,可有效控制最大并發量,提高系統資源的使用率,同時避免過多資源競争,避免堵塞。
  3. 可控性很好,可以定期定時執行、線程中斷機制等。

newCachedThreadPool

newCachedThreadPool:建立帶有緩存的線程池。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by Zero on 2017/8/30.
 */
public class ThreadTest {
    public static void main(String[] args) {
        test();
    }

    private static void test(){
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {

            cachedThreadPool.execute(new Runnable() {

                @Override
                public void run() {
                    System.out.println(Thread.currentThread());
                }
            });
        }
    }
}                
Thread[pool-1-thread-2,5,main]
Thread[pool-1-thread-4,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-6,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-5,5,main]
Thread[pool-1-thread-7,5,main]
Thread[pool-1-thread-7,5,main]
Thread[pool-1-thread-5,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-4,5,main]
Thread[pool-1-thread-7,5,main]
Thread[pool-1-thread-6,5,main]
Thread[pool-1-thread-4,5,main]
... ...                

線程在建立的同時,還有部分線程被回收後再利用,剛剛做了下測試,在每次列印之前加了2ms的延遲,列印的都是“Thread[pool-1-thread-1,5,main]”。

線程池為無限大(其實是Interger. MAX_VALUE),當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次建立線程,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則建立線程。

newFixedThreadPool

newFixedThreadPool:建立一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by Zero on 2017/8/30.
 */
public class ThreadTest {
    public static void main(String[] args) {
        test();
    }

    private static void test(){
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 100; i++) {

            fixedThreadPool.execute(new Runnable() {

                @Override
                public void run() {
                    System.out.println(Thread.currentThread());
                }
            });
        }
    }
}                
Thread[pool-1-thread-2,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-2,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-2,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-2,5,main]
... ...                

此處配置最大的線程長度是3,由列印可看出,此處始終在三個線程中執行。

newScheduledThreadPool

newScheduledThreadPool:支援定時和周期性執行的線程池。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Created by Zero on 2017/8/30.
 */
public class ThreadTest {
    public static void main(String[] args) {
        test();
    }

    private static void test(){
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
        /**
         * 延遲3秒執行
         */
        scheduledThreadPool.schedule(new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread());
            }
        }, 3000, TimeUnit.MILLISECONDS);

        /**
         * 延遲1秒後每3秒執行一次
         */
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread());
            }
        }, 1, 3000, TimeUnit.MILLISECONDS);
    }
}                
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-2,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-2,5,main]
Thread[pool-1-thread-2,5,main]
... ...                

此方法無論任務執行時間長短,都是當第一個任務執行完成之後,延遲指定時間再開始執行第二個任務。

在日常開發中,newScheduledThreadPool可以作為timer的替代品,對比timer,newScheduledThreadPool更安全更強大。

newSingleThreadExecutor

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by Zero on 2017/8/30.
 */
public class ThreadTest {
    public static void main(String[] args) {
        test();
    }

    private static void test(){
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 100; i++) {
            singleThreadExecutor.execute(new Runnable() {

                @Override
                public void run() {
                    System.out.println(Thread.currentThread());
                }
            });
        }
    }
}                

繼續閱讀