程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java線程池基本使用

Java線程池基本使用

編輯:JAVA綜合教程

Java線程池基本使用


無論在Java的開發中還是在Android的開發中,線程都占有重要的地位,所以今天就來說說線程池的東西。

一、線程池概述

在Android開發中,我們經常把一個耗時任務放在一個線程中進行執行,目的就是為了避免ANR異常。但是如果我們在一個頁面開了很多線程,線程在短時間內執行結束,我們這樣頻繁的創建線程就降低了系統的運行效率。所以就有了線程池。線程池的作用是什麼呢?
線程池會根據系統的環境變量,自動或手動配置一個線程池中的線程數量,使線程的創建和回收達到一個理想的狀態,減少了系統資源的消耗,提高系統的效率。這樣我們就得出了使用線程池的幾個好處:

降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗 通過線程池對線程進行管理,提高了系統的承受力。(每個線程打越需要1MB內存)。 提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行

二、線程池的基本使用

在jdk1.5中,java給我們提供了java.util.concurrent包,這個包裡就是關於我們線程池的使用接口和類。線程池的頂級接口就是Executor接口。它有一個execute(Runnable)方法用來執行所提交的Runnable任務。它只是一個接口,所以需要有它的實現體:

scsd

配置線程池是一件比較復雜的工作,如果我們對線程池的原理不是很了解,很容易導致配置的線程池達不到效果。所以系統給我們提供了Executors類,它提供一些靜態方法幫助我們創建一些常用的線程池。

public static ExecutorService newFixedThreadPool(int nThreads):創建固定數目線程的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程。 public static ExecutorService newCachedThreadPool():創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):創建一個支持定時及周期性的任務執行的線程池,多數情況下可用來替代Timer類。 public static ExecutorService newSingleThreadExecutor():創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。

測試案例:

我們創建一個Text的線程,用於測試:

    class Text implements Runnable{
        String name;
        public Text(String name){
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "--" + name);
        }
    }

現在我們開始測試線程池的效果:

1、ExecutorService newFixedThreadPool(int nThreads)創建指定大小的線程池數。
    public static void main(String[]args){
        //我們創建一個指定大小為3的線程池
        ExecutorService exServiceFixed = Executors.newFixedThreadPool(3);
        Text t1 = new Text("t1");
        Text t2 = new Text("t2");
        Text t3 = new Text("t3");
        Text t4 = new Text("t4");
        Text t5 = new Text("t5");
        exServiceFixed.execute(t1);
        exServiceFixed.execute(t2);
        exServiceFixed.execute(t3);
        exServiceFixed.execute(t4);
        exServiceFixed.execute(t5);
        exServiceFixed.shutdown();
    }

結果:

    pool-1-thread-1--t1
    pool-1-thread-1--t4
    pool-1-thread-1--t5
    pool-1-thread-2--t2
    pool-1-thread-3--t3

我們可以看到盡管我們用線程池執行了5個線程任務,但是在線程池內部僅僅有我們指定的3個線程在工作,所以達到了提高效率的作用。

2、ExecutorService newCachedThreadPool()創建一個可重用不指定大小的線程池
    public static void main(String[]args){
        //我們創建一個可重用的線程池
        ExecutorService exServiceCached = Executors.newCachedThreadPool();
        Text t1 = new Text("t1");
        Text t2 = new Text("t2");
        Text t3 = new Text("t3");
        Text t4 = new Text("t4");
        Text t5 = new Text("t5");
        exServiceCached.execute(t1);
        exServiceCached.execute(t2);
        exServiceCached.execute(t3);
        exServiceCached.execute(t4);
        exServiceCached.execute(t5);
        exServiceCached.shutdown();
    }

運行結果:

    pool-1-thread-1--t1
    pool-1-thread-2--t2
    pool-1-thread-3--t3
    pool-1-thread-4--t4
    pool-1-thread-5--t5

線程池中開了5個線程,這個線程池的特點就是可重用,不限制大小,數量以JVM在系統中能創建的大小為准。

3、ScheduledExecutorService newScheduledThreadPool(int corePoolSize)創建定時的線程池。

例如:

    public static void main(String[]args){
        //我們創建一個可重用的線程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        Text t1 = new Text("t1");
        Text t2 = new Text("t2");
        Text t3 = new Text("t3");
        Text t4 = new Text("t4");
        Text t5 = new Text("t5");
        scheduledExecutorService.schedule(t1, 2000, TimeUnit.MILLISECONDS);
        scheduledExecutorService.schedule(t2, 2000, TimeUnit.MILLISECONDS);
    }

注意,此時使用的ScheduledExecutorService,它是ExecutorService接口的實現接口。schedule方法,用來定時任務,線程會在指定時間後進行執行。

另外,它還有定時持續的任務,

    scheduledExecutorService.scheduleAtFixedRate(t1, 1000,2000, TimeUnit.MILLISECONDS);
    scheduledExecutorService.scheduleAtFixedRate(t2, 1000,2000, TimeUnit.MILLISECONDS);
    scheduledExecutorService.scheduleWithFixedDelay(t1, 1000,2000, TimeUnit.MILLISECONDS);

每隔一段時間就去執行任務。

    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-2--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
    pool-1-thread-2--t1
    pool-1-thread-1--t2
4、ExecutorService newSingleThreadExecutor創建單線程線程池
    public static void main(String[]args){
        //我們創建一個可重用的線程池
        ExecutorService executorServiceSingle = Executors.newSingleThreadExecutor();
        Text t1 = new Text("t1");
        Text t2 = new Text("t2");
        Text t3 = new Text("t3");
        executorServiceSingle.execute(t1);
        executorServiceSingle.execute(t2);
        executorServiceSingle.execute(t3);
    }

結果:

    pool-1-thread-1--t1
    pool-1-thread-1--t2
    pool-1-thread-1--t3

我們可以看到只有一個線程在執行我們的任務。

三、線程池的補充知識

這裡補充一個知識點就是ExecutorService.submit()方法。這個方法有三個重載:

Future submit(Callable task); Future submit(Runnable task, T result); Future
    public interface Callable {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }

什麼都沒實現,就一個call()方法,根據注釋,這個方法用來計算某個結果。

Future的源碼結構:

    public interface Future {
        boolean cancel(boolean mayInterruptIfRunning);
        boolean isCancelled();
        boolean isDone();
        V get() throws InterruptedException, ExecutionException;
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }

  在Future接口中聲明了5個方法,下面依次解釋每個方法的作用:

cancel方法用來取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設置true,則表示可以取消正在執行過程中的任務。如果任務已經完成,則無論mayInterruptIfRunning為true還是false,此方法肯定返回false,即如果取消已經完成的任務會返回false;如果任務正在執行,若mayInterruptIfRunning設置為true,則返回true,若mayInterruptIfRunning設置為false,則返回false;如果任務還沒有執行,則無論mayInterruptIfRunning為true還是false,肯定返回true。 isCancelled方法表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。 isDone方法表示任務是否已經完成,若任務完成,則返回true; get()方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回;

get(long timeout, TimeUnit unit)用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null。
  也就是說Future提供了三種功能:

  1)判斷任務是否完成;

  2)能夠中斷任務;

  3)能夠獲取任務執行結果。

  因為Future只是一個接口,所以是無法直接用來創建對象使用的,因此就有了下面的FutureTask。

也不復雜,幾個方法判斷任務是否結束,以及通過get方法獲取任務的結果。我們直接看一個例子吧!

    public class MainDemo {
        public static void main(String[]args){
            //我們創建一個可重用的線程池
            ExecutorService executorServiceSingle = Executors.newSingleThreadExecutor();
            Future future = executorServiceSingle.submit(new AddCallable());
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

    class AddCallable implements Callable{

        @Override
        public Integer call() throws Exception {
            //執行我們的業務邏輯處理
            return 2+3;
        }
    }

我們實現一個Callable接口用來處理我們的任務,然後通過Future來獲取任務的結果。果Executor後台線程池還沒有完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。這也就是為什麼需要捕捉InterruptedException異常的原因。這點是不是跟我們Android的下載任務差不多。開啟一個下載任務,然後通過Handler發送的UIThread中進行處理。

同樣還有一種組合Callable+FutureTask

    Task task = new Task();
    FutureTask futureTask = new FutureTask(task);
    Thread thread = new Thread(futureTask);
    thread.start();

四、總結

本著復習知識點的原則,並沒有介紹線程池的配置,大家可以去搜索相關資料學習下,復習這個的知識點,就是准備這幾天寫個Android網絡請求的簡單框架使用,所以知識點也知識簡單介紹了用法,具體的深入分析沒做。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved