天天看點

關于Java多線程的一些内容及synchronized的用法

  Java多線程是Java的一個重要特性,今天沒事總結一下,當然隻是一個簡單總結,畢竟要是多線程真正寫起來一篇是遠遠不夠的。

  

  建立多線程的兩種方式

  

  先說比較簡單的,在Java中實作多線程一般有四種方式,但是常用的就是兩種,一種是繼承Thread類,重寫run方法,另外一種就是實作Runnable接口,實作run方法,之後建立一個線程類,将實作Runnable接口的類作為線程類的參數傳遞進去。直接上兩種方式的代碼見下圖

  

關于Java多線程的一些内容及synchronized的用法

  

關于Java多線程的一些内容及synchronized的用法

  這裡代碼很簡單,就不做什麼解釋了,唯一要解釋的幾個點就是如果在main方法裡面調用Thread.currentThread().getName()得到的是主線程的名字,主線程預設的名字是main。啟動線程的方式是調用start方法而不是run方法,run方法是線程方法的内容。當start方法調用後線程處于就緒狀态,之後配置設定到時間片就會啟動線程。

  線程的生命周期

  

  接下來總結一下線程的生命周期,關于線程的生命周期有很多種不同的說法,其中比較流行的一種是線程大緻可以分為五個狀态,建立狀态,就緒狀态,運作狀态,阻塞狀态以及死亡狀态。每種狀态有不同的說法,比如就緒狀态可能被叫做等待狀态,阻塞狀态可能被叫做挂起狀态等等

  

  當建立一個新的線程對象,那麼此時線程處于建立狀态,當調用線程的start方法,該線程處于就緒狀态,此時線程并不會運作,如果cpu時間片配置設定到線程,那麼該線程就會處于運作狀态,線上程的就緒狀态可以調用線程的多個set方法來設定線程的各種屬性,常見的用法是調用set來設定線程的優先級priority,線程的名字,線程的屬性(是否是守護線程),所謂守護線程就是背景線程

  當線程調用sleep方法就會進入休眠狀态,所謂休眠狀态就是阻塞狀态的一種,當調用該方法的時候就抛出一個異常InterruptedException,但是因為線上程的run方法中不能用throws關鍵字,是以用try catch進行捕獲。當線程處于休眠狀态調用

interrupt方法會中斷休眠抛出異常

  當線程執行完了run方法或者因為某種特殊情況結束了run方法,線程就結束處于死亡狀态。

  

  關于終止線程的方法有三種:第一種是使用while循環,預設循環判斷條件為true,當執行到某個臨界值想要退出的時候設定一個boolean值,将其值設為false,此時無法進行循環,跳出run方法,線程終止。第二種方法是調用stop方法,但是這種方法在Java早期的版本中存在,基本不推薦使用,因為不知道可能會發生什麼意想不到的情況。還有一種·方式是調用interrupt方法來中斷線程。

  關于線程join方法的使用

  正常情況下Java的多個線程是處于異步執行狀态,即多個線程交替執行,不知道具體的運作順序,當一個線程調用start方法後如果不想和其他線程交替運作,那麼你可以調用線程的join方法,這個方法的作用是使調用start方法的線程優先執行完,其他的線程必須處于阻塞狀态,隻有該線程執行完以後其他線程才有機會調用。實際場景就是如果線程b需要線程a運作完的值做進一步的運算,那麼線程a調用start方法後調用join方法讓線程a處于最高優先級即可。好了直接上代碼比較直覺,見下圖

  

關于Java多線程的一些内容及synchronized的用法

  執行這段代碼,由于join方法的存在,子線程t1會先執行100次,之後才是主線程執行50次,如果沒有join方法,執行的結果是子線程與主線程交替進行。這就是join方法的作用

  關于synchronized關鍵字的用法

  好了,接下來可以總結下synchronized的用法了,多線程固然有其優勢的地方,但是當多線程共享一段資料時由于預設是異步操作,可能一個線程執行了一部分操作共享資料的語句就被另外一個線程去執行了,這樣很容易造成共享資料出錯。因為線程的本質就是多條語句的一個集合。當然這樣很容易想到解決辦法,就是讓操作共享資料的語句在一個線程内執行完,讓線程的異步操作變為同步操作,也就是多個線程是排隊進行操作,一段時間内隻有一個線程其他線程處于阻塞狀态。這就需要用到synchronized關鍵字了

  synchronized可以修飾的東西有很多,比如可以修飾類,普通方法,靜态方法,常量,代碼塊等。

  一個比較常用到需要同步操作的地方就是賣票,假設一共有四個視窗賣400張票,那麼根據實際情況你很容易想到在同一時刻一張票隻能被其中的一個視窗賣而不能同時被多個視窗賣,同時一張票隻有在被四個視窗中的任意一個視窗賣掉以後,其他的視窗才能在剩餘票的基礎上賣下一張票。也就是說隻有一個線程執行,而其他三個線程在看戲,解決辦法就是用synchronized進行同步。

  直接上代碼看,見下圖

  

關于Java多線程的一些内容及synchronized的用法

  簡單解釋一下就是建立四個線程模拟四個視窗,他們共用的是由t建立的線程,隻不過四個線程起了不同的名字,在run方法内用synchronized鎖住某個對象,修飾的就是synchronized下面包住的這段代碼。由于每個對象都有一把鎖,是以當一個線程比如t1執行run方法碰到synchronized關鍵字,就獲得了對象的鎖,這裡是獲得了a的鎖,這樣線程t1就獲得了執行賣票代碼的獨家控制權,其他三個線程此時隻能處于阻塞狀态,當t1的cpu時間片運作完後釋放鎖,之後四個線程繼續競争鎖。這裡a可以替換成一個普通對象o,或者是this,Ticket2.class,效果是一樣的,隻要確定四個線程競争的是同一個對象的鎖即可,其中this表示的是目前對象,其實就是t。但是如果在while(true)内部建立一個普通的Object對象,之後synchronized鎖住這個對象是不起作用的,因為這樣每個線程執行到run方法實際是相當于建立了四個不同的對象,彼此之間互不幹擾。

  用synchronized修飾普通方法,那麼這個方法就變為同步方法,當然還有修飾代碼塊使其變為同步代碼塊。當一個線程通路對象的同步方法時,該對象的所有同步方法都會被阻塞,其他線程此時無法通路該對象的任意一個同步方法,但是可以通路該對象的非同步方法,見代碼如下

  

public class Test7 {
   public static void main(String[] args) {
      Thread7 t=new Thread7();
      Thread a=new Thread(t, "線程1号");
      Thread b=new Thread(t,"線程2号");
      a.start();
      b.start();
}
}
class Thread7 implements Runnable{
    private int number;
    private int number2;

    public Thread7() {
        number=;
        number2=;
    }
    public void add(){
        synchronized (this) {
            for(int i=;i<;i++){
                System.out.println(Thread.currentThread().getName()+":"+(number++));
                try {
                    Thread.sleep();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
    //隻是一個輸出操作,沒有進行寫,是以不用同步
    public void print(){
        for(int i=;i<;i++){
            System.out.println(Thread.currentThread().getName()+"number:"+number2);
            try {
                Thread.sleep();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    @Override
    public void run() {
        String name=Thread.currentThread().getName();//擷取目前線程的名字
        if(name.equals("線程1号")){
            add();
        }else if(name.equals("線程2号")){
            print();
        }
    }

}
           

  執行上面這段代碼,你會發現兩個線程交替進行,這就驗證了之前的說法,新的線程是可以通路非同步方法的.

  

  用synchronized修飾靜态方法,因為靜态方法屬于類而不屬于某個特定對象,是以一個線程一旦通路某個同步靜态方法,就獲得了這個對象的所屬類的鎖,此時其他線程無法同時通路該類下任意一個對象的同步靜态方法,直接看代碼見下圖

  

關于Java多線程的一些内容及synchronized的用法

  

  這裡雖然t1和t2屬于兩個不同的對象,但是由于同步靜态方法的存在,使得兩個線程a和b處于同步狀态,即隻有其中一個線程執行完以後另外一個線程才能執行,這就是同步靜态方法起到的作用。

  将一個方法修飾為同步方法有兩種形式,一種是在方法名前加synchronized關鍵字,另外一種是在方法内部的第一行加上synchronized(this),兩者起到的效果是一樣的,不過後者的細粒度更高。 下面兩種寫法是等價的

  

關于Java多線程的一些内容及synchronized的用法

  基本上關于synchronized的用法就上面這些,最後再補充一下,synchronized關鍵字是不能被繼承的,也就是說如果一個類的某個方法是同步方法,那麼子類繼承該類後這個方法并不是同步的,隻是一個普通的方法,如果想要讓其變為同步方法那麼必須顯示聲明一下。