多線程核心技術(1)-線程的基本方法

進程和線程

    瞭解多線程首先要了解進程和線程的概念,在操做系統裏,進程是資源分配最小單位,通常狀況下一個應用就會在計算機系統內開啓一個進程,線程能夠理解爲進程中多個獨立運行的子任務,是操做系統可以進行調度運算的最小單位,可是線程不擁有資源,只能共享進程中的數據,因此多個線程對進程中某個數據同時進行修改時,就會產生線程安全問題。java

    因爲一個進程中容許存在多個線程,因此在多線程中,如何處理線程併發和線程之間通訊的問題,是學習多線程編程的重點。 瞭解多線程首先要了解進程和線程的概念,在操做系統裏,進程是資源分配最小單位,通常狀況下一個應用就會在計算機系統內開啓一個進程,線程能夠理解爲進程中多個獨立運行的子任務,是操做系統可以進行調度運算的最小單位,可是線程不擁有資源,只能共享進程中的數據,因此多個線程對進程中某個數據同時進行修改時,就會產生線程安全問題。因爲一個進程中容許存在多個線程,因此在多線程中,如何處理線程併發和線程之間通訊的問題,是學習多線程編程的重點。編程

多線程的使用

    在java中,建立一個線程通常有兩種方式,繼承Thread類或者實現Runable接口,重寫run方法便可,而後調用start()方法便可以開啓一個線程並執行。若是想要獲取當前線程執行返回值,在jdk1.5之後,能夠經過實現Callable接口,而後藉助FutureTask或者線程池獲得返回值。因爲線程的執行具備隨機性,因此線程的開啓順序並不意味線程的執行順序。安全

繼承Thread建立一個線程

/** * 繼承Thread 重寫Run方法 * 類是單繼承的,生產環境中若是此類無需實現其餘接口 可以使用這種方法建立線程 * User: lijinpeng * Created by Shanghai on 2019/4/13. */
@Slf4j
public class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        log.info("Hi,I am a thread extends Thread,My name is:{}", this.getName());
    }
}

複製代碼

實現Runable接口建立一個線程

/** * 實現Runnable接口 * 類容許有多個接口實現 生產中通常使用這種方式建立線程 * 線程的開啓還須要藉助於Thread實現 * User: lijinpeng * Created by Shanghai on 2019/4/13. */
@Slf4j
@Getter
public class ThreadRunable implements Runnable {

    private String name;

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

    public void run() {
        log.info("Hi,I am a thread implements Runnable,My name is:{}", this.getName());
    }
}
複製代碼

實現Callable 接口 獲取線程執行結果

/** * 實現Callable接口建立獲取具備返回值的線程 * 線程使用須要藉助FutureTask和Thread,或者使用線程池 * User: lijinpeng * Created by Shanghai on 2019/4/13. */
@Slf4j
public class CallableThread implements Callable<Integer> {

    private AtomicInteger seed;
    @Getter
    private String name;

    public CallableThread(String name, AtomicInteger seed) {
        this.name = name;
        this.seed = seed;
    }

    public Integer call() throws Exception {
        //使用併發安全的原子類生成一個整數
        Integer value = seed.getAndIncrement();
        log.info("I am thread implements Callable,my name is:{} my value is:{}", this.name, value);
        return value;
    }
}
複製代碼

驗證三種線程的啓動

/** * User: lijinpeng * Created by Shanghai on 2019/4/13. */
@Slf4j
public class ThreadTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
     threadTest();
     runableTest();
     callableTest();
    }

    public static void threadTest() {
        MyThread threadA = new MyThread("threadA");
        threadA.start();
    }

    public static void runableTest() {
        ThreadRunable runable = new ThreadRunable("threadB");
        //須要藉助Thread來開啓一個新的線程
        Thread threadB = new Thread(runable);
        threadB.start();
    }

    public static void callableTest() throws ExecutionException, InterruptedException {
        AtomicInteger atomic = new AtomicInteger();
        CallableThread threadC1 = new CallableThread("threadC1", atomic);
        CallableThread threadC2 = new CallableThread("threadC2", atomic);
        CallableThread threadC3 = new CallableThread("threadC3", atomic);
        FutureTask<Integer> task1 = new FutureTask<Integer>(threadC1);
        FutureTask<Integer> task2 = new FutureTask<Integer>(threadC2);
        FutureTask<Integer> task3 = new FutureTask<Integer>(threadC3);
        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task2);
        Thread thread3 = new Thread(task3);
        thread1.start();
        thread2.start();
        thread3.start();
        while (task1.isDone()&&task2.isDone()&&task3.isDone())
        {
        }
        log.info(threadC1.getName()+"執行結果:"+String.valueOf(task1.get()));
        log.info(threadC2.getName()+"執行結果:"+String.valueOf(task2.get()));
        log.info(threadC2.getName()+"執行結果:"+String.valueOf(task3.get()));
    }
}
複製代碼

如下是程序執行結果:多線程

結論:併發

  1. 這三種方式均可以開啓一個線程,實現具體開啓線程的任務仍是交給Thread類實現,所以對於RunableCallable來講,最後都是要藉助於Thread開啓線程,類是單繼承的,接口是多實現的,因爲生產環境業務複雜性,一個類可能會有其餘功能,所以通常使用接口實現的方式。
  2. 從上面的線程聲明順序和執行順序結果來看,線程的執行是無序的,CPU執行任務是採用輪詢機制來提升CPU使用率,在線程獲取執行資源進行就緒隊列後 纔會再次被CPU調用,而這個過程跟程序無關。

線程的生命週期

     一個線程的運行一般伴隨着線程的啓動、阻塞、中止等過程,線程啓動能夠經過Thread類的start()方法執行,因爲多線程可能會共享進程數據,阻塞通常發生在等待其餘線程釋放進程某塊資源的過程,當線程執行完畢,能夠自動中止,也能夠經過調用stop()強制終止線程,或者在線程執行過程當中因爲異常致使線程終止,瞭解線程的生命週期是學習多線程最重要的理論基礎。ide

    下圖爲線程的生命週期以及狀態轉換過程函數

新建狀態

    當經過Thread thead=new Thread()建立一個線程的時候,該線程就處於 new 狀態,也叫新建狀態。學習

就緒狀態測試

    當調用thread.start()時,線程就進入了就緒狀態,在該狀態下線程並不會運行,只是表示線程進入可供CPU調用的就緒隊列,具有運行條件。this

運行狀態

    當線程得到了JVM中線程調度器的調度時候,線程就進入運行狀態,會執行重寫的 run方法。

阻塞狀態

    此時的線程仍處於活動狀態,可是因爲某種緣由失去了CPU對其調度權利,具體緣由可分爲如下幾種

  1. 同步阻塞

    此時因爲線程A須要獲取進程的資源1,可是資源1被線程B所持有,必須等待線程B釋放資源1以後,該線程纔會進入資源1的就緒線程池裏,獲取到資源1後,等待被CPU調度器調度再次運行。同步阻塞通常出如今線程等待某項資源的使用權利,在程序中使用鎖機制會產生同步阻塞。

  1. 等待阻塞

    當執行Thread類的wait()join()方法時,會形成當前線程的同步阻塞,wait()會使當前線程暫停運行,而且釋放所擁有的鎖,能夠通該線程要等待的某個類(Object)的notify()或notifyall()方法喚醒當前線程。join()方法會阻塞當前線程,直到線程執行完畢,能夠經過join(time)指定等待的時間,而後喚醒線程。

  1. 其餘阻塞

    調用sleep()方法主動放棄所佔用的CPU資源,這種方式不會釋放該線程所擁有的鎖,或者調用一個阻塞式IO方法、發出了I/O請求,進入這種阻塞狀態。被阻塞的線程會在合適的時候(阻塞解除後)從新進入就緒狀態,從新等待線程調度器再次調度它。

死亡狀態

     當線程執行完run方法時,就會自動終止或者處於死亡狀態,這是線程的正常死亡過程。或者經過顯示調用stop()終止線程,但不安全。還能夠經過拋異常法終止線程。

實例變量與線程安全

多線程訪問進程資源

    在多線程任務應用中若是多個線程執行之間使用了進程的不一樣資源,即運行中不共享任何進程資源,各線程運行不受影響,且不會產生數據安全問題。若是多個線程共享了進程的某塊資源,會同時修改該塊資源數據,產生最終結果與預期結果不一致的狀況,致使線程安全問題。如圖:

主內存與工做內存

    Java內存模型分爲主內存,和工做內存。主內存是全部的線程所共享的,工做內存是每一個線程本身有一個,不是共享的。每條線程還有本身的工做內存,線程的工做內存中保存了被該線程使用到的變量的主內存副本拷貝。線程對變量的全部操做(讀取、賦值),都必須在工做內存中進行,而不能直接讀寫主內存中的變量。不一樣線程之間也沒法直接訪問對方工做內存中的變量,線程間變量值的傳遞均須要經過主內存來完成,線程、主內存、工做內存三者之間的交互關係以下圖:

    線程對主存的操做指令:lock,unlock,read,load,use,assign,store,write操做

  • read-load階段從主內存複製變量到當前工做內存

  • useassign階段執行代碼改變共享變量值

  • storewrite階段用工做內存數據刷新主存對應變量的值。

  • store and write執行時機

    1. ava內存模型規定不容許readloadstorewrite操做之一單獨出現,以上兩個操做必須按順序執行,不必 連續執行,也就是說readload之間、storewrite之間是可插入其餘指令的。
    2. 容許一個線程丟棄它的最近的assign操做,即變量在工做內存中改變了以後必須把該變化同步回主內存。變量在當前線程中改變一次其實就是一次assign,並且不容許丟棄最近的assign,因此一定有一次store and write,又根據第一條read and load 和store and write不能單一出現,因此有一次store and write一定有一次read and load,所以推斷出,變量在當前線程中每一次變化都會執行 read 、 load 、use 、assign、store、write
    3. volatile修飾變量,是在useassign階段保證獲取到的變量永遠是跟主內存變量保持同步

    非線程安全問題

    在多線程環境下useassign是屢次出現的,但此操做並非原子性的,也就是說在線程A執行了readload從主內存 加載過變量C後,此時若是B線程修改了主內存中變量C的值,因爲線程A已經加載過變量C,沒法感知數據已經發生變化,即從線程A的角度來看,工做內存和主內存的變量A已經再也不同步,當線程A使用useassign時,就會出現非線程安全的問題。解決此問題能夠經過使用volatile關鍵字修飾,volatile能夠保證線程每次使用useassign時,都從主內存中拿到最新的數據,並且能夠防止指令重排,但volatile僅僅是保證變量的可見性,沒法使數據加載的幾個步驟是原子操做,因此volatile並不能保證線程安全。

    以下代碼所示:

    多個業務線程訪問用戶餘額balance,最終致使扣款總金額超過了用戶餘額,由線程不安全致使的資損情景.並且每一個業務線程都扣款了兩次,也說明了線程啓動時須要將balance加載到工做內存中,以後該線程基於加載到的balance操做,其餘線程如何改變balance值,對當前業務線程來講都是不可見的。

/** * 業務訂單代扣線程 持續扣費 * User: lijinpeng * Created by Shanghai on 2019/4/13. */
@Slf4j
public class WithHoldPayThread extends Thread {
    //繳費金額
    private Integer amt;
    //業務類型
    private String busiType;

    public WithHoldPayThread(Integer amt, String busiType) {
        this.amt = amt;
        this.busiType = busiType;
    }

    @Override
    public void run() {
        int payTime = 0;
        while (WithHodeTest.balance > 0) {
            synchronized (WithHodeTest.balance) {
                boolean result = false;
                if (WithHodeTest.balance >= amt) {
                    WithHodeTest.balance -= amt;
                    result = true;
                    payTime++;
                }
                log.info("業務:{} 扣款金額:{} 扣款狀態:{}", busiType, amt,result);
            }
        }
        log.info("業務:{} 共繳費:{} 次", busiType, payTime);
    }
}
複製代碼

     測試函數

/** * User: lijinpeng * Created by Shanghai on 2019/4/13. */
public class WithHodeTest {
    //用戶餘額 單位 分
    public static volatile Integer balance=100;

    public static void main(String[] args) {
        WithHoldPayThread phoneFare = new WithHoldPayThread(50, "繳存話費");
        WithHoldPayThread waterFare = new WithHoldPayThread(50, "繳存水費");
        WithHoldPayThread electricFare = new WithHoldPayThread(50, "繳存電費");
        phoneFare.start();
        waterFare.start();
        electricFare.start();
    }
}
複製代碼

     執行結果:

    實驗結果證實,每一個線程的扣款都成功了,這就致使了線程安全問題,解決這個問題最簡單的作法是在run方法裏面加synchronized修飾,而且對balance使用volatile修飾就能夠了。

//用戶餘額 單位 分
    public static  volatile Integer balance=100;
複製代碼
@Override
    public void run() {
        int payTime = 0;
        while (WithHodeTest.balance > 0) {
            synchronized (WithHodeTest.balance) {
                boolean result = false;
                if (WithHodeTest.balance >= amt) {
                    WithHodeTest.balance -= amt;
                    result = true;
                    payTime++;
                }
                log.info("業務:{} 扣款金額:{} 扣款狀態:{}", busiType, amt,result);
            }
        }
        log.info("業務:{} 共繳費:{} 次", busiType, payTime);
    }
複製代碼

執行結果:

線程的基本API

     java線程類Thread提供了線程操做的基本方法,好比判斷線程是否存活的isAlive(),阻塞線程的wait() join(),讓線程休眠的sleep(),中止線程的stop(), 暫停和喚醒線程 的suspendresume等等,有些方法因爲會致使線程不安全或者獨佔資源已被廢棄,因此咱們應該謹慎使用。

getId()

獲取線程的惟一ID,此方法在實際生產中能夠根據線程號跟蹤一個業務的調用具體過程

isAlive()

判斷線程是否處於活動狀態,活動狀態就是線程已啓動,處於運行或者準備運行,還沒有 結束,能夠經過調用 isAlive()方法判斷當前線程任務是否執行完畢。

sleep()

    使正在執行任務的線程在指定的毫秒時間內暫停執行,正在執行的線程指this.currentThread()返回的線程。該狀態下的線程不會釋放鎖資源。

suspend()與resume()

suspend()能夠暫停線程,resume能夠恢復線程繼續執行。

/** * User: lijinpeng * Created by Shanghai on 2019/4/15. */
@Slf4j
public class SuspendAndResumeThread extends Thread {
    @Getter
    private  int number = 0;

    @Override
    public void run() {
        while (true) {
            number++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        SuspendAndResumeThread thread=new SuspendAndResumeThread();
        thread.start();
        Thread.sleep(200);
        thread.suspend();
        //此時線程已經暫停執行
        log.info("A time:{} number={}",System.currentTimeMillis(),thread.getNumber());
        Thread.sleep(200);
        //B time的執行結果應該與A time 一致
        log.info("B time:{} number={}",System.currentTimeMillis(),thread.getNumber());
        //喚醒繼續執行
        thread.resume();
        Thread.sleep(200);
        log.info("C time:{} number={}",System.currentTimeMillis(),thread.getNumber());
    }
}
複製代碼

    執行結果和預期一致

suspend()resume()能夠暫停和恢復線程,用法也很簡單,但是jdk卻廢棄了這種方法,由於這種用法可能會形成"資源獨 佔"的狀況和數據不一致的狀況。

    資源獨佔:如下代碼展現了多個支付業務,在同步代碼中,支付業務withdraw在當前線程中暫停了線程執行,後面的支付業務沒法在進入付款方法的狀況,形成了資源獨佔

/** * User: lijinpeng * Created by Shanghai on 2019/4/17. */
@Slf4j
public class PaymentServiceImpl {
    private int balance;

    public PaymentServiceImpl(int balance) {
        this.balance = balance;
    }

    public synchronized void payService(int amt) {
        String buziType = Thread.currentThread().getName();
        log.info("開始作支付業務.....業務類型:{}", buziType);
        if (balance >= amt) {
            balance -= amt;
            //若是是提現業務 先去餘額扣款 再作支付業務
            if ("withdraw".equals(buziType)) {
                log.info("轉帳業務將當前線程 暫停!");
                //暫停線程 模擬去轉帳 若是轉帳失敗 該線程會一直處於在暫停 並且不會釋放同步鎖
                Thread.currentThread().suspend();
                log.info("轉帳成功!");
            }

            log.info("業務:{} 扣款成功,當前用戶餘額:{}", buziType, balance);
        } else {
            log.info("業務:{} 扣款失敗,當前用戶餘額不足", buziType);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final PaymentServiceImpl paymentService = new PaymentServiceImpl(2000);
        Thread fastpay = new Thread(new Runnable() {
            public void run() {
                paymentService.payService(150);
            }
        }, "fastpay");
        Thread withdraw = new Thread(new Runnable() {
            public void run() {
                paymentService.payService(50);
            }
        }, "withdraw");
        Thread payfor = new Thread(new Runnable() {
            public void run() {
                paymentService.payService(50);
            }
        }, "payfor");
        fastpay.start();
        Thread.sleep(1000);
        withdraw.start();
        Thread.sleep(1000);
        payfor.start();
    }
}
複製代碼

    執行結果:

    從執行結果上來看,fastpay付款業務首先執行,不會受withdraw業務暫停線程的影響,當執行withdraw後,暫停了線程,若是在當前線程中不適用resume喚醒線程 會致使withdraw線程一直佔用payService()方法資源。payfor業務沒法執行,產生資源獨佔的狀況。

    數據不一致:一下代碼驗證了經過ThreadExcuterUtils獲取當前線程名和執行任務,在兩個參數賦值過程當中發生了線程暫停,致使線程名和執行任務不一致的狀況。

/** * User: lijinpeng * Created by Shanghai on 2019/4/17. */
@Getter
@Slf4j
public class ThreadExcuterUtils {
    //當前線程名
    private String localThreadName;
    //當前線程id
    @Setter
    private String localTask;

    public void setThreadInfo(String threadName) {
        this.localThreadName = threadName;
        if ("gc_thread".equals(threadName)) {
            Thread.currentThread().suspend();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final ThreadExcuterUtils utils = new ThreadExcuterUtils();
        new Thread(new Runnable() {
            public void run() {
                utils.setThreadInfo("log_thread");
                utils.setLocalTask("記錄日誌");
            }
        }).start();
        Thread.sleep(1000);
        new Thread(new Runnable() {
            public void run() {
                utils.setThreadInfo("gc_thread");
                utils.setLocalTask("GC垃圾回收");
            }
        }).start();
        Thread.sleep(1000);
        log.info("當前線程:{} 當前任務:{}",utils.getLocalThreadName(),utils.getLocalTask());
    }
}
複製代碼

    執行結果:

​ 從執行結果看,當前線程是gc_thread ,對應的任務應該是 「GC垃圾回收」,可是結果倒是 「記錄日誌」,出現了數據不一致的狀況。

​ 以上兩種狀況可能因爲業務場景設計的不合適或者能夠用其餘線程同步解決該問題,可是對於suspendresume的使用,咱們應該謹慎當心,suspendresume必定要成對使用,避免形成資源獨佔和數據不一致的狀況。

setPriority()

    在操做系統中,線程能夠劃分優先級,優先級較高的線程獲得的CPU的資源越多,也就是CPU優先執行優先級較高的線程對象中的任務,設置線程優先級有助於幫助"線程規劃器"肯定在下一次選擇哪個線程來優先執行,設置線程優先級能夠經過setPriority()設置。在java中,線程優先級分爲1-10 這個10個等級,數字越大優先級越高,若是小於1 或者大於10 會拋出throw new IlleageArgumentException()。

    JDK使用3個常量預先設置線程優先級:

public final static int MIN_PRIORITY = 1;

   /** * The default priority that is assigned to a thread. */
    public final static int NORM_PRIORITY = 5;

    /** * The maximum priority that a thread can have. */
    public final static int MAX_PRIORITY = 10;
複製代碼

一、線程優先級具備繼承性

​ 假如ThreadB extend ThreadA,ThreadA線程優先級爲5,則ThreadB的優先級也爲5

二、線程優先級具備隨機性

    設置線程的優先級,只能確保優先級高的儘量先得到cpu執行資源,可是並不表明着必定比優先級低的線程先得到執行權,具體仍是由cpu決定。

/** * User: lijinpeng * Created by Shanghai on 2019/4/17. */
@Slf4j
public class LowPriorityThread extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
        log.info("☆☆☆☆☆");
    }
}

/** * User: lijinpeng * Created by Shanghai on 2019/4/17. */
@Slf4j
public class HighPriorityThread extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
        log.info("★★★★★");
    }

}

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            HighPriorityThread thread = new HighPriorityThread();
            LowPriorityThread thread1 = new LowPriorityThread();
            thread.setPriority(10);
            thread1.setPriority(1);
            thread.start();
            thread1.start();
        }
    }
複製代碼

    執行結果:

​ 從執行結果看,優先級高的大部分線程老是會先於優先級的線程執行完。

yield()

    yield()表示當前線程可讓出cpu資源,cpu資源是否讓出取決於cpu調度,當cpu資源緊張時候可能會收回該線程執行資源,若是cpu資源充足,有可能就不會回收。使用了yield()該線程任務時間可能會延長。

    如下代碼驗證了不適用yeild和使用yeild線程執行時間對比:

/** * User: lijinpeng * Created by Shanghai on 2019/4/17. */
@Slf4j
public class YieldThread extends Thread {


    @Override
    public void run() {
// Thread.yield();
        long beginTime = System.currentTimeMillis();
        int result = 0;
        for (int i = 0; i < 500000000; i++) {
            result += i;
        }
        long endTime = System.currentTimeMillis();
        log.info("當前線程執行時間:{} 毫秒",endTime-beginTime);
    }

    public static void main(String[] args) {
        YieldThread thread=new YieldThread();
        thread.start();
    }
}
複製代碼

    如下是兩次執行結果對比,會發現使用了yiled()執行方法有所延長,當在CPU資源緊張時,兩個執行耗時差異會更明顯。

    不使用yeild

    使用了yeild

線程的中止

    中止一個線程意味着中止線程正在作的任務,放棄當前操做,可是中止線程操做並不像java種的for循環使用break退出循環這麼簡單,由於線程執行任務過程可能會使用到主內存中的共享數據,一旦放棄該線程任務,其所操做的共享數據處理不當就會產生非線程安全問題。中止一個正在執行中的線程可使用Thread.stop()方法,直接暴力,不過stop方法和suspendresume同樣,都是過時方法,會產生不可預料的結果,目前比較安全的方法是使用Thread.interrupt(),在線程中使用該方法會給線程打一箇中止的標誌,並不會真正的中止,可是咱們能夠經過判斷是否存在這個標誌點在run()進行任務終止。總結一下,java中有三種中止線程的方法:

  1. 線程正常執行完畢 即終止。
  2. 使用stop強制終止線程,若是使用共享資源,會產生不可預料的結果,已廢棄。
  3. 使用interrupt()終端線程。

判斷線程狀態

//會清除線程狀態
 public static boolean interrupted() //不會清楚線程狀態 public boolean isInterrupted() 複製代碼

    第一個方法interrupted()是靜態的,用來判斷執行這段代碼的線程是否處於終止狀態,調用該方法會清除當前線程狀態,好比第一次調用時獲取線程狀態時true,以後清除了該線程狀態,第二次調用就會變成false。(用的不多)

    第二個方法屬於該線程對象,用於判斷當前線程是否處於終止狀態,該方法不會清除線程狀態,因此若是線程狀態不變,屢次調用該方法獲取的線程狀態必定時一致的。(很是經常使用)

異常法中止線程

    咱們能夠經過thread.isInterrupted()來獲取線程狀態,在線程執行過程當中,能夠經過判斷線程狀態來決定線程是否要繼續執行,若是不須要執行能夠直接拋出異常 或者使用return直接返回 終止異常 就能夠達到終止線程的目的。

    以下代碼所示:

    線程類每間隔1s打印一下當前時間,每次打印前先判斷線程狀態,若是線程未終止繼續執行,若是終止就拋出異常!

/** * User: lijinpeng * Created by Shanghai on 2019/4/17. */
@Slf4j
public class LogRecordThread extends Thread {
    @Override
    public void run() {
        try {
            while (true) {

                if (!this.isInterrupted()) {
                    Thread.sleep(1000);
                    log.info("日誌記錄工做執行中!當前時間:{}", System.currentTimeMillis());
                } else {
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException ex) {
            log.error("當前線程已終止,任務終止!");
           // ex.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        LogRecordThread thread = new LogRecordThread();
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}
複製代碼

    運行結果:

    如上所示線程跟預期同樣結束了。此外還可使用return直接返回便可結束線程任務。

sleep中中止線程

    若是線程處於sleep狀態中,調用thread.interrupt()方法會拋出InterruptedException異常,此外還會清除中止狀態值,使之變成false,能夠簡單理解爲,若是在線程sleep的時候調用thread.interrupt()時候,線程狀態值與以前保持一致。

/** * User: lijinpeng * Created by Shanghai on 2019/4/17. */
@Slf4j
public class SleepInterruptThread extends Thread {
    @Override
    public void run() {
        try {
            log.info("開始執行線程任務....");
            Thread.sleep(3000);
            log.info("線程任務執行完畢!");
        } catch (InterruptedException ex) {
          log.error("線程被終止了,此時線程是否中斷標誌:{}",this.isInterrupted());
          ex.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        SleepInterruptThread thread=new SleepInterruptThread();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
複製代碼

    運行結果:

stop暴力中止線程

    直接調用thread.stop就能夠中止一個線程,可是因爲中止線程會致使一些處理工做沒法完成,致使數據不完整,還會和suspend和resume同樣產生數據不一致的狀況,因此jdk已經不建議使用。若是想要中止線程直接經過上面的異常法便可。

小結

    本文主要介紹了線程的三種建立方式,線程的幾種狀態以及狀態轉換、線程安全問題 、線程基本API以及如何中止一個線程,學習掌握了這些其實也就掌握了多線程的核心,雖然線程併發安全問題是咱們關注的重點,但不瞭解上面幾點就沒法真正理解產生以及解決線程安全問題的方法,雖然簡單,仍是要記錄一下。

相關文章
相關標籤/搜索