1. 程式人生 > >多執行緒的三種建立方式

多執行緒的三種建立方式

繼承Thread類建立執行緒類

public class Thread extends Object implements Runnable

  1. 定義Thread類的子類,並重寫其run()方法
  2. 建立Thread子類的例項,即建立了執行緒物件
  3. 呼叫執行緒物件的start()方法啟動執行緒
public class FirstThread extends Thread {
    public void run(){
        for(int i=0;i<100;i++){
            /*
             * Thread類已經繼承了Object
             * Object類建立了name選項 並且有其getName(),setName()方法
             * 在繼承Thread的類裡面使用時只需要用this引用
            */
System.out.println(this.getName()+" "+i); } } public static void main(String[] args) { for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+" "+i); if(i==20){ new FirstThread().start(); new
FirstThread().start(); } } } }

Thread類已經繼承了Object
Object類建立了name選項 並且有其getName(),setName()方法
在繼承Thread的類裡面使用時只需要用this引用

上面兩個副執行緒和主執行緒隨機切換,又因為使用的是繼承Thread的類所以兩個副執行緒不能共享資源

start()方法呼叫後並不是立即執行多執行緒程式碼,而是使得該執行緒程式設計可執行狀態,什麼時候執行是由作業系統決定的

實現Runnable介面建立執行緒類

public Thread()
public Thread(Runnable target)
public Thread(Runnable target,String name)

  1. 定義Runnable介面的實現類,並重寫該介面的run()方法
  2. 建立Runnable實現類的例項,並以此作為Thread的target來建立Thread物件,該Thread物件才是真正的執行緒物件。
public class SecondThread implements Runnable {
    public void run(){
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);

            if(i==20){
                SecondThread st=new SecondThread();
                //通過new Thread(target,name)建立執行緒
                new Thread(st,"新執行緒1").start();
                new Thread(st,"新執行緒2").start();
            }
        }
    }
}

上面的結果是兩個副執行緒和主執行緒隨機切換,但是並沒有共享資源,因為他們根本沒有能用來共享的資源。

start()方法呼叫後並不是立即執行多執行緒程式碼,而是使得該執行緒程式設計可執行狀態,什麼時候執行是由作業系統決定的

繼承Thread類和建立Runnable介面的共享資源詳解

在只有可以用來共享的資源時候,也就是同用一個例項化物件。兩個建立方式在共享資源時才會有所區別,否則它們都不會共享資源共享資源通常用private static 修飾符來修飾。

class Thread1 extends Thread{  
    private int count=5;  
    private String name;  
    public Thread1(String name) {  
       this.name=name;  
    }  
    public void run() {  
        for (int i = 0; i < 5; i++) {  
            System.out.println(name + "執行  count= " + count--);  
            try {  
                sleep((int) Math.random() * 10);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  

    }  
}  

public class Main {  

    public static void main(String[] args) {  
        Thread1 mTh1=new Thread1("A");  
        Thread1 mTh2=new Thread1("B");  
        mTh1.start();  
        mTh2.start();  

    }  

}  

B執行 count= 5
A執行 count= 5
B執行 count= 4
B執行 count= 3
B執行 count= 2
B執行 count= 1
A執行 count= 4
A執行 count= 3
A執行 count= 2
A執行 count= 1

正是因為有了private int count=5;一句才有了共享資源,但這是繼承Thread類的子類,並不能共享資源

class Thread2 implements Runnable{  
    private int count=15;  
    public void run() {  
          for (int i = 0; i < 5; i++) {  
              System.out.println(Thread.currentThread().getName() + "執行  count= " + count--);  
                try {  
                    Thread.sleep((int) Math.random() * 10);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  

    }  

}  
public class Main {  

    public static void main(String[] args) {  

        Thread2 my = new Thread2();  
            new Thread(my, "C").start();//同一個mt,但是在Thread中就不可以,如果用同一個例項化物件mt,就會出現異常     
            new Thread(my, "D").start();  
            new Thread(my, "E").start();  
    }  

}  

C執行 count= 15
D執行 count= 14
E執行 count= 13
D執行 count= 12
D執行 count= 10
D執行 count= 9
D執行 count= 8
C執行 count= 11
E執行 count= 12
C執行 count= 7
E執行 count= 6
C執行 count= 5
E執行 count= 4
C執行 count= 3
E執行 count= 2

同樣的正是因為有了private int count=15這個共同的例項化物件,實現Runnable的類才可以共享資源

那麼為什麼繼承Thread類的子類實現Runable介面的類在共享資源時有區別呢?
因為Java中只能支援單繼承,單繼承特點意味著只能有一個子類去繼承
而Runnabl介面後可以跟好多類,便可以進行多個執行緒共享一個資源的操作

使用Callable和Future建立執行緒

Callable怎麼看起來都像Runnable介面的增強版,Callable有一個call()方法相當於Runnable的run()方法,但是功能卻更加強大:

call()方法可以有返回值
call()方法可以宣告丟擲異常

Callable介面有泛型限制,Callable接口裡的泛型形參型別與call()方法的返回值型別相同。
而且Callable介面是函式式介面,因此可使用Lambda表示式建立Callable物件
Runnable介面也是函式式介面,因此也可以使用Lambda表示式建立Runnable物件

  1. 建立Callable介面的實現類,並實現call()方法,該call()方法將作為執行緒執行體,再建立Callable實現類的例項
  2. 使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法
  3. 使用FutureTask類物件作為Thread物件的target建立並啟動新執行緒
  4. 呼叫FutureTask物件的get()方法來獲得子執行緒結束後的返回值
public class ThirdThread implements Callable<Integer> {
    public Integer call(){
        int i=0;
        for(;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        return i;
    }

    public static void main(String[] args){
        ThirdThread tt=new ThirdThread();
        FutureTask<Integer> task=new FutureTask<>(tt);
        Thread t=new Thread(task,"有返回值的執行緒");
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20){
                t.start();
            }
        }
        try{
            System.out.println("返回值是:"+task.get());
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

使用Lambda表示式的Callable和Future建立的執行緒

public class ThirdThread{
    public static void main(String[] args){
        ThirdThread tt=new ThirdThread();
        //先使用Lambda表示式建立Callable<Integer>物件
        //使用FutureTask封裝Callable物件
        FutureTask<Integer> task=new FutureTask<Integer>((Callable<Integer>)()->{
            int i=0;
            for(;i<100;i++){
                System.out.println(Thread.currentThread().getName()+"的迴圈變數i的值:"+i);
            }
            return i;
        });

        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"的迴圈變數i的值:"+i);
            if(i==20){
                new Thread(task,"有返回值的執行緒").start();
            }
        }
        try{
            System.out.println("子執行緒的返回值"+task.get());
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}