併發編程之線程建立到銷燬、經常使用API

  在前面一篇介紹了線程的生命週期【併發編程之多線程概念 】,在本篇將正式介紹如何建立、中斷線程,以及線程是如何銷燬的。最後,咱們會講解一些常見的線程API。html

 

線程建立java

  Java 5 之前,實現線程有兩種方式:擴展java.lang.Thread類,實現java.lang.Runnable接口。這兩種方式都是都是直接建立線程,而每次new Thread都會消耗比較大的資源,致使每次新建對象時性能差;並且線程缺少統一管理,可能無限制新建線程,相互之間競爭,極可能佔用過多系統資源致使死機或OOM。同時,new Thread的線程缺少更多功能,如定時執行、按期執行、線程中斷。編程

  Java 5開始,JDK提供了4中線程池(newFixedThreadPool、newCachedThreadPool、newScheduledThreadPool、newSingleThreadExecutor)來獲取線程。這樣作的好處是:能夠重用已經存在的線程,減小對象建立、消亡的開銷,性能佳;並且線程池可有效控制最大併發線程數,提升系統資源的使用率,同時避免過多資源競爭,避免堵塞。經過特定的線程池也能夠實現定時執行、按期執行、單線程、併發數控制等功能。緩存

  建立線程的代碼實現安全

  • 擴展java.lang.Thread類
    • 自定義一個類繼承java.lang.Thread
    • 重寫Thread的run(),把自定義線程的任務定義在run方法上
    • 實例化自定義的Thread對象,並調用start()啓動線程
//1.自定義一個類繼承Thread類
public class ExThread extends Thread {
    //2.重寫run()
    @Override
    public void run() {
       for (int i = 0; i < 100; i++) {
           System.out.println(Thread.currentThread().getName()+」:」+i);
       }
    }

    public static void main(String[] args) {
        //3.建立Thread子類對象
        ExThread exThread = new ExThread();
        //4.調用start方法啓動自定義線程
        exThread.start();
    }
}

 

  • 實現java.lang.Runnable接口
    • 自定義一個類實現Runnable接口
    • 實現Runnable接口中的run(),把自定義線程的任務定義在run方法上
    • 建立Runnable實現類的對象
    • 建立Thread對象,而且把Runnable實現類的對象做爲參數傳遞
    • 調用Thread對象的start()啓動自定義線程
//1.自定義一個類實現Runnable接口
public class ImThread implements Runnable{
    //2.實現run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+」:」+i);
        }  
    }
   
    public static void main(String[] args) {
        //3.建立Runnable實現類對象
        ImThread imThread = new ImThread();
        //4.建立Thread對象
        Thread thread = new Thread(imThread);
        //5.調用start()開啓線程
        thread.start();
    }
}

  

  • newFixedThreadPool

    建立一個固定線程數的線程池,可控制線程最大併發數,超出的線程會在隊列中等待多線程

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

public class CreateThreadByFixedPool {

    /**
     * Cover Runnable.run()
     */
    private static void run(){
        System.out.println(Thread.currentThread().getName()+" is running...");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        ExecutorService pool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            pool.execute(CreateThreadByFixedPool::run);
        }
    }
}

 

  • newCachedThreadPool

    建立一個可緩存線程池,若是線程池長度超過處理須要,可靈活回收空閒線程,若無可回收,則新建線程.併發

    線程池的容量爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。ide

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

public class CreateThreadByCachedPool {

    public static void main(String[] args) {

        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            pool.execute(() -> System.out.println(Thread.currentThread().getName()+" is running..."));
        }
    }
}

 

  • newScheduledThreadPool

    建立一個固定線程數的線程池,支持定時及週期性任務執行。工具

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

public class CreateThreadByScheduledPool {

    public static void main(String[] args) {

        ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
//delay 2s excute. pool.schedule(() -> System.out.println(Thread.currentThread().getName()+" delays 2s "), 2, TimeUnit.SECONDS);
//delay 2s and every 3s excute. pool.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName()+" delays 2s every 3s execte"), 2, 3, TimeUnit.SECONDS); } }

 

  • newSingleThreadExecutor

    建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行。post

public class CreateThreadBySingleThreadPool {

    public static void main(String[] args) {

        ExecutorService pool = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            pool.execute(() ->{
                System.out.println(String.format("The thread %d (%s) is running...",
                        index,Thread.currentThread().getName()));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

 

  Thread負責線程自己相關的職責和控制,Runnable負責邏輯業務。

  在實現自定義線程時,推薦使用Runnable接口,由於其具備如下幾個優勢:
  • Java是單繼承多實現的,實現接口有利於程序拓展
  • 實現Runnable接口可爲多個線程共享run() 【繼承Thread類,重寫run()只能被該線程使用】
  • 不一樣線程使用同一個Runnable,不用共享資源

 

線程中斷

    interrupt()方法能夠用來請求終止線程。
  • 當對一個線程調用interrupt方法時,線程的中斷狀態(boolean標誌)會被置位。
  • 判斷當前線程是否中斷,可以使用Thread.currentThread.isInterrupt()
  • 中斷並非強制終止線程,中斷線程只是引發當前線程的注意,由它本身決定是否響應中斷。【有些(很是重要的)線程會處理完異常後繼續執行,並不理會中斷;可是更加廣泛的狀況是:線程簡單的將中斷做爲一個終止請求。】

 

線程銷燬

    線程銷燬的幾種情景:
  • 線程結束,自行關閉
  • 異常退出
  • 經過interrupt()修改isInterrupt()標誌進行結束
public static void main(String[] args) throws InterruptedException {
        Thread t  = new Thread(){
            @Override
            public void run() {
                System.out.println("I will start work.");
                while(!isInterrupted()){
                    System.out.println("working....");
                }
                System.out.println("I will exit.");
            }
        };
        t.start();
        TimeUnit.MICROSECONDS.sleep(100);
        System.out.println("System will exit.");
        t.interrupt();     }
  • 使用volatile修飾的開關flag關閉線程(由於線程的interrupt標識極可能被擦除)【chapter04.FlagThreadExit】
public class FlagThreadExit {

    static class MyTask extends Thread{
        
        private volatile boolean closed = false;
        
        @Override
        public void run() {
            System.out.println("I will start work.");
            while(!closed && !isInterrupted()){
                System.out.println("working....");
            }
            System.out.println("I will exit.");
        }
        
        public void closed(){
            this.closed = true;
            this.interrupt();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        MyTask task = new MyTask();
        task.start();
        TimeUnit.MICROSECONDS.sleep(100);
        System.out.println("System will exit.");
        task.closed();     }
}

 

多線程API

方法
返回值
做用
 
yield()
static void
暫停當前正在執行的線程對象,並執行其餘線程。
只有優先級大於等於該線程優先級的線程(包括該線程)纔有機會被執行
釋放CPU資源,不會放棄monitor鎖
sleep()
static void
使當前線程休眠,其它任意線程都有執行的機會
釋放CPU資源,不會放棄monitor鎖
wait()
void
使當前線程等待
Object的方法
interrupt()
void
中斷線程
可中斷方法
interrupted()
static boolean
判斷當前線程是否中斷
 
isInterrupted()
boolean
測試線程是否已經中斷
 
join()
void
在線程A內,join線程B,線程A會進入BLOCKED狀態,直到線程B結束生命週期或者線程A的BLOCKED狀態被另外的線程中斷
可中斷方法
 
  • 可中斷方法被打斷後會收到中斷異常InterruptedException.
  • yield()和sleep()的比較
    • 都是Thread類的靜態方法
    • 都會使當前處於運行狀態的線程放棄CPU
    • yield只會讓位給相同或更高優先級的線程,sleep讓位給全部的線程
    • 當線程執行了sleep方法後,將轉到阻塞狀態,而執行了yield方法以後,則轉到就緒狀態;
    • sleep方法有可能拋出異常,而yield則沒有【sleep是可中斷方法,建議使用sleep】
  • sleep和wait的比較
    • wait和sleep方法均可以使線程進入阻塞狀態
    • wait和sleep都是可中斷方法
    • wait使Object的方法,sleep是Thread的方法
    • wait必須在同步代碼中執行,而sleep不須要
    • 在同步代碼中,sleep不會釋放monitor鎖,而wait方法會釋放monitor鎖
    • sleep方法在短暫休眠後主動退出阻塞,而(沒有指定時間的)wait方法則須要被其它線程notify或interrupt纔會退出阻塞

 

wait使用

  • 必須在同步當法中使用wait和notify方法(wait和notify的前提是必須持有同步方法的monitor的全部權)
  • 同步代碼的monitor必須與執行wait和notify方法的對象一致
public class WaitDemo {

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.execute(() -> {
            synchronized (WaitDemo.class){
                System.out.println("Enter Thread1...");
                System.out.println(Thread.currentThread().getName()+" is waiting...");
                try {
                    WaitDemo.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread1 is going...");
                System.out.println("Shut down Thread1.");
            }
        });

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        pool.execute(() ->{
            synchronized (WaitDemo.class) {
                System.out.println("Enter Thread2...");
                System.out.println(Thread.currentThread().getName()+" is notifying other thread...");
                WaitDemo.class.notify();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread2 is going...");
                System.out.println("Shut down Thread2.");
            }

        });
    }

}

 

 

補充

  • 棄用stop()和suspend()的緣由
                stop()用來終止一個線程,可是不安全的; stop()會終止當前線程的全部未結束的方法,包括run()。當前線程被終止,當即釋放被它鎖住的鎖。這會致使對象處於不一致的狀態。【在轉帳過程當中,可能錢剛轉出,還未轉入另外一個帳戶,線程就被中斷。致使對象被破壞,數據出錯,其餘線程獲得的將會是錯誤的對象數據】
                suspend()用來阻塞一個線程,直至另外一個線程調用resume()。suspend()會常常致使死鎖。 調用suspend()的時候,目標線程會被掛起,可是仍然會持有以前得到的鎖定。此時,其餘線程都不能訪問被鎖定的資源。若是調用suspend()的線程試圖獲取同一個鎖,那麼線程死鎖(被掛起的線程等着恢復,而將其掛起的線程等待鎖資源)
  • suspend()的替代方案
                應在本身的Thread類中置入一個標誌,指出線程應該活動仍是掛起。若標誌指出線程應該掛起,便用wait()命其進入等待狀態。若標誌指出線程應當恢復,則用一個notify()從新啓動線程。
 
  •  在實際開發中,調用start()啓動線程的方法已再也不推薦。應該從運行機制上減小須要並行運行的任務數量。若是有不少任務,要爲每一個任務建立一個獨立線程的編程所付出的代價太大了。可使用線程池來解決這個問題。
  •     線程信息查看工具:JDK自帶的Jconsole
相關文章
相關標籤/搜索