Java多線程基礎(一)——線程與鎖

1、線程的基本概念

1.1 單線程

簡單的說,單線程就是進程中只有一個線程。單線程在程序執行時,所走的程序路徑按照連續順序排下來,前面的必須處理好,後面的纔會執行。java

Java示例:網絡

public class SingleThread {
    public static void main(String[] args) {
        for (int i = 0; i < 10000; i++) {
            System.out.print(i + " ");
        }
    }
}

上述Java代碼中,只有一個主線程執行main方法。多線程

1.2 多線程

由一個以上線程組成的程序稱爲多線程程序。常見的多線程程序如:GUI應用程序、I/O操做、網絡容器等。
Java中,必定是從主線程開始執行(main方法),而後在主線程的某個位置啓動新的線程。架構

2、線程的基本操做

2.1 建立

Java中建立多線程類兩種方法:併發

一、繼承java.lang.Threadide

Java示例:this

public class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.print(i + " ");
        }
    }
}
public class MultiThread {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();    //啓動子線程
        //主線程繼續同時向下執行
        for (int i = 0; i < 10000; i++) {
            System.out.print(i + " ");
        }
    }
}

上述代碼中,MyThread類繼承了類java.lang.Thread,並覆寫了run方法。主線程從main方法開始執行,當主線程執行至t.start()時,啓動新線程(注意此處是調用start方法,不是run方法),新線程會併發執行自身的run方法。spa

二、實現java.lang.Runnable接口線程

Java示例:3d

public class MyThread implements Runnable {
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.print(i + " ");
        }
    }
}
public class MultiThread {
    public static void main(String[] args) {
        Thread t = new Thread(new MyThread());
        t.start();    //啓動子線程
        //主線程繼續同時向下執行
        for (int i = 0; i < 10000; i++) {
            System.out.print(i + " ");
        }
    }
}

上述代碼中,MyThread類實現了java.lang.Runnable接口,並覆寫了run方法,其它與繼承java.lang.Thread徹底相同。實際上,java.lang.Thread類自己也實現了Runnable接口,只不過java.lang.Thread類的run方法主體裏空的,一般被子類覆寫(override)。

注意:主線程執行完成後,若是還有子線程正在執行,程序也不會結束。只有當全部線程都結束時(不含Daemon Thread),程序纔會結束。

2.2 暫停

Java中線程的暫停是調用java.lang.Thread類的sleep方法(注意是類方法)。該方法會使當前正在執行的線程暫停指定的時間,若是線程持有鎖,sleep方法結束前並不會釋放該鎖。

Java示例:

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.print(i + " ");
            try {
                Thread.sleep(1000);    //當前main線程暫停1000ms
            } catch (InterruptedException e) {
            }
        }
    }
}

上述代碼中,當main線程調用Thread.sleep(1000)後,線程會被暫停,若是被interrupt,則會拋出InterruptedException異常。

2.3 互斥

Java中線程的共享互斥操做,會使用synchronized關鍵字。線程共享互斥的架構稱爲監視(monitor),而獲取鎖有時也稱爲「持有(own)監視」。

每一個鎖在同一時刻,只能由一個線程持有。
注意:synchronized方法或聲明執行期間,如程序遇到任何異常或return,線程都會釋放鎖。

一、synchronized方法

Java示例1:

//synchronized實例方法
public synchronized void deposit(int m) {
    System.out.print("This is synchronized method.");
}

注:synchronized實例方法採用this鎖(即當前對象)去作線程的共享互斥。

Java示例2:

//synchronized類方法
public static synchronized void deposit(int m) {
    System.out.print("This is synchronized static method.");
}

注:synchronized類方法採用類對象鎖(即當前類的類對象)去作線程的共享互斥。如上述示例中,採用類.class(繼承自java.lang.Class)做爲鎖。

二、synchronized聲明
Java示例:

public void deposit(int m) {
    synchronized (this) {
        System.out.print("This is synchronized statement with this lock.");
    }
    synchronized (Something.class) {
        System.out.print("This is synchronized statement with class lock.");
    }
}

注:synchronized聲明能夠採用任意鎖,上述示例中,分別採用了對象鎖(this)和類鎖(something.class)

2.4 中斷

java.lang.Thread類有一個interrupt方法,該方法直接對線程調用。當被interrupt的線程正在sleep或wait時,會拋出InterruptedException異常。
事實上,interrupt方法只是改變目標線程的中斷狀態(interrupt status),而那些會拋出InterruptedException異常的方法,如wait、sleep、join等,都是在方法內部不斷地檢查中斷狀態的值。

  • interrupt方法

Thread實例方法:必須由其它線程獲取被調用線程的實例後,進行調用。實際上,只是改變了被調用線程的內部中斷狀態;

  • Thread.interrupted方法

Thread類方法:必須在當前執行線程內調用,該方法返回當前線程的內部中斷狀態,而後清除中斷狀態(置爲false) ;

  • isInterrupted方法

Thread實例方法:用來檢查指定線程的中斷狀態。當線程爲中斷狀態時,會返回true;不然返回false。

2.5 協調

一、wait set / wait方法
wait set是一個虛擬的概念,每一個Java類的實例都有一個wait set,當對象執行wait方法時,當前線程就會暫停,並進入該對象的wait set。
當發生如下事件時,線程纔會退出wait set:
①有其它線程以notify方法喚醒該線程
②有其它線程以notifyAll方法喚醒該線程
③有其它線程以interrupt方法喚醒該線程
④wait方法已到期
注:當前線程若要執行obj.wait(),則必須先獲取該對象鎖。當線程進入wait set後,就已經釋放了該對象鎖。

下圖中線程A先得到對象鎖,而後調用wait()方法(此時線程B沒法獲取鎖,只能等待)。當線程A調用完wait()方法進入wait set後會自動釋放鎖,線程B得到鎖。

二、notify方法
notify方法至關於從wait set中從挑出一個線程並喚醒。
下圖中線程A在當前實例對象的wait set中等待,此時線程B必須拿到同一實例的對象鎖,才能調用notify方法喚醒wait set中的任意一個線程。
注:線程B調用notify方法後,並不會當即釋放鎖,會有一段時間差。

三、notifyAll方法
notifyAll方法至關於將wait set中的全部線程都喚醒。

四、總結
wait、notify、notifyAll這三個方法都是java.lang.Object類的方法(注意,不是Thread類的方法)。
若線程沒有拿到當前對象鎖就直接調用對象的這些方法,都會拋出java.lang.IllegalMonitorStateException異常。

  • obj.wait()是把當前線程放到obj的wait set;
  • obj.notify()是從obj的wait set裏喚醒1個線程;
  • obj.notifyAll()是喚醒全部在obj的wait set裏的線程。

3、線程的狀態轉移

3-1 線程狀態轉移圖

  • 當建立一個Thread子類或實現Runnable接口類的實例時,線程進入【初始】狀態;
  • 調用實例的start方法後,線程進入【可執行】狀態;
  • 系統會在某一時刻自動調度處於【可執行】狀態的線程,被調度的線程會調用run方法,進入【執行中】狀態;
  • 線程執行完run方法後,進入【結束】狀態;
  • 處於【結束】狀態的線程,在某一時刻,會被JVM垃圾回收;
  • 處於【執行中】狀態的線程,若調用了Thread.yield方法,會回到【可執行】狀態,等待再次被調度;
  • 處於【執行中】狀態的線程,若調用了wait方法,會進入wait set並一直等待,直到被其它線程經過notify、notifyAll、interrupt方法喚醒;
  • 處於【執行中】狀態的線程,若調用了Thread.sleep方法,會進入【Sleep】狀態,沒法繼續向下執行。當sleep時間結束或被interrupt時,會回到【可執行狀態】;
  • 處於【執行中】狀態的線程,若遇到阻塞I/O操做,也會中止等待I/O完成,而後回到【可執行狀態】;
相關文章
相關標籤/搜索