1. 程式人生 > >Java多執行緒面試知識點彙總(超詳細總結)

Java多執行緒面試知識點彙總(超詳細總結)

一、sleep()方法、wait()方法、yeild()方法、interrupt()方法、notify()、notifyAll()方法
1、sleep()方法:
sleep方法為Thread的靜態方法;
sleep方法的作用是讓執行緒休眠指定時間,在時間到達時自動恢復執行緒的執行;
sleep方法不會釋放執行緒鎖;

2、wait()方法:
wait方法是Object的方法;
任意一個物件都可以呼叫wait方法,呼叫wait方法會將呼叫者的執行緒掛起,使該執行緒進入一個叫waitSet 的等待區域,直到其他執行緒呼叫同一個物件的notify

方法才會重新啟用呼叫者;
當wait方法被呼叫時,它會釋放它所佔用的鎖標記,從而使執行緒所在物件中的synchronize資料可以被別的執行緒所使用,
所以wait()方法必須在同步塊中使用,notify()和notifyAll()方法都會對物件的“鎖標記”進行修改,所以都需要在同步塊中進行呼叫,
如果不在同步塊中呼叫,雖然可以編輯通過,但是執行時會報IllegalMonitorStateException(非法的監控狀態異常);

3、yeild()方法:
yeild()方法表示停止當前執行緒,使該執行緒進入可執行狀態,讓同等優先順序的執行緒執行,如果沒有同等優先順序的執行緒,那麼yeild()方法不
會起作用;
4、notify()和nofityAll()方法;
notify()會通知一個處在wait()狀態的執行緒;如果有多個執行緒處在wait狀態,他會隨機喚醒其中一個;
notifyAll()會通知過所有處在wait()狀態的執行緒,具體執行哪一個執行緒,根據優先順序而定;
二、java執行緒的狀態
java執行緒有四種狀態:產生、就緒、執行、阻塞、死亡
產生:執行緒被建立,但是未啟動(未呼叫start())
就緒:執行緒被啟動(呼叫start()),處於可執行狀態,等待CPU的排程;
執行:執行緒正常的執行狀態;
死亡:一個執行緒正常執行結束;
阻塞:
(1)、等待阻塞:執行的執行緒執行wait()方法,jvm會把該執行緒放入waitSet執行緒池(釋放鎖);
(2)、同步阻塞(死鎖):執行的執行緒在獲取其他物件的同步鎖是,該物件的同步鎖被別的執行緒鎖佔用,則jvm會將該執行緒放入執行緒池中;
(3)、其他阻塞:執行的執行緒執行sleep方法或者執行t.join()方法,被別的執行緒打斷,jvm把該執行緒置為阻塞狀態,當sleep超時或者join執行緒結

束時執行緒重新進入就緒狀態
三、java實現多執行緒的方法:繼承Thread類,實現runnable介面,實現Callable介面
1、繼承Thread類:

public class MyThread extends Thread {  
      public void run() {  
       System.out.println("MyThread.run()");  
      }  
    }  
    MyThread myThread1 = new MyThread();  
    myThread1.start();

2、實現Runnable介面:

public class MyThread immplements Runnable {
        public void run(){
            sysout.out.println("MyThread.run()");
        }
    }
    MyThread runThread = new MyThread();
    Thread runThread = new Thread(runThread,"");
    runThread.start();

3、實現Callable介面,通過FutureTask包裝器建立Thread執行緒

優缺點:
1)、繼承Thread類為單繼承,實現Runnable方法為多實現,所以在靈活性上來說,使用實現Runnable方法更靈活;
2)、通過實現Runnable介面的方式可以實現多執行緒內的資源共享;
3)、增加程式碼的健壯性,程式碼可以被多個執行緒共享,程式碼和資料獨立;
4)、執行緒池只能放實現Runnable或callable類的執行緒,不能直接放入繼承Thread類的執行緒;

四、執行緒排程
1、調整現場優先順序:Java執行緒有優先順序,優先順序高的執行緒獲得較多的執行機會(執行時間);
static int Max_priority 執行緒可以具有的最高優先順序,值為10;
static int MIN_PRIORIYT 執行緒可以具有的最低優先順序,值為1;
static int NORM_PRIORITY 分配給執行緒的預設優先順序,值為5;
Thread類的setPriority()和getPriority()方法分別用來設定和獲取執行緒的優先順序;
2、執行緒睡眠:Thread.sleep(long millins)使執行緒轉到阻塞狀態;
3、執行緒等待:Object.wait()方法,釋放執行緒鎖,使執行緒進入等待狀態,直到被其他執行緒喚醒(notify()和notifyAll());
4、執行緒讓步:Thread.yeild()方法暫停當前正在執行的執行緒,使其進入等待執行狀態,把執行機會讓給相同優先順序或更高優先順序的執行緒,如果沒有較高優先順序

或相同優先順序的執行緒,該執行緒會繼續執行;
5、執行緒加入:join()方法,在當前執行緒中呼叫另一個執行緒的join()方法,則當前執行緒轉入阻塞狀態,知道另一個程序執行結束,當前執行緒再有阻塞狀態轉

為就緒狀態;

五、執行緒類的一些常用方法:
1)、sleep():強迫一個執行緒睡眠N毫秒;
2)、isAlive():判斷一個執行緒是否存活;
3)、join():執行緒插隊;
4)、activeCount():程式中活躍的執行緒數;
5)、enumerate():列舉程式中的執行緒;
6)、currentThread():得到當前執行緒;
7)、isDeamon():一個執行緒是否為守護執行緒;
8)、setName():為執行緒設定一個名字;
9)、wait():執行緒等待;
10)、notify():喚醒一個執行緒;
11)、setPriority():設定一個執行緒的優先順序;

六:執行緒同步
執行緒同步主要使用synchronized關鍵字;具體有兩種實現方式:1、作為關鍵字修飾類中一個方法;2、修飾方法中的一塊區域(程式碼);
1、把synchronized當做方法(函式)的修飾符:

public class Name{//類名為Name
    //getName方法
    public synchronized void getName(){
        system.out.println(“123”);
    }
}

類Name 有兩個例項物件n1和n2,此時有兩個執行緒t1和t2;n1線上程t1中執行getName()方法(獲得這個方法的鎖),那麼n1就不能線上程t2中執行getName(

)方法了,但是n2可以在t1執行緒中執行getName()方法,同理n2 不能同時在t2執行緒中執行getName()方法;所以說實際上synchronized鎖的是getName()

這個方法的物件(n1和n2),而不是鎖的這個方法,這個需要理解;
2、同步塊,例項程式碼如下:

public void getName(Object o){
        synchronized(o){
            //TODO
        }
    }

這裡表示鎖住這個變數o;
這裡做一個執行緒安全的單例模式

public class Car{
  //構造方法私有化
  private Car();
  //建立一個靜態的私有的空的常量car
  private static Car car = null; 
  //對外開放一個靜態的共有的方法用來獲取例項
  public static getInstance(){
    if(car  == null){
      synchronized(Car.getClass()){
        if(car  == null){
            car = new Car();
        }
           }        
     }
    return car;
    }
}

七、執行緒資料傳遞
在傳統的開發模式下,當我們呼叫一個函式時,通過這個函式的引數將資料傳入,並通過這個函式的返回值來返回最終的計算結果,但是在多執行緒的非同步開發

模式下,資料的傳遞和返回同同步開發模式有很大區別。由於執行緒的執行和結果是不可預料的,因此在傳遞和返回資料時就無法像函式一樣通過函式引數和

return語句來返回資料;
1、通過構造方法傳遞引數

package mythread;   
public class MyThread1 extends Thread   
{   
private String name;   
public MyThread1(String name)   
{   
this.name = name;   
}   
public void run()   
{   
System.out.println("hello " + name);   
}   
public static void main(String[] args)   
{   
Thread thread = new MyThread1("world");   
thread.start();   
}   
}   

由於這種方法是在建立執行緒物件的同時傳遞資料的,因此,線上程執行之前這些資料就就已經到位了,這樣就不會造成資料線上程執行後才傳入的現象。如果

要傳遞更復雜的資料,可以使用集合、類等資料結構。使用構造方法來傳遞資料雖然比較安全,但如果要傳遞的資料比較多時,就會造成很多不便。由於Java

沒有預設引數,要想實現類似預設引數的效果,就得使用過載,這樣不但使構造方法本身過於複雜,又會使構造方法在數量上大增。因此,要想避免這種情況

,就得通過類方法或類變數來傳遞資料。
2、通過變數和方法傳遞資料
向物件中傳入資料一般有兩次機會,第一次機會是在建立物件時通過構造方法將資料傳入,另外一次機會就是在類中定義一系列的public的方法或變數(也可

稱之為欄位)。然後在建立完物件後,通過物件例項逐個賦值。下面的程式碼是對MyThread1類的改版,使用了一個setName方法來設定 name變數:

package mythread;   
public class MyThread2 implements Runnable   
{   
private String name;   
public void setName(String name)   
{   
this.name = name;   
}   
public void run()   
{   
System.out.println("hello " + name);   
}   
public static void main(String[] args)   
{   
MyThread2 myThread = new MyThread2();   
myThread.setName("world");   
Thread thread = new Thread(myThread);   
thread.start();   
}   
}  

3、最後是執行緒之間的資料共享,以下貼出生產者消費者模式的實現:
實現場景:有一個饅頭房,生產者生產饅頭,消費者消費饅頭,當饅頭數量為0時要停止消費,開始生產,當生產的饅頭大於5時停止生產,開始消費;
分析:
1、饅頭類:變數:mt(饅頭),num(饅頭數量),eatMantou()(吃饅頭方法),produceMantou()(生產饅頭方法),此處通過構造方法傳遞物件
2、消費者類:吃饅頭,即用來呼叫饅頭物件的吃饅頭方法;
3、生產者類:生產饅頭,即用來呼叫饅頭物件的生產饅頭方法;
4、Test類:用來測試;
具體程式碼如下:

package com.yp.producerAndconsumer;
/**
 * @prama 生產者消費者問題中的產品
 */
public class Mantou {
    private String mt;
    private int num;


    public String getMt() {
        return mt;
    }

    public void setMt(String mt) {
        this.mt = mt;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public Mantou(String mt, int num) {
        this.mt = mt;
        this.num = num;
    }

    /**
     * 消費饅頭
     */
    public synchronized void eatMantou(){
        //如果饅頭數量大於0,則消費饅頭
        while(num >0){
            System.out.println("消費後有:---"+this.num+"\n");
            num--;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        while(num ==0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.notify();
        }

    }

    /**
     * 生產饅頭
     */
    public synchronized void produceMantou(){
        while(num<5){
            System.out.println("生產後有:---"+this.num+"個"+"\n");
            num ++;
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        while(num ==5){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.notify();
        }
    }
}

消費者類;

package com.yp.producerAndconsumer;

public class Consumer implements Runnable {
    private Mantou m ;
    public Consumer(Mantou m) {
        this.m = m;
    }

    @Override
    public  void run() {
        m.eatMantou();
    }
}

生產者類:

package com.yp.producerAndconsumer;

public class Producer implements Runnable{
    Mantou m ;
    public Producer(Mantou m) {
        this.m = m;
    }


    @Override
    public  void run() {
        m.produceMantou();
    }
}

測試類:

package com.yp.producerAndconsumer;

/**
 * @param 生產者消費者問題
 * @author YangPeng
 *
 */
public class ProducerAndConsumer {
    public static void main(String[] args) {
        Mantou mantou = new Mantou("花捲",4);
        Producer producer = new Producer(mantou);
        Consumer consumer = new Consumer(mantou);
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);
        t1.start();
        t2.start();

    }
}