java多線程系列(一)---多線程技能

java多線程技能

前言:本系列將從零開始講解java多線程相關的技術,內容參考於《java多線程核心技術》與《java併發編程實戰》等相關資料,但願站在巨人的肩膀上,再經過個人理解能讓知識更加簡單易懂。html

目錄

併發歷史

  • 在沒有操做系統的時候,一臺計算機只執行一個程序,在那個時候,對珍貴的計算機資源來講是一種浪費
  • 爲了提升資源利用率(好比在等待輸入的時候,能夠執行其餘程序),爲了提升公平性(不一樣用戶和程序對計算機上的資源有平等的使用權),爲了提升便利性(實現多個任務的時候,能夠經過多個程序,而不用一個程序實現多個任務)計算機加入了操做系統
  • 一樣,相同的緣由,線程誕生了。線程能夠共享進程的資源。

線程優點

發揮多處理器的強大功能

  • 隨着技術的發展,多處理器系統愈來愈普及。在一個雙處理器系統上,若是隻用一個線程,那麼無疑浪費了資源。

線程狀態

  • 新建(New):建立後還沒有啓動的線程
  • 運行(Runanle):包括了操做系統線程中的Running和Ready,處於此狀態的線程可能正在執行或者等待cpu分配時間片
  • 無限期等待(Waiting):等待被其餘線程顯式喚醒,執行wait或者join方法或者LockSupport的park方法
  • 限期等待(Timed Waiting):必定時間後會由系統自動喚醒
  • 阻塞(Blocked):等待獲取到一個排它鎖
  • 結束(Terminated):線程執行結束

多線程編程的兩種方式

  • 繼承Thread
  • 實現Runnable接口

經過繼承Thread

代碼的執行順序與代碼的順序無關

public class T1 {
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        myThread.start();
        System.out.println("代碼的執行結果與代碼的順序無關");
    }
}
class MyThread extends Thread
{
    public void run()
    {
        System.out.println("建立的線程");
    }
}

若是直接執行run方法是同步(主線程調用),start方法是讓系統來找一個時間來調用run方法(子線程調用),

public class T1 {
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        myThread.run();
        System.out.println("若是是直接執行run方法,確定是按代碼順序執行的,由於是經過主線程調用的");
    }
}
class MyThread extends Thread
{
    public void run()
    {
        System.out.println("建立的線程");
    }
}

經過實現Runnable接口

比繼承Thread的方式更有優點

  • java不能多繼承,因此若是線程類已經有一個父類,那麼沒法再繼承Thread類
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("運行中!");
    }
}
public class Run {

    public static void main(String[] args) {
        Runnable runnable=new MyRunnable();
        Thread thread=new Thread(runnable);
        thread.start();
        System.out.println("運行結束!");
    }

}

線程數據非共享

public static void main(String[] args) {
            MyThread a = new MyThread("A");
            MyThread b = new MyThread("B");
            MyThread c = new MyThread("C");
            a.start();
            b.start();
            c.start();
        }
        
class MyThread extends Thread {

    private int count = 5;

    public MyThread(String name) {
        super();
        this.setName(name);
    }

    @Override
    public void run() {
        super.run();
        while (count > 0) {
            count--;
            System.out.println("由 " + this.currentThread().getName()
                    + " 計算,count=" + count);
        }
    }
}
  • 這裏的i並不共享,每個線程維護本身的i變量

線程數據共享

public static void main(String[] args) {
    MyThread mythread=new MyThread();
    //線程a b c啓動的時候,執行的是myThread的方法,此時數據共享
    Thread a=new Thread(mythread,"A");
    Thread b=new Thread(mythread,"B");
    Thread c=new Thread(mythread,"C");

    a.start();
    b.start();
    c.start();

}
  • 因爲i++不是原子操做(先獲取i的值,讓後再加一,再把結果賦給i),因此輸出的值會有重複的狀況,好比4 4 2

synchronized關鍵字讓i++同步執行

public synchronized void run() {
        super.run();
            count--;
            System.out.println("由 "+this.currentThread().getName()+" 計算,count="+count);//輸出的必定是4 3 2
    }
  • synchronized 關鍵字,給方法加上鎖,多個線程嘗試拿到鎖,拿到鎖的線程執行方法,拿不到的不斷的嘗試拿到鎖

Thread方法

  • currentThread(),得到當前線程
  • isLive() 線程是否活躍狀態(啓動還未運行或者啓動了正在運行即爲活躍狀態)
  • sleep()方法,讓線程休眠
  • getId()方法 得到該線程的惟一標識
  • suspeend()方法,讓線程暫停(已報廢)
  • ressume()方法,讓線程恢復(已報廢)
  • stop()方法,讓線程終止(已報廢)

中止線程的方法

  • 線程本身執行完後自動終止
  • stop強制終止,不安全
  • 使用interrupt方法

interrupt方法

  • 線程對象有一個boolean變量表明是否有中斷請求,interrupt方法將線程的中斷狀態設置會true,可是並無馬上終止線程,就像告訴你兒子要好好學習同樣,可是你兒子怎麼樣關鍵看的是你兒子。
public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(200);
            thread.interrupt();
        } catch (InterruptedException e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end!");
    }
    class MyThread extends Thread {
        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 500000; i++) {
                System.out.println("i=" + (i + 1));
            }
        }
    }
  • 上面的代碼雖然執行了interrupt方法,可是並無中斷run方法裏面的程序,而且run方法所有執行完,也就是一直執行到500000

判斷線程是否中斷

  • interrupted方法判斷當前線程是否中斷,清除中斷標誌
  • isInterrupt 方法判斷線程是否中斷,不清除中斷標誌

interrupted方法

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 500000; i++) {
            System.out.println("i=" + (i + 1));
        }
    }
}
public class Run {
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(1000);
            thread.interrupt();
            //Thread.currentThread().interrupt();
            System.out.println("是否中止1?="+thread.interrupted());//false
            System.out.println("是否中止2?="+thread.interrupted());//false main線程沒有被中斷!!!
      //......
  • 這裏thread線程執行了interrupt方法,按到裏thread的中斷狀態應該爲true,可是由於interrupted方法判斷的是當前線程的中斷狀態,也就是main線程(main線程執行thread.interrupted方法),因此他的中斷狀態是false
public class Run {
    public static void main(String[] args) {
        try {
            Thread.currentThread().interrupt();
            System.out.println("是否中止1?="+Thread.interrupted());//true
            System.out.println("是否中止2?="+Thread.interrupted());//false 
      //......
  • 主線程執行interrupt方法,第一次執行interrupted方法的時候,中斷狀態爲true,可是interrupted方法有清除中斷標誌的做用,因此再執行的時候輸出的是false

isInterrupt方法

public static void main(String[] args) {
        MyThread thread=new MyThread();
        thread.start();
        thread.interrupt();
        System.out.println(thread.isInterrupted());//true
        System.out.println(thread.isInterrupted());//true
    }
  • 這裏也有判斷線程中斷的做用,而判斷的是他的調用者的中斷狀態,並且沒有清除中斷標誌的做用,因此兩次都是true

中止線程

  • 在上面的代碼中,咱們雖然執行了interrupt方法,可是並無中斷進程,那麼咱們若是來中斷呢?咱們能夠在run方法中進行判斷,判斷中斷狀態,狀態爲true,那麼就中止run方法。
import exthread.MyThread;

import exthread.MyThread;

public class Run {

    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end!");
    }

}

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 500000; i++) {
            if (this.interrupted()) {
                System.out.println("已是中止狀態了!我要退出了!");
                break;
            }
            System.out.println("i=" + (i + 1));
        }
        System.out.println("666");
    }
}
  • 還有一個問題,咱們要中斷進程,經過上面的的操做咱們終止了for循環,可是後面的輸出666仍然執行,這並非咱們想要的中斷。因而咱們能夠順便拋出異常,而後直接捕獲,這樣的話後面的代碼就不執行了。

異常法中止線程

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        try {
            for (int i = 0; i < 500000; i++) {
                if (this.interrupted()) {
                    System.out.println("已是中止狀態了!我要退出了!");
                    throw new InterruptedException();
                }
                System.out.println("i=" + (i + 1));
            }
            System.out.println("我在for下面");
        } catch (InterruptedException e) {
            System.out.println("進MyThread.java類run方法中的catch了!");
            e.printStackTrace();
        }
    }
}
  • 固然咱們也能夠直接return,可是拋出異常比較好,由於後面能夠繼續將異常拋出,讓線程中斷事件獲得傳播

return 中止線程

for (int i = 0; i < 500000; i++) {
                if (this.interrupted()) {
                    System.out.println("已是中止狀態了!我要退出了!");
                    return;
                }
                System.out.println("i=" + (i + 1));
            }

sleep與interrupt

  • 中斷狀態,進入sleep拋出異常
  • 睡眠進入中斷狀態,拋出異常
public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(200);
            thread.interrupt();
        } catch (InterruptedException e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end!");
    }
}
    class MyThread extends Thread {
        @Override
        public void run() {
            super.run();
            try {
                System.out.println("run begin");
                Thread.sleep(200000);
                System.out.println("run end");
            } catch (InterruptedException e) {
                System.out.println("在沉睡中被中止!進入catch!"+this.isInterrupted());
                e.printStackTrace();
            }
        }
    }*/

暫停線程

  • suspend (做廢)會讓同步方法直接鎖住
public static void main(String[] args) {
        try {
            final SynchronizedObject object = new SynchronizedObject();

            Thread thread1 = new Thread() {
                @Override
                public void run() {
                    object.printString();
                }
            };

            thread1.setName("a");
            thread1.start();

            Thread.sleep(1000);

            Thread thread2 = new Thread() {
                @Override
                public void run() {
                    System.out
                            .println("thread2啓動了,但進入不了printString()方法!只打印1個begin");
                    System.out
                            .println("由於printString()方法被a線程鎖定而且永遠的suspend暫停了!");
                    object.printString();
                }
            };
            thread2.start();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
    class SynchronizedObject {

        synchronized public void printString() {
            System.out.println("begin");
            if (Thread.currentThread().getName().equals("a")) {
                System.out.println("a線程永遠 suspend了!");
                Thread.currentThread().suspend();
            }
            System.out.println("end");
        }

    }
  • 當thread1執行了suspend方法後,printString方法直接就被鎖住了,也就是thread1把這個鎖佔住了,可是卻不工做
public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
  • 用System.out.println() 方法替換pringString方法一樣也會鎖住,由於這個方法也加鎖了,當thread1暫停以後,一樣佔住了這個鎖

yield方法

  • 放棄當前cpu資源,讓其餘任務去佔用,可是何時放棄不知道,由於放棄後,可能又開始得到時間片

線程優先級

  • cpu先執行優先級高的線程的對象任務, setPriority方法能夠設置線程的優先級
  • 線程優先級具備繼承性,也就是說A線程啓動B線程,兩者優先級同樣,一樣main主線程啓動線程A,main和A的優先級也是同樣的
public static void main(String[] args) {
        System.out.println("main thread begin priority="
                + Thread.currentThread().getPriority());
        Thread.currentThread().setPriority(6);
        System.out.println("main thread end   priority="
                + Thread.currentThread().getPriority());
        MyThread1 thread1 = new MyThread1();
        thread1.start();
    }


    class MyThread1 extends Thread {
        @Override
        public void run() {
            System.out.println("MyThread1 run priority=" + this.getPriority());
            MyThread2 thread2 = new MyThread2();
            thread2.start();
        }
    }

優先級特性

  • 規則性,cpu儘可能將資源給優先級高的
  • 隨機性,優先級較高的不必定先執行完run方法

守護線程

  • 線程有兩種一種是用戶線程,一種是守護線程
  • 垃圾回收線程是典型的守護線程,當jvm中還有非守護線程,守護線程就一直還在,知道非守護線程不存在了,守護線程才銷燬

總結

  • 線程提升了資源利用率
  • 線程的實現能夠經過繼承Thread類,也能夠經過實Runnable接口
  • 線程中斷方式有3種,經常使用的是interrupt方法,該方法並無當即中斷線程,只是作了一箇中斷標誌
  • interrupted和isInterrupt兩種方法均可以查看線程中斷狀態,第一種查看的是當前線程的中斷狀態,第二種查看的該方法調用者的中斷狀態
  • interrupted方法會清除中斷狀態,isInterrupt不會清除中斷狀態
  • interrupt方法沒有真正中斷線程,因此能夠在run方法裏面判斷中斷狀態,而後經過拋出異常或者return來中斷線程
  • 當線中斷狀態爲true,再進入sleep會拋出異常,反之同樣,sleep狀態執行interrupt方法,一樣會拋出異常
  • 線程暫停容易將鎖佔住
  • 線程具備優先級,能夠經過方法設置線程優先級,cpu會將資源儘可能給優先級高的線程,可是當優先級差異不大的時候,優先級高的不必定先執行完run方法
  • 線程有兩種,一種用戶線程,一種守護線程,直到用戶線程都銷燬,守護線程才銷燬

我以爲分享是一種精神,分享是個人樂趣所在,不是說我以爲我講得必定是對的,我講得可能不少是不對的,可是我但願我講的東西是我人生的體驗和思考,是給不少人反思,也許給你一秒鐘、半秒鐘,哪怕說一句話有點道理,引起本身心裏的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)

做者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。若是以爲還有幫助的話,能夠點一下右下角的【推薦】,但願可以持續的爲你們帶來好的技術文章!想跟我一塊兒進步麼?那就【關注】我吧。java

相關文章
相關標籤/搜索