死磕java底層(一)—多線程

1.線程和進程

1.1線程和進程的區別

  • 進程 它是內存中的一段獨立的空間,能夠負責當前應用程序的運行。當前這個進程負責調度當前程序中的全部運行細節(操做系統爲進程分配一塊獨立的運行空間);
  • 線程 它是位於進程中,負責當前進程中的某個具有獨立運行資格的空間(進程爲線程分配一塊獨立運行的空間); 進程是負責某個程序的執行,線程是負責進程中某個獨立功能的運行,一個進程至少要包含一個線程。
  • 多線程 在一個進程中能夠開啓多個線程,讓多個線程同時去完成某項任務。使用多線程的目的是爲了提升程序的執行效率。

1.2線程運行狀態

這裏寫圖片描述

經過Thread類或Runnable接口建立線程對象以後進入初始狀態;調用start方法進入可運行狀態(就緒狀態),此時並非真正的運行,只是表明已經作好了運行前的各項裝備;若是此線程獲取到cpu的時間片,則進入到真正的可運行狀態,執行run方法裏面的業務邏輯;若是run方法執行完畢或調用stop方法則線程運行結束,進入死亡狀態;在運行狀態時調用不一樣方法也會進入其餘不一樣狀態,若是調用強制運行方法join或休眠方法將進入等待狀態,時間到後自動進入就緒狀態,隨時準備獲取cpu時間片;若是看到synchronized則進入同步隊列等待狀態,或者若是調用了wait方法則進入等待狀態,等待狀態的線程必需要經過notify喚醒纔可進入等待狀態,若是其它線程執行完畢,本線程拿到同步鎖則進入就緒狀態,等待獲取cpu時間片。某個線程是否會執行只能看它可否爭搶到cpu時間片,可是經過調高優先級來讓線程更大機率的被優先執行。 參考文檔:https://mp.weixin.qq.com/s?src=11&timestamp=1513562547&ver=581&signature=30FEkCCQvF3E1tt67vYVym5tRNsSk3d8HGe0v9TAonJmhLh4-53fDEBbgwNFOlgp5rAlGFAJQXYnviaFRwiQ9NmbtIWnZGpotGcuV0Ok*3WzWxg4X6e2mxU0JrgbRb&new=1html

2.多線程

多線程運行的原理是:cpu在線程中作時間片的切換。cpu負責程序的執行,在每一個時間點它其實只能運行一個程序而不是多個程序,不停的在多個程序之間高速切換,而一個程序其實就是一個進程即多個線程,說到底其實就是cpu在多個線程之間不停的作高速切換,而開多個線程就是不讓cpu歇着,最大程度的壓榨它來爲程序服務。實現多線程有三種方式:繼承Thread類;實現Runnable接口;使用線程池。java

2.1繼承Thread類

public class MyExtendsThread extends Thread {
    String flag;

    public MyExtendsThread(String flag){
        this.flag = flag;
    }

    @Override
    public void run(){
        String name = Thread.currentThread().getName();
        System.out.println("線程"+name+"開始工做了...");
        Random random = new Random();
        for (int i = 0;i < 20;i++){
            try {
                Thread.sleep(random.nextInt(10)*100);
                System.out.println(name+"============="+flag);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread t0 = new MyExtendsThread("t0");
        Thread t1 = new MyExtendsThread("t1");

        t0.start();
        t1.start();
//        t0.run();
//        t1.run();
    }
}
複製代碼

調用線程要用start方法,而不是run方法,使用run方法只是調用方法,實際執行的仍是Main線程,而調用start方法能夠明顯的看到線程爭搶。git

2.2實現Runnable接口

public class MyThreadImplementRunnable implements Runnable {

    int x;

    public MyThreadImplementRunnable(int x) {
        this.x = x;
    }
    
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println("線程"+name+"開始執行");
        Random random = new Random();
        for(int i = 0;i<20;i++){
            try {
                Thread.sleep(random.nextInt(10)*100);
                System.out.println(name+"============="+x);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyThreadImplementRunnable(1),"線程1");
        Thread t2 = new Thread(new MyThreadImplementRunnable(2),"線程2");
    
        t1.start();
        t2.start();
    }
}
複製代碼

2.3實現Callable接口

  • 建立實現Callable接口的類MyThreadImplementCallable;
  • 建立一個類對象:MyThreadImplementCallable callable = new MyThreadImplementCallable("測試");
  • 由Callable建立一個FutureTask對象: FutureTask futureTask = new FutureTask(callable); 注意:FutureTask是一個包裝器,它經過接受Callable來建立,它同時實現了Future和Runnable接口。
  • 由FutureTask建立一個Thread對象: Thread thread = new Thread(futureTask);
  • 啓動線程: thread.start();
  • 獲取任務線程執行結果 futureTask.get(); 注意:實現Callable接口的線程能夠得到任務線程的執行結果;實現Runnable接口的線程沒法獲取任務線程執行的結果。
public class MyThreadImplementCallable implements Callable<String> {

    String name;
    public MyThreadImplementCallable(String name) {
        this.name = name;
    }

    @Override
    public String call() throws Exception {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"開始工做==============");
        Random random  = new Random();
        Thread.sleep(random.nextInt(5)*100);  //模擬執行業務
        return name+":執行完成";
    }

    public static void main(String[] args) throws Exception{
        MyThreadImplementCallable callable = new MyThreadImplementCallable("測試");
        FutureTask<String> futureTask = new FutureTask<String>(callable);
        Thread thread = new Thread(futureTask);

        thread.start();
        String result = futureTask.get();  //獲取任務線程執行結果
        System.out.println("線程的執行結果:"+result);
    }
}
複製代碼

2.4使用線程池

見下面的線程池專講。 參考文檔:https://www.cnblogs.com/langtianya/archive/2013/03/14/2959713.htmlgithub

3.同步

3.1synchronized關鍵字

public class MySynchronized {
    public static void main(String[] args){
        final MySynchronized synchronized1 = new MySynchronized();
        final MySynchronized synchronized2 = new MySynchronized();
        new Thread("thread1"){
            @Override
            public void run(){
                synchronized (synchronized1){
                    try {
                        System.out.println(this.getName()+":start");
                        Thread.sleep(1000);
                        System.out.println(this.getName()+":wake up");
                        System.out.println(this.getName()+":end");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread("thread2"){
            @Override
            public void run() {
                synchronized (synchronized1){  //爭搶同一把鎖時,線程1沒釋放以前,線程2只能等待
//                synchronized (synchronized2){ //若是不是一把鎖,能夠看到兩句話交叉打印,發生爭搶
                    System.out.println(this.getName()+":start");
                    System.out.println(this.getName()+":end");
                }
            }
        }.start();
    }
}
複製代碼

synchronized是java中的關鍵字,屬於java語言的內置特性。若是一個代碼塊使用synchronized修飾,則這塊代碼是同步的,當一個線程獲取到這個鎖而且開始執行時,其它線程只能一直眼睜睜的等着這個線程執行而後釋放鎖,其中釋放鎖只有兩種緣由:1.線程正常執行完畢;2.線程執行時發生異常,jvm自動將鎖釋放。能夠看到使用synchronized關鍵字以後每一個時刻只會有一個線程執行代碼塊裏面的共享代碼,線程安全;缺點也很明顯,其它線程只能等鎖釋放,資源浪費嚴重。編程

3.2Lock接口

  • lock和synchronized的區別: Lock不是Java語言內置的,它是一個接口,經過這個接口能夠實現同步訪問,synchronized是Java語言的關鍵字,是內置特性;Lock和synchronized有一點很是大的不一樣,採用synchronized不須要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完以後,系統會自動讓線程釋放對鎖的佔用;而Lock則必需要用戶去手動釋放鎖,若是沒有主動釋放鎖,就有可能致使出現死鎖現象。 Lock是一個接口,它裏面有以下方法:
public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
}
複製代碼

lock()、tryLock()、tryLock(long time, TimeUnit unit)、lockInterruptibly()是用來獲取鎖的。 unLock()方法是用來釋放鎖的。緩存

  • lock就是用來獲取鎖的,前面說到若是採用Lock,必須主動去釋放鎖。即便發生異常,程序也不會自動釋放鎖,所以通常來講,使用Lock必須在try{}catch{}塊中進行,而且將釋放鎖的操做放在finally塊中進行,以保證鎖必定被被釋放,防止死鎖的發生。
public class MyLock {

    private static ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private static Lock lock = new ReentrantLock();

    public static <E> void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                Thread thread = Thread.currentThread();

                lock.lock();  //獲取鎖
                try {
                    System.out.println(thread.getName() + "獲得了鎖");
                    for (int i = 0; i < 5; i++) {
                        arrayList.add(i);
                    }
                } catch (Exception e) {
                } finally {
                    System.out.println(thread.getName() + "釋放了鎖");
                    lock.unlock();  //釋放鎖
                }

            };
        }.start();

        new Thread() {
            @Override
            public void run() {
                Thread thread = Thread.currentThread();
                lock.lock();
                try {
                    System.out.println(thread.getName() + "獲得了鎖");
                    for (int i = 0; i < 5; i++) {
                        arrayList.add(i);
                    }
                } catch (Exception e) {
                } finally {
                    System.out.println(thread.getName() + "釋放了鎖");
                    lock.unlock();
                }

            };
        }.start();
    }
}
複製代碼
  • tryLock()表示用來嘗試獲取鎖,若是獲取成功,則返回true,若是獲取失敗(即鎖已被其餘線程獲取),則返回false,也就說這個方法不管如何都會當即返回,在拿不到鎖時不會一直等待。
  • tryLock(long time, TimeUnit unit)方法和tryLock()方法相似,區別在於這個方法在拿不到鎖時會等待必定的時間,在時間期限以內若是還拿不到鎖,就返回false。若是一開始就拿到鎖或者在等待期間內拿到了鎖,則返回true。
//觀察現象:一個線程得到鎖後,另外一個線程取不到鎖,不會一直等待
public class MyTryLock {

    private static List<Integer> arrayList = new ArrayList<Integer>();
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread("線程1") {
            @Override
            public void run() {
                Thread thread = Thread.currentThread();
                boolean tryLock = lock.tryLock();
                System.out.println(thread.getName()+"======="+tryLock);
                if(tryLock){
                    try {
                        System.out.println(thread.getName() + "獲得了鎖");
                        for(int i = 0;i < 20;i++){
                            arrayList.add(i);
                        }
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                        System.out.println(thread.getName() + "釋放了鎖");
                    }
                }
            }
        }.start();

        new Thread("線程2") {
            @Override
            public void run() {
                Thread thread = Thread.currentThread();
                boolean tryLock = lock.tryLock();
                System.out.println(thread.getName()+"======="+tryLock);
                if(tryLock){
                    try {
                        System.out.println(thread.getName() + "獲得了鎖");
                        for(int i = 0;i < 20;i++){
                            arrayList.add(i);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                        System.out.println(thread.getName() + "釋放了鎖");
                    }
                }
            }
        }.start();
    }
}
複製代碼

線程1和線程2共享成員變量arrayList,當線程1獲取鎖的時候,線程2就獲取不到鎖,沒辦法執行它的業務邏輯,只有等線程1執行完畢,釋放了鎖,線程2才能獲取鎖,執行它的代碼,進而保證了線程安全。安全

  • lockInterruptibly()方法比較特殊,當經過這個方法去獲取鎖時,若是線程正在等待獲取鎖,則這個線程可以響應中斷,即中斷線程的等待狀態。 注意,當一個線程獲取了鎖以後,是不會被interrupt()方法中斷的。 所以當經過lockInterruptibly()方法獲取某個鎖時,若是不能獲取到,只有進行等待的狀況下,是能夠中斷的。而用synchronized修飾的話,當一個線程處於等待某個鎖的狀態,是沒法被中斷的,只有一直等待下去。
  • Lock接口的實現類——ReentrantLock 直接使用lock接口的話,咱們須要實現不少方法,不方便,ReentrantLock是惟一實現了Lock接口的類,而且ReentrantLock提供了更多的方法,ReentrantLock,意思是「可重入鎖」,使用它能夠建立Lock對象。
  • ReadWriteLock也是一個接口,在它裏面只定義了兩個方法:
public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}
複製代碼

一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將文件的讀寫操做分開,分紅2個鎖來分配給線程,從而使得多個線程能夠同時進行讀操做。bash

  • ReentrantReadWriteLock裏面提供了不少豐富的方法,不過最主要的有兩個方法:readLock()和writeLock()用來獲取讀鎖和寫鎖,使用這個讀寫鎖操做的結果就是:要麼執行的全是讀操做,結束完以後全執行寫操做,中間不會交叉讀寫。
/**
 * @author 劉俊重
 * 若是有一個線程已經佔用了讀鎖,則此時其餘線程若是要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。
 * 若是有一個線程已經佔用了寫鎖,則此時其餘線程若是申請寫鎖或者讀鎖,則申請的線程會一直等待釋放寫鎖。
 */
public class MyReentrantReadWriteLock {

    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        final MyReentrantReadWriteLock myTest = new MyReentrantReadWriteLock();
        new Thread("線程1"){
            @Override
            public void run(){
                myTest.read(Thread.currentThread());
                myTest.writer(Thread.currentThread());
            }
        }.start();

        new Thread("線程2"){
            @Override
            public void run(){
                myTest.read(Thread.currentThread());
                myTest.writer(Thread.currentThread());
            }
        }.start();
    }

    /**
     * @Description 讀方法
     * @Author 劉俊重
     * @Date 2017/12/18
     */
    private void read(Thread thread){
        readWriteLock.readLock().lock();
        try {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis()-start<=1){
                System.out.println(thread.getName()+"===正在執行讀操做");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
            System.out.println(thread.getName()+"==釋放讀鎖");
        }
    }

    /**
     * @Description 寫方法
     * @Author 劉俊重
     * @Date 2017/12/18
     */
    private void writer(Thread thread){
        readWriteLock.writeLock().lock();
        try {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis()-start<=1){
                System.out.println(thread.getName()+"===正在執行寫操做");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
            System.out.println(thread.getName()+"==釋放寫鎖");
        }
    }
}
複製代碼

Lock和Synchronized的選擇:多線程

  • Lock是一個接口,而sysnchrinized是java關鍵字,屬於內置的語言實現;
  • synchronized關鍵字程序運行完成以後或出現異常時會釋放鎖,使用lock不會自動釋放鎖,只能本身使用unlock釋放,不然會引發死鎖,最好在finally中釋放;
  • 使用lock可使用trylock方法判斷有沒有得到鎖,使用synchronized沒法判斷;
  • 使用lock可讓等待鎖的線程中斷,使用synchronized沒法讓線程中斷,只能一直等待下去;
  • 使用lock能夠提升多線程讀操做的效率。 結論:若是競爭的資源不激烈,則使用synchronized和lock效率差很少;若是有大量線程同時競爭,則lock要遠遠優於synchronized。

4.volatile關鍵字

程序執行時有主內存,每一個線程工做時也有本身的工做內存。當一個線程開始工做時會從主內存中拷貝一個變量的副本到工做內存中,在工做內存中操做完副本時再更新回主內存。當存在多線程時,若是工做內存A處理完還沒來得及更新回主內存以前,工做內存B就從主內存中拉取了這個變量,那麼很明顯這個變量並非最新的數據,會出現問題。怎麼解決呢?可使用volatile,volatile有個最顯著的特性就是對它所修飾變量具備可見性,什麼意思呢,就是當一個線程修改了變量的值,新的值會馬上(立刻)同步到主內存中,其它線程使用時拉取到的就是最新的變量值。儘管volatile能保證變量的可見性,但並不能保證線程安全,由於它不能保證原子性。要想線程安全仍是要用同步或者鎖。 有一篇文檔寫volatile寫的很好,貼一下:http://dwz.cn/76TMGW併發

5.線程池

JDK1.5以後引入了高級併發特性,在java.util.concurrent包中,是專門用於多線程併發編程的,充分利用了現代計算機多處理器和多核心的功能以編寫大規模併發應用程序。主要包含原子量、併發集合、同步器、可重入鎖,並對線程池的建立提供了強力的支持。

5.1線程池的5種建立方式

  • Single Thread Executor : 只有一個線程的線程池,全部提交的任務都是順序執行; 代碼: Executors.newSingleThreadExecutor()
  • Cached Thread Pool : 線程池裏有不少線程須要同時執行,老的可用線程將被新的任務觸發從新執行,若是線程超過60秒內沒執行,那麼將被終止並從池中刪除; 代碼:Executors.newCachedThreadPool()
  • Fixed Thread Pool : 擁有固定線程數的線程池,若是沒有任務執行,那麼線程會一直等待, 代碼: Executors.newFixedThreadPool(4) 在構造函數中的參數4是線程池的大小,你能夠隨意設置,最好設置成和cpu的核數量保持一致,獲取cpu的核數量int cpuNums = Runtime.getRuntime().availableProcessors();
  • Scheduled Thread Pool : 用來調度即將執行的任務的線程池,可能不是直接執行, 每隔多久執行一次,屬於策略型的。 代碼:Executors.newScheduledThreadPool()
  • Single Thread Scheduled Pool : 只有一個線程,用來調度任務在指定時間執行,代碼:Executors.newSingleThreadScheduledExecutor() 示例代碼以下:
public static void main(String[] args) {
        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        //獲取cpu核心數
        int num = Runtime.getRuntime().availableProcessors();
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(num);
        ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(8);
        ScheduledExecutorService newSingleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
    }
複製代碼

5.2線程池的使用

說到線程池使用以前再強調一下Runnable的孿生兄弟——Callable,他們兩個很像,只是Runnable的run方法不會有任何返回結果,主線程沒法得到任務線程的返回值;可是Callable的call方法能夠返回結果,可是主線程在獲取時是被阻塞,須要等待任務線程返回才能拿到結果,因此Callable比Runnable更強大,那麼怎麼獲取到這個執行結果呢?答案是Future,使用Future能夠獲取到Callable執行的結果。 如今開始說線程池怎麼使用,也有兩種方式,一種Runnable的,一種Callable的:

  • 提交 Runnable ,任務完成後 Future 對象返回 null,調用excute,提交任務, 匿名Runable重寫run方法, run方法裏是業務邏輯。 示例代碼:
public class TestPoolWithRunnable {
    public static void main(String[] args) throws Exception{
        ExecutorService pool = Executors.newFixedThreadPool(4);
        for (int i=0;i<10;i++){
            Future<?> submit = pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "開始執行");
                }
            });
            System.out.println("執行結果:"+submit.get());  //全部的執行結果全是null
        }
        pool.shutdown();  //關閉線程池
    }
}
複製代碼
  • 提交 Callable,該方法返回一個 Future 實例表示任務的狀態,調用submit提交任務, 匿名Callable,重寫call方法, 有返回值, 獲取返回值會阻塞,一直要等到線程任務返回結果。
/**
 * @author 劉俊重
 * Callable 跟Runnable的區別:
 * Runnable的run方法不會有任何返回結果,因此主線程沒法得到任務線程的返回值
 * Callable的call方法能夠返回結果,可是主線程在獲取時是被阻塞,須要等待任務線程返回才能拿到結果
 */
public class TestPoolWithCallable {

    public static void main(String[] args) throws Exception{
        ExecutorService pool = Executors.newFixedThreadPool(4);
        for(int i=0;i<10;i++){
            Future<String> future = pool.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    Thread.sleep(500);
                    return "===="+Thread.currentThread().getName();
                }
            });
            //從Future中get結果,這個方法是會被阻塞的,一直要等到線程任務返回結果
            System.out.println("執行結果:"+future.get());
        }

        pool.shutdown();
    }
}
複製代碼

如何解決獲取執行結果阻塞的問題? 在使用future.get()方法獲取結果時,這個方法是阻塞的,怎麼提升效率呢?若是在不要求立馬拿到執行結果的狀況下,能夠先將執行結果放在隊列裏面,待程序執行完畢以後在獲取每一個線程的執行結果,示例代碼以下:

public class TestThreadPool {

    public static void main(String[] args) throws Exception{
        Future<?> submit = null;
        //建立緩存線程池
        ExecutorService cachePool = Executors.newCachedThreadPool();

        //用來存在Callable執行結果
        List<Future<?>> futureList = new ArrayList<Future<?>>();

        for(int i = 0;i<10;i++){
            //cachePool提交線程,Callable,Runnable無返回值
            //submit = cachePool.submit(new TaskCallable(i));
            submit = cachePool.submit(new TaskRunnable(i));

            //把這些執行結果放到list中,後面再取能夠避免阻塞
            futureList.add(submit);
        }
        cachePool.shutdown();
        //打印執行結果
        for(Future f : futureList){
            boolean done = f.isDone();
            System.out.println(done?"已完成":"未完成");
            System.out.println("線程返回結果:"+f.get());
        }
    }
}
複製代碼

把submit放在list集合中,線程直線完畢以後再取。

6.java併發編程總結

6.1不使用線程池的缺點

直接使用new Thread().start()的方式,對於通常場景是沒問題的,但若是是在併發請求很高的狀況下,就會有隱患:

  • 新建線程的開銷。線程雖然比進程要輕量許多,但對於JVM來講,新建一個線程的代價仍是很大的,決不一樣於新建一個對象。
  • 資源消耗量。沒有一個池來限制線程的數量,會致使線程的數量直接取決於應用的併發量。
  • 穩定性。當線程數量超過系統資源所能承受的程度,穩定性就會成問題。

6.2線程池的類型

不論是經過Executors建立線程池,仍是經過Spring來管理,都得知道有哪幾種線程池:

  • FixedThreadPool:定長線程池,提交任務時建立線程,直到池的最大容量,若是有線程非預期結束,會補充新線程;
  • CachedThreadPool:可變線程池,它猶如一個彈簧,若是沒有任務需求時,它回收空閒線程,若是需求增長,則按需增長線程,不對池的大小作限制;
  • SingleThreadExecutor:單線程。處理不過來的任務會進入FIFO隊列等待執行;
  • SecheduledThreadPool:週期性線程池。支持執行週期性線程任務 其實,這些不一樣類型的線程池都是經過構建一個ThreadPoolExecutor來完成的,所不一樣的是corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory這麼幾個參數。

6.3線程池飽和策略

由以上線程池類型可知,除了CachedThreadPool其餘線程池都有飽和的可能,當飽和之後就須要相應的策略處理請求線程的任務,好比,達到上限時經過ThreadPoolExecutor.setRejectedExecutionHandler方法設置一個拒絕任務的策略,JDK提供了AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy幾種策略。

我的博客地址:http://catchu.github.io

相關文章
相關標籤/搜索