java併發編程 | 線程詳解

我的網站:chenmingyu.top/concurrent-…java

進程與線程

進程:操做系統在運行一個程序的時候就會爲其建立一個進程(好比一個java程序),進程是資源分配的最小單位,一個進程包含多個線程編程

線程:線程是cpu調度的最小單位,每一個線程擁有各自的計數器,對戰和局部變量等屬性,而且能過訪問共享的內存變量安全

線程的狀態

java線程的生命週期總共包括6個階段:多線程

  1. 初始狀態:線程被建立,可是尚未調用start()方法
  2. 運行狀態:java中將就緒狀態和運行狀態統稱爲運行狀態
  3. 阻塞狀態:線程阻塞,線程等待進入synchronized修飾的代碼塊或方法
  4. 等待狀態:線程進入等待狀態,須要調用notify()notifyAll()進行喚醒
  5. 超時等待狀態:線程進入等待狀態,在指定時間後自行返回
  6. 終止狀態:線程執行完畢

在某一時刻,線程只能處於其中的一個狀態併發

線程初始化後,調用start()方法變爲運行狀態,調用wait()join()等方法,線程由運行狀態變爲等待狀態,調用notify()notifyAll()等方法,線程由等待狀態變成運行狀態,超時等待狀態就是在等待狀態基礎上加了時間限制,超過規定時間,自動更改成運行狀態,當須要執行同步方法時,若是沒有得到鎖,這時線程狀態就變爲阻塞狀態,直到獲取到鎖,變爲運行狀態,當執行完線程的run()方法後,線程變爲終止狀態ide

建立線程

建立線程有三種方式網站

  1. 繼承Thread
  2. 實現Runnable接口
  3. 實現Callable接口

繼承Threadthis

/** * @author: chenmingyu * @date: 2019/4/8 15:13 * @description: 繼承Thread類 */
public class ThreadTest extends Thread{

    @Override
    public void run() {
        IntStream.range(0,10).forEach(i->{
            System.out.println(this.getName()+":"+i);
        });
    }

    public static void main(String[] args) {
        Thread thread = new ThreadTest();
        thread.start();
    }
}
複製代碼
實現Runnable接口
/** * @author: chenmingyu * @date: 2019/4/8 15:18 * @description: 實現Runnable接口 */
public class RunnableTest implements Runnable {

    @Override
    public void run() {
        IntStream.range(0,10).forEach(i->{
            System.out.println(Thread.currentThread().getName()+":"+i);
        });
    }

    public static void main(String[] args) {
        Runnable runnable = new RunnableTest();
        new Thread(runnable,"RunnableTest").start();
    }
}
複製代碼
實現Callable接口
/** * @author: chenmingyu * @date: 2019/4/8 15:23 * @description: 實現Callable接口 */
public class CallableTest implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        IntStream.range(0,10).forEach(i->{
            System.out.println(Thread.currentThread().getName()+":"+i);
        });
        return -1;
    }

    public static void main(String[] args) throws Exception {
        Callable callable = new CallableTest();
        FutureTask futureTask = new FutureTask(callable);
        new Thread(futureTask,"future").start();
        System.out.println("result:"+futureTask.get());
    }
}
複製代碼

線程間通訊

不安全的線程暫停,恢復,中止操做

Thread提供的過時方法能夠實現對線程進行暫停suspend(),恢復resume(),中止stop()的操做spa

例:建立一個線程,run()中循環輸出當前時間,在main()方法中對新建線程進行暫停,恢復,中止的操做操作系統

/** * @author: chenmingyu * @date: 2019/4/8 15:51 * @description: 線程的暫停,恢復,中止 */
public class OperationThread implements Runnable{

    @Override
    public void run() {
        while (true){
            try {
                TimeUnit.SECONDS.sleep(1L);
                System.out.println(Thread.currentThread().getName()+"運行中:"+LocalTime.now());
            }catch (InterruptedException e){
                System.err.println(e.getMessage());
            }
        }
    }

    public static void main(String[] args) throws Exception{
        Runnable runnable = new OperationThread();
        Thread thread = new Thread(runnable,"operationThread");
        /** * 啓動,輸出當前時間 */
        thread.start();
        TimeUnit.SECONDS.sleep(3L);

        /** * 線程暫停,不在輸出當前時間 */
        System.out.println("此處暫停:"+LocalTime.now());
        thread.suspend();
        TimeUnit.SECONDS.sleep(3L);

        /** * 線程恢復,繼續輸出當前時間 */
        System.out.println("此處恢復:"+LocalTime.now());
        thread.resume();
        TimeUnit.SECONDS.sleep(3L);

        /** * 線程中止,不在輸出當前時間 */
        thread.stop();
        System.out.println("此處中止:"+LocalTime.now());
        TimeUnit.SECONDS.sleep(3L);
    }
}
複製代碼

輸出

由於是過時方法,因此不推薦使用,使用suspend()方法後,線程不會釋放已經佔有的資源,就進入睡眠狀態,容易引起死鎖問題,而使用stop()方法終結一個線程是不會保證線程的資源正常釋放的,可能會致使程序異常

安全的線程暫停,恢復(等待/通知機制)

線程安全的暫停,恢復操做可使用等待/通知機制代替

安全的線程暫停,恢復(等待/通知機制)

相關方法:

方法名 描述
notify() 通知一個在對象上等待的線程,使其重wait()方法中返回,前提是該線程得到了對象的鎖
notifyAll() 通知全部等待在該對象上的線程
wait() 調用該方法線程進入等待狀態,只有等待另外線程的通知或被中斷纔會返回,調用該方法會釋放對象的鎖
wait(long) 超時等待一段時間(毫秒),若是超過期間就返回
wait(long,int) 對於超時時間耕細粒度的控制,能夠達到納秒

例:建立一個名爲waitThread的線程,在run()方法,使用中使用synchronized進行加鎖,以變量flag爲條件進行while循環,在循環中調用LOCK.wait()方法,此時會釋放對象鎖,由main()方法得到鎖,調用LOCK.notify()方法通知LOCK對象上等待的waitThread線程,將其置爲阻塞狀態,並將變量flag置爲true,當waitThread線程再次獲取對象鎖以後繼續執行餘下代碼

/** * @author: chenmingyu * @date: 2019/4/8 20:00 * @description: wait/notify */
public class WaitNotifyTest {

    private static Object LOCK = new Object();
    private static Boolean FLAG = Boolean.TRUE;


    public static void main(String[] args) throws InterruptedException{
        Runnable r = new WaitThread();
        new Thread(r,"waitThread").start();
        TimeUnit.SECONDS.sleep(1L);
        synchronized (LOCK){
            System.out.println(Thread.currentThread().getName()+"喚醒waitThread線程:"+LocalTime.now());
            /** * 線程狀態由等待狀態變爲阻塞狀態 */
            LOCK.notify();
            /** * 只有當前線程釋放對象鎖,waitThread獲取到LOCK對象的鎖以後纔會從wait()方法中返回 */
            TimeUnit.SECONDS.sleep(2L);
            FLAG = Boolean.FALSE;
        }
    }

    public static class WaitThread implements Runnable {
        @Override
        public void run() {
            /** * 加鎖 */
            synchronized (LOCK){
                while (FLAG){
                    try {
                        System.out.println(Thread.currentThread().getName()+"運行中:"+LocalTime.now());
                        /** * 線程狀態變爲等待狀態 */
                        LOCK.wait();
                        /** * 再次得到對象鎖以後,纔會執行 */
                        System.out.println(Thread.currentThread().getName()+"被喚醒:"+LocalTime.now());
                    }catch (InterruptedException e){
                        System.err.println(e.getMessage());
                    }
                }
            }
            System.out.println(Thread.currentThread().getName()+"即將中止:"+LocalTime.now());
        }
    }
}
複製代碼

輸出

能夠看到在 mian線程調用 LOCK.notify()方法後,沉睡了2s才釋放對象鎖, waitThread線程在得到對象鎖以後執行餘下代碼

安全的線程中止操做(中斷標識)

線程的安全中止操做是利用線程的中斷標識來實現,線程的中斷屬性表示一個運行中的線程是否被其餘線程進行了中斷操做,其餘線程經過調用該線程的interrupt()方法對其進行中斷操做,而該線程經過檢查自身是否被中斷來進行響應,當一個線程被中斷可使用Thread.interrupted()方法對當前線程的中斷標識位進行復位

例:新建一個線程,run方法中使用Thread.currentThread().isInterrupted()是否中斷做爲判斷條件,在主線程中使用thread.interrupt()方法對子線程進行中斷操做,用來達到終止線程的操做,這種方式會讓子線程能夠去清理資源或一些別的操做,而使用stop()方法則會會直接終止線程

/** * @author: chenmingyu * @date: 2019/4/8 20:47 * @description: 中斷 */
public class InterruptTest {
    
    public static void main(String[] args) throws InterruptedException {
        Runnable r = new StopThread();
        Thread thread = new Thread(r,"stopThread");
        thread.start();
        TimeUnit.SECONDS.sleep(1L);
        System.out.println(Thread.currentThread().getName()+"對stopThread線程進行中斷:"+LocalTime.now());
        thread.interrupt();
    }

    public static class StopThread implements Runnable {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName()+"運行中:"+LocalTime.now());
            }
            System.out.println(Thread.currentThread().getName()+"中止:"+LocalTime.now());
        }
    }
}
複製代碼
Thread.join()

Thread.join()做用是等待該線程終止

好比在主線程中新建一個子線程,調用子線程的join()方法,那麼在子線程未執行完時,主線程的狀態是阻塞狀態,只有當子線程執行結束,主線程纔會繼續往下執行

方法名 做用
join() 調用A線程的join()方法後,那麼當前線程須要等待A線程終止,才能夠繼續執行
join(long) join()方法的基礎上增長了時間限制(毫秒),超出時間後,不管A線程是否執行完,當前線程都進入就緒狀態,從新等待cpu調用
join(long,int) join(long)方法基礎上,時間控制上更加嚴謹,時間細粒度爲納秒(Long毫秒+int納秒)

例:循環建立子線程,在main線程中調用子線程的join()方法,在子線程中輸出了一句日誌

/** * @author: chenmingyu * @date: 2019/4/9 20:53 * @description: thread.join(); */
public class JoinThreadTest {

    public static void main(String[] args) throws InterruptedException{

        IntStream.range(0, 5).forEach(i -> {
            try {
                Runnable runnable = new JoinThread();
                Thread thread = new Thread(runnable,"joinThread");
                thread.start();
                thread.join();
                System.out.println(Thread.currentThread().getName() + "運行中: " + LocalTime.now());
            } catch (InterruptedException e) {
                System.err.println(e.getMessage());
            }
            System.out.println("------- 分隔符 ------- ");
        });
    }

    public static class JoinThread implements Runnable {
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(1L);
                System.out.println(Thread.currentThread().getName() + "運行中: " + LocalTime.now());
            } catch (InterruptedException e) {
                System.err.println(e.getMessage());
            }
        }
    }
}
複製代碼

輸出

每次循環都是主線程等待子線程終止,在子線程執行完以後主線程纔會繼續執行

thread.join()源碼

調用方線程(調用join方法的線程)執行等待操做,直到被調用的線程(join方法所屬的線程)結束,再被喚醒

public final void join() throws InterruptedException {
    join(0);
}
複製代碼

join(0)方法

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}
複製代碼

join()方法實現等待實際上是調用了wait()方法,isAlive()方法的做用是監測子線程是否終止,若是終止或者超過了指定時間,代碼就繼續往下執行,不然就繼續等待,知道條件知足

ThreadLocal

ThreadLocal,叫線程變量,是一個以ThreadLocal對象爲鍵,任意對象爲值的存儲結構,ThreadLocal類型的變量在每一個線程中是獨立的,在多線程環境下不會相互影響

/** * @author: chenmingyu * @date: 2019/4/10 17:56 * @description: ThreadLocal */
public class ThreadLocalTest {

    private static String STATE;
    private static ThreadLocal<String> STRING_THREAD_LOCAL = new InheritableThreadLocal<>();


    public static void main(String[] args) throws InterruptedException{

        STATE = "未重置";
        STRING_THREAD_LOCAL.set("未重置");

        Thread thread = new Thread(() ->
        {
            STATE = "已重置";
            STRING_THREAD_LOCAL.set("已重置");
            System.out.println(Thread.currentThread().getName() + " : 變量已重置");
        });
        thread.start();
        thread.join();

        System.out.println(Thread.currentThread().getName() + "STATE : " + STATE);
        System.out.println(Thread.currentThread().getName() + "STRING_THREAD_LOCAL : " + STRING_THREAD_LOCAL.get());
    }
}
複製代碼

輸出

ThreadLocal<String>類型的變量STRING_THREAD_LOCAL未被子線程修改

參考:java併發編程的藝術

相關文章
相關標籤/搜索