1. 程式人生 > >java面試常問問題(二)

java面試常問問題(二)

一、執行緒有幾種建立方式?

這是一道比較常見的java執行緒問題,一般就是兩種執行緒建立方式:

  • 繼承Thread類
  • 實現Runnable介面

繼承Thread類

public class MyThread extends Thread{
    private String name;

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

    public void run() {
        for (int i = 0; i < 5; ++i) {
            System.out.println(name + " 執行,i=" + i);
        }
    }
}

class ThreadDemo{
    public static void main(String[] args) {
        MyThread m1 = new MyThread("執行緒1");
        MyThread m2 = new MyThread("執行緒2");

        m1.start();
        m2.start();
    }
}

實現Runnblae介面

class MyRunnable implements Runnable {
    private String name;

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

    @Override
    public void run() {
        for (int i = 0; i < 5; ++i) {
            System.out.println(name + " 執行,i=" + i);
        }
    }
}

class RunnableDemo {
    public static void main(String[] args) {
        MyThread m1 = new MyThread("執行緒1");
        MyThread m2 = new MyThread("執行緒2");

        Thread t1 = new Thread(m1); // 例項化Thread類物件
        Thread t2 = new Thread(m2);

        t1.start(); // 啟動多執行緒
        t2.start();
    }
}

結果:多個執行緒交替執行,執行結果每次都不會一樣

執行緒1 執行,i=0
執行緒2 執行,i=0
執行緒1 執行,i=1
執行緒2 執行,i=1
執行緒1 執行,i=2
執行緒2 執行,i=2
執行緒1 執行,i=3
執行緒2 執行,i=3
執行緒1 執行,i=4
執行緒2 執行,i=4

從Thread類的定義可以發現,Thread也是Runnable介面的子類,但是Thread類並沒有實現Runnable介面的run()方法,下面是Thread類的定義:

Private Runnable target; 
public Thread(Runnable target,String name){ 
    init(null,target,name,0); 
} 
private void init(ThreadGroup g,Runnable target,String name,long stackSize){ 
    ... 
    this.target=target; 
} 
public void run(){ 
    if(target!=null){ 
        target.run(); 
    } 
}

從定義中可以發現,在 Thread 類中的 run() 方法呼叫的是 Runnable 介面中的 run() 方法,也就是說此方法是由 Runnable 子類完成的,所以如果要通過繼承 Thread 類實現多執行緒,則必須覆寫 run()。

相比較而言Runnable()介面實現更好,因為實現介面的方式比繼承類的方式更加靈活,也能減少程式減少程式之間的耦合度。如果一個類繼承Thread類,則不適合於多個執行緒共享資源,而實現了 Runnable 介面,就可以方便的實現資源的共享。

二、start()方法和run()方法的區別?

  • 只有呼叫了start()方法,才會表現出多執行緒的特性,不同執行緒裡面的run()方法裡面的程式碼交替執行。
  • 只是呼叫run()方法,那麼程式碼還是會同步執行,必須等待一個執行緒的run()方法裡面的程式碼全部執行完畢後,另一個執行緒才可以執行run()方法裡面的程式碼。

三、Runnable介面和Callable介面的區別?

  • Runnable介面中的run()方法的返回值是void,它就是純粹執行run()方法中的程式碼而已
  • Callable介面中的call()方法是有返回值的,該返回值的型別是一個泛型號,和Future、FutureTask配合使用,可以獲取非同步執行結果。

四、執行緒的狀態有哪些?

要想實現多執行緒,必須在主執行緒中建立新的執行緒物件。任何執行緒一般具有5種狀態,即建立,就緒,執行,阻塞,終止。下面分別介紹一下這幾種狀態:

  • 建立狀態

在程式中用構造方法建立了一個執行緒物件後,新的執行緒物件便處於新建狀態,此時它已經有了相應的記憶體空間和其他資源,但還處於不可執行狀態。新建一個執行緒物件可採用Thread 類的構造方法來實現,例如 “Thread thread=new Thread()”。

  • 就緒狀態

新建執行緒物件後,呼叫該執行緒的 start() 方法就可以啟動執行緒。當執行緒啟動時,執行緒進入就緒狀態。此時,執行緒將進入執行緒佇列排隊,等待 CPU 服務,這表明它已經具備了執行條件。

  • 執行狀態

當就緒狀態被呼叫並獲得處理器資源時,執行緒就進入了執行狀態。此時,自動呼叫該執行緒物件的 run() 方法。run() 方法定義該執行緒的操作和功能。

  • 阻塞狀態

一個正在執行的執行緒在某些特殊情況下,如被人為掛起或需要執行耗時的輸入/輸出操作,會讓 CPU 暫時中止自己的執行,進入阻塞狀態。在可執行狀態下,如果呼叫sleep(),suspend(),wait() 等方法,執行緒都將進入阻塞狀態,發生阻塞時執行緒不能進入排隊佇列,只有當引起阻塞的原因被消除後,執行緒才可以轉入就緒狀態。

  • 死亡狀態

執行緒呼叫 stop() 方法時或 run() 方法執行結束後,即處於死亡狀態。處於死亡狀態的執行緒不具有繼續執行的能力。

五、Java 程式每次執行至少啟動幾個執行緒?

至少啟動兩個執行緒,每當使用 Java 命令執行一個類時,實際上都會啟動一個 JVM,每一個JVM實際上就是在作業系統中啟動一個執行緒,Java 本身具備了垃圾的收集機制。所以在 Java 執行時至少會啟動兩個執行緒,一個是 main 執行緒,另外一個是垃圾收集執行緒。

6、執行緒操作的方法?

  • 執行緒的強制執行

線上程操作中,可以使用join()方法讓一個執行緒強制執行,執行緒強制執行期間,其他執行緒無法執行,必須等待此執行緒完成之後才可以繼續執行。

public class MyThreadJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; ++i) {
            System.out.println(Thread.currentThread().getName() + " 執行,i=" + i); //取得當前執行緒的名字
        }
    }
}

class ThreadJoinDemo{
    public static void main(String[] args) {
        MyThreadJoin myThreadJoin = new MyThreadJoin();
        Thread t = new Thread(myThreadJoin, "執行緒");
        t.start();
        for(int i =0; i < 5; ++i){
            if(i > 2){
                try {
                    t.join(); // 執行緒強制執行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Main執行緒執行:"+i);
        }
    }
}

執行結果

Main執行緒執行:0
Main執行緒執行:1
Main執行緒執行:2
執行緒 執行,i=0
執行緒 執行,i=1
執行緒 執行,i=2
執行緒 執行,i=3
執行緒 執行,i=4
Main執行緒執行:3
Main執行緒執行:4
  • 執行緒休眠

在程式中允許一個執行緒進行暫時的休眠,直接使用 Thread.sleep() 即可實現休眠。這個執行緒操作在其他例子中有用到。

  • 執行緒中斷

當一個執行緒執行時,另外一個執行緒可以直接通過interrupt()方法中斷其執行狀態。

public class MyThreadInterrput implements Runnable{
    public void run(){
        System.out.println("1、進入run()方法") ;
        try{
            Thread.sleep(10000) ;   // 執行緒休眠10秒
            System.out.println("2、已經完成了休眠") ;
        }catch(InterruptedException e){
            System.out.println("3、休眠被終止") ;
            return ; // 返回呼叫處
        }
        System.out.println("4、run()方法正常結束") ;
    }
};
class ThreadInterruptDemo{
    public static void main(String args[]){
        MyThreadInterrput mt = new MyThreadInterrput() ;
        Thread t = new Thread(mt,"執行緒");
        t.start() ; // 啟動執行緒
        try{
            Thread.sleep(2000) ;    // 執行緒休眠2秒
        }catch(InterruptedException e){
            System.out.println("5、休眠被終止") ;
        }
        t.interrupt() ; // 中斷執行緒執行
    }
};

執行結果

1、進入run()方法
3、休眠被終止
  • 後臺執行緒

在 Java 程式中,只要前臺有一個執行緒在執行,則整個 Java 程序都不會消失,所以此時可以設定一個後臺執行緒,這樣即使 Java 執行緒結束了,此後臺執行緒依然會繼續執行,要想實現這樣的操作,直接使用 setDaemon() 方法即可。

public class MyThreadDeamon implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println(Thread.currentThread().getName() + "正在執行");
        }
    }
};

class ThreadDaemonDemo{
    public static void main(String[] args) {
        MyThreadDeamon myThreadDeamon = new MyThreadDeamon();
        Thread thread = new Thread(myThreadDeamon, "執行緒");
        thread.setDaemon(true);
        thread.start();
    }
}

儘管run()是while無限迴圈,但是程式依舊可以執行完,因為無限迴圈已經設定為後置執行。

  • 執行緒的優先順序

在 Java 的執行緒操作中,所有的執行緒在執行前都會保持在就緒狀態,那麼此時,通過setPriorit()設定優先順序, 哪個執行緒的優先順序高,哪個執行緒就有可能會先被執行。

public class MyThreadPriority  implements Runnable{
    public void run(){  
        for(int i=0;i<5;i++){
            try{
                Thread.sleep(500) ; // 執行緒休眠
            }catch(InterruptedException e){
            }
            System.out.println(Thread.currentThread().getName()
                    + "執行,i = " + i) ;  
        }
    }
};
 class ThreadPriorityDemo{
    public static void main(String args[]){
        Thread t1 = new Thread(new MyThreadPriority(),"執行緒A") ; 
        Thread t2 = new Thread(new MyThreadPriority(),"執行緒B") ; 
        Thread t3 = new Thread(new MyThreadPriority(),"執行緒C") ; 
        t1.setPriority(Thread.MIN_PRIORITY) ;   // 優先順序最低
        t2.setPriority(Thread.MAX_PRIORITY) ;   // 優先順序最高
        t3.setPriority(Thread.NORM_PRIORITY) ;  // 優先順序最中等
        t1.start() ;    
        t2.start() ;    
        t3.start() ;    
    }
};

執行結果

執行緒B執行,i = 0
執行緒C執行,i = 0
執行緒A執行,i = 0
執行緒C執行,i = 1
執行緒A執行,i = 1
執行緒B執行,i = 1
執行緒C執行,i = 2
執行緒B執行,i = 2
執行緒A執行,i = 2
執行緒B執行,i = 3
執行緒C執行,i = 3
執行緒A執行,i = 3
執行緒B執行,i = 4
執行緒C執行,i = 4
執行緒A執行,i = 4

從程式的執行結果中可以觀察到,執行緒將根據其優先順序的大小來決定哪個執行緒會先執行,但是需要注意並非優先順序越高就一定會先執行,哪個執行緒先執行將由 CPU 的排程決定。

  • 執行緒的禮讓

線上程操作中,也可以使用 yield() 方法將一個執行緒的操作暫時讓給其他執行緒執行

public class MyThreadYield implements Runnable{
    public void run(){
        for(int i=0;i<5;i++){
            try{
                Thread.sleep(500) ;
            }catch(Exception e){
            }
            System.out.println(Thread.currentThread().getName()
                    + "執行,i = " + i) ;
            if(i==2){
                System.out.print("執行緒"+Thread.currentThread().getName()+"禮讓:") ;
                Thread.currentThread().yield() ;    // 執行緒禮讓
            }
        }
    }
};
class ThreadYieldDemo{
    public static void main(String args[]){
        MyThreadYield my = new MyThreadYield() ;
        Thread t1 = new Thread(my,"執行緒A") ;
        Thread t2 = new Thread(my,"執行緒B") ;
        t1.start() ;
        t2.start() ;
    }
}; 

執行結果

執行緒A執行,i = 0
執行緒B執行,i = 0
執行緒A執行,i = 1
執行緒B執行,i = 1
執行緒A執行,i = 2
執行緒執行緒A禮讓:執行緒B執行,i = 2
執行緒執行緒B禮讓:執行緒A執行,i = 3
執行緒B執行,i = 3
執行緒A執行,i = 4
執行緒B執行,i = 4

點我關注