Thinking in Java--Java多執行緒學習筆記(1)

acm_lkl發表於2020-04-04

這幾天開始學習java多執行緒併發程式設計的內容了,以前也學習過多執行緒的知識,但是總是覺得學的不是很清楚;希望這一次學習《java程式設計思想》能讓自己對併發,多執行緒的概念有一個更加深入的瞭解。這一章估計要寫好幾篇部落格了,這篇部落格是對於基礎的一個總結,主要內容是對啟動一個執行緒的幾種方式和對執行緒一些操作函式的總結。

首先來了解一下多執行緒的概念,多執行緒看起來同一時刻在同時執行多個任務,但是從作業系統的層面來講只是讓多個任務以極快的速度進行切換而已,一個時刻實際上還是隻有一個任務在cpu上執行的。Java中的多執行緒靠的是Thread類實現的,我們將任務交給Thread類的物件,然後它就會以多執行緒的方式來執行我們的任務。Thread類中有一個run()函式,這個函式體中放我們需要執行的任務。
我們可以有三種方式來啟動一個執行緒。第一是直接繼承Thread類,實現裡面的run()方法;這種方法最簡單,但是這樣就不能再繼承別的類了。第二種方法是實現Runnable介面,我們也需要實現裡面的run()方法;但是現在它還是沒有任何的執行緒能力的,要實現執行緒行為,你必須顯示的將一個任務附線上程上實現,具體來說就是將一個Runnable物件傳給一個Thread物件。第三種方法是實現Callable介面,這個介面的內容和Runnable介面並沒有什麼不同,不同點在於Callable介面是可以有返回值的,我們可以顯式的捕獲這個返回值。

///通過繼承Thread實現一個執行緒類
public class FirstThread extends Thread{

    private int i;
    public void run(){
        for(int i=0;i<10;i++){
            ///getName()是Thread類的方法,因為繼承了Thread類
            ///所以可以直接呼叫
            System.out.println(getName()+" "+i);
        }
    }
    public static void main(String[] args){
        for(int i=0;i<10;i++){

            //一般情況可以通過Thread.currentThread取得當前的執行緒
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==1){
                ExecutorService exec = Executors.newCachedThreadPool();
               exec.execute(new FirstThread()); //新增一個新任務
               exec.shutdown(); ///防止後面再有新任務被新增進來
            }
        }
    }/*Output
        main 0
        main 1
        main 2
        Thread-0 0
        Thread-0 1
        Thread-0 2
        Thread-0 3
        Thread-0 4
        Thread-0 5
        Thread-0 6
        Thread-0 7
        Thread-0 8
        Thread-0 9
        main 3
        main 4
        main 5
        main 6
        main 7
        main 8
        main 9
    */
}
package lkl;

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

//通過實現Runnable介面來實現一個執行緒類
public class SecondThread implements Runnable{

    private int i;

    ///同樣要實現run()方法
    public void run(){
        for(int i=0; i<10 ;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }

    public static void main(String[] args){

        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==1){
                ExecutorService exec = Executors.newCachedThreadPool();
                exec.execute(new Thread(new SecondThread(),"新執行緒1"));
                exec.shutdown();
            }
        }/*
        main 0
        main 1
        pool-1-thread-1 0
        pool-1-thread-1 1
        pool-1-thread-1 2
        pool-1-thread-1 3
        pool-1-thread-1 4
        pool-1-thread-1 5
        pool-1-thread-1 6
        pool-1-thread-1 7
        pool-1-thread-1 8
        pool-1-thread-1 9
        main 2
        main 3
        main 4
        main 5
        main 6
        main 7
        main 8
        main 9
        */
    }
}
package lkl;

//通過繼承Callable介面來實現執行緒類
//Callable介面和FutureTask介面接合使用
//我們使用FutureTask對Callable進行封裝,然後
//就可以通過FutureTask物件獲得執行緒的返回值,
///在這種情況下,也可以捕獲執行緒丟擲的異常。
//另外注意:這兩個類都是有泛型限制的,具體的型別和call()方法的返回值一樣
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class ThirdThread implements Callable<Integer>{

    private int i;

    ///實現call()方法,作為執行緒執行體
    public Integer call(){
        for(i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        return i;
    }

    public static void main(String[] args){
        ThirdThread rt = new ThirdThread();

        //使用FutureTask來包裝Callable物件
        FutureTask<Integer> task = new FutureTask(rt);
        for(int i=0; i<10;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==1){
                ExecutorService exec = Executors.newCachedThreadPool();
                exec.execute(task);
                exec.shutdown();
            }
        }
        try{
            //獲取執行緒返回值,如果執行緒還沒執行完,則會阻塞直到取得返回值
            System.out.println("執行緒的返回值:"+task.get());
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }/*
            main 0
            main 1
            pool-1-thread-1 0
            pool-1-thread-1 1
            pool-1-thread-1 2
            pool-1-thread-1 3
            main 2
            main 3
            main 4
            main 5
            main 6
            main 7
            main 8
            main 9
            pool-1-thread-1 4
            pool-1-thread-1 5
            pool-1-thread-1 6
            pool-1-thread-1 7
            pool-1-thread-1 8
            pool-1-thread-1 9
            執行緒的返回值:10
    */
}

然後在上面的程式碼中我們看到了一個Executor物件,這裡解釋下:Executor(執行器)是從Java5開始提供的一個管理Thread物件的類,Executor物件在客戶端和任務執行之間提供了一個間階層;與客戶端直接執行任務不同,任務由這個中介物件執行。Executor允許你管理非同步任務的執行,而無需顯示的管理執行緒的生命週期。具體的步驟是建立一個ExecutorService物件,然後將執行緒放入這個物件中去執行,對應的ExecutorService物件三種:第一是前面用的CachedThreadPool,會在程式中建立與所需數量相同的執行緒。第二是FixedThreadPool,可以一次性建立指定數目的執行緒(通過在構造器中指定數目實現)。第三種是SingleThreadExecutor,這種執行器每次只能執行一個執行緒;如果向其提交了多個任務,那麼這些任務將排隊,依次執行。

下面簡要的介紹一下幾種控制執行緒的方法(函式)
(1).join()方法
join()方法意味著讓一個執行緒等待另一個執行緒。如果我在現在的執行緒上呼叫t.join(),那麼當前執行緒就會阻塞,直到t執行緒完成為止;join()有一個過載的方法可以指定阻塞的時間。

package lkl;
///join()方法的使用
public class JoinThread extends Thread{

    public JoinThread(String name){
        super(name);
    }

    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(getName()+" "+i);
        }
    }

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

        for(int i=0; i<10;i++){
            if(i==1){
                JoinThread jt = new JoinThread("被Join的執行緒");
                jt.start();

                //main執行緒呼叫了jt執行緒的Join()方法,則main執行緒
                //只有等jt結束後,main執行緒才會繼續進行
                jt.join();
            }
            System.out.println(Thread.currentThread().getName());
        }
    }
}

(2).setDaemon(true)方法
這個方法意味著將當前執行緒設為後臺執行緒。後臺執行緒是在後臺執行的,它的任務是為其它的執行緒提供服務(如JVM的垃圾回收機制就是典型的後臺執行緒)。後臺執行緒的特點在於如果所有的前臺執行緒都死亡了,那麼後臺執行緒就會自動死亡。

package lkl;
public class DaemonThread extends Thread{

    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(getName()+" "+i);
        }
    }

    public static void main(String[] args) throws Exception{
        DaemonThread dt = new DaemonThread();
        dt.setDaemon(true); //設為後臺執行緒,需要在啟動之前設定
        dt.start();
        for(int i=0; i<10;i++){
        System.out.println(Thread.currentThread().getName()+" "+i);
    }
    //可以看到在main執行緒結束自後,Daemon執行緒也結束了
  }
}
package lkl;

import java.util.concurrent.*;
//後臺執行緒會在不執行finally子句的前提下
//就會終止其run()方法

class Adaemon implements Runnable{

    public  void run(){
        try{
            System.out.println("Starting ADamon");
            TimeUnit.SECONDS.sleep(1);
        }
        catch(InterruptedException e){
            System.out.println("err");
        }
        finally{
            System.out.println("This should always run?");
        }
    }
}

public class DaemonsDontRunFinally {

    public static void main(String[] args){
        Thread t = new Thread(new Adaemon());
        t.setDaemon(true);
        t.start();
    }
}

(3).執行緒睡眠sleep()
sleep()的功能是很簡單的,只是簡單的讓當前正在執行的執行緒一段時間(我們可以指定這段時間),並進入阻塞狀態。sleep()是Thread類的一個靜態方法。

(4).執行緒讓步yield()
yield()和sleep()有點類似,也是讓當前正在執行的執行緒暫停,但是它不會阻塞當前執行緒,而是讓其進入就緒狀態。然後在進行一次排程,調取一個優先順序相同或更高的執行緒進行執行。指的注意的是yield()只是一個建議性的方法,它並不能完全保證當前執行緒讓步,所以我們應該更多的使用sleep()而不是yield()。

(5).setPriority()改變執行緒的優先順序
一般來說優先順序越高的執行緒其獲得執行的機會就越大,但這是依賴於具體的作業系統的;而且不同的作業系統的優先順序的級別劃分也是不同的。所以Java提供了三個靜態常量來表示優先順序:MAX_PRIORITY 表示最高優先順序。MIN_PRIORITY表示最低優先順序。NORM_PRIORITY表示一般的優先順序;如果我們要設定執行緒的優先順序,我們應該儘量選擇這三個常量,而不是其它的數字。

相關文章