Java多線程基礎知識篇

来源:http://www.cnblogs.com/qifengshi/archive/2016/12/28/6230322.html
-Advertisement-
Play Games

這篇是Java多線程基本用法的一個總結。 本篇文章會從一下幾個方面來說明Java多線程的基本用法: 1. 如何使用多線程 2. 如何得到多線程的一些信息 3. 如何停止線程 4. 如何暫停線程 5. 線程的一些其他用法 所有的代碼均可以在 "char01" 如何使用多線程 啟動線程的兩種方式 Jav ...


這篇是Java多線程基本用法的一個總結。
本篇文章會從一下幾個方面來說明Java多線程的基本用法:

  1. 如何使用多線程
  2. 如何得到多線程的一些信息
  3. 如何停止線程
  4. 如何暫停線程
  5. 線程的一些其他用法

所有的代碼均可以在char01

如何使用多線程

啟動線程的兩種方式

Java 提供了2種方式來使用多線程, 一種是編寫一個類來繼承Thread,然後覆寫run方法,然後調用start方法來啟動線程。這時這個類就會以另一個線程的方式來運行run方法裡面的代碼。另一種是編寫一個類來實現Runnable介面,然後實現介面方法run,然後創造一個Thread對象,把實現了Runnable介面的類當做構造參數,傳入Thread對象,最後該Thread對象調用start方法。
這裡的start方法是一個有啟動功能的方法,該方法內部回調run方法。所以,只有調用了start方法才會啟動另一個線程,直接調用run方法,還是在同一個線程中執行run,而不是在另一個線程執行run
此外,start方法只是告訴虛擬機,該線程可以啟動了,也就說該線程在就緒的狀態,但不代表調用start就立即運行了,這要等待JVM來決定什麼時候執行這個線程。也就是說,如果有兩個線程A,B ,A先調用start,B後調用start,不代表A線程先運行,B線程後運行。這都是由JVM決定了,可以認為是隨機啟動。
下麵我們用實際的代碼,來說明兩種啟動線程的方式:
第一種,繼承Thread

public class ExampleThread extends Thread{

    @Override
    public void run() {
        super.run();
        System.out.println("這是一個繼承自Thread的ExampleThread");
    }
}

測試的代碼可以看test目錄下的ExampleThreadTest
另一種,實現了Runnable介面

public class ExampleRunable  implements Runnable{

    public void run() {
        System.out.println("這是實現Runnable介面的類");
    }
}

測試的代碼可以看test目錄下的ExampleRunableTest類。

如何得到多線程的一些信息

我們在啟動多線程之後,希望能通過一些API得到啟動的線程的一些信息。JDK給我們提供了一個Thread類的方法來得到線程的一些信息。

  • 線程的名字 —— getName()
  • 線程的ID —— getId()
  • 線程是否存活 —— isAlive()

得到線程的名字

這些方法是屬於Thread的內部方法,所以我們可以用兩種方式調用這些方法,一個是我們的類繼承Thread來使用多線程的時候,可以用過this來調用。另一種是通過Thread.currentThread() 來調用這些方法。但是這兩個方法在不同的使用場景下是有區別的。
我們先簡單來看兩個方法的使用。
第一個Thread.currentThread()的使用,代碼如下:

public class ExampleCurrentThread extends Thread{

    public ExampleCurrentThread(){
        System.out.println("構造方法的列印:" + Thread.currentThread().getName());
    }

    @Override
    public void run() {
        super.run();
        System.out.println("run方法的列印:" + Thread.currentThread().getName());
    }
}

測試的代碼如下:

public class ExampleCurrentThreadTest extends TestCase {

    public void testInit() throws Exception{
        ExampleCurrentThread thread = new ExampleCurrentThread();
    }

    public void testRun() throws Exception {
        ExampleCurrentThread thread = new ExampleCurrentThread();
        thread.start();
        Thread.sleep(1000);
    }
}

結果如下:

構造方法的列印:main
run方法的列印:Thread-0
構造方法的列印:main

為什麼我們在ExampleCurrentThread內部用Thread.currentThread()會顯示構造方法的列印是main,是因為Thread.currentThread()返回的是代碼段正在被那個線程調用的信息。這裡面很顯然構造方法是被main線程執行的,而run方法是被我們自己啟動的線程執行的,因為沒有給他起名字,所以預設是Thread-0。
接下來,我們在看一看繼承自Thread,用this調用。

public class ComplexCurrentThread extends Thread{

    public ComplexCurrentThread() {
        System.out.println("begin=========");
        System.out.println("Thread.currentThread().getName=" + Thread.currentThread().getName());

        System.out.println("this.getName()=" + this.getName());
        System.out.println("end===========");
    }

    @Override
    public void run() {
        super.run();
        System.out.println("run begin=======");
        System.out.println("Thread.currentThread().getName=" + Thread.currentThread().getName());
        System.out.println("this.getName()=" + this.getName());
        System.out.println("run end==========");
    }
}

測試代碼如下:

public class ComplexCurrentThreadTest extends TestCase {
    public void testRun() throws Exception {
        ComplexCurrentThread thread = new ComplexCurrentThread();
        thread.setName("byhieg");
        thread.start();

        Thread.sleep(3000);
    }
}

結果如下:

begin=========
Thread.currentThread().getName=main
this.getName()=Thread-0
end===========
run begin=======
Thread.currentThread().getName=byhieg
this.getName()=byhieg
run end==========

首先在創建對象的時候,構造器還是被main線程所執行,所以Thread.currentThread()得到的就是Main線程的名字,但是this方法指的是調用方法的那個對象,也就是ComplexCurrentThread的線程信息,還沒有setName,所以是預設的名字。然後run方法無論是Thread.currentThread()還是this返回的都是設置了byhieg名字的線程信息。
所以Thread.currentThread指的是具體執行這個代碼塊的線程信息。構造器是main執行的,而run方法則是哪個線程start,哪個線程執行run。這麼看來,this能得到的信息是不准確的,因為如果我們在run中執行了this.getName(),但是run方法卻是由另一個線程start的,我們是無法通過this.getName得到運行run方法的新城的信息的。而且只有繼承了Thread的類才能有getName等方法,這對於Java沒有多繼承的特性語言來說,是個災難。所有後面凡是要得到線程的信息,我們都用Thread.currentThread()來調用API。

得到線程的ID

調用getID取得線程的唯一標識。這個和上面的getName用法一致,沒什麼好說的,可以直接看ExampleIdThread和他的測試類ExampleIdThreadTest
所有的代碼均可以在char01

判斷線程是否存活

方法isAlive()的作用是測試線程是否處於活動狀態。所謂活動狀態,就是線程已經啟動但是沒有終止。即該線程start之後,被認為是存活的。
我們看一下具體的例子:

public class AliveThread extends Thread{

    @Override
    public void run() {
        super.run();
        System.out.println("run方法中是否存活" + "   "  + Thread.currentThread().isAlive());
    }
}

測試方法如下:

public class AliveThreadTest extends TestCase {
    public void testRun() throws Exception {
        AliveThread thread = new AliveThread();
        System.out.println("begin == " + thread.isAlive());
        thread.start();
        Thread.sleep(1000);
        System.out.println("end ==" + thread.isAlive());

        Thread.sleep(3000);
    }
}

結果如下:

begin == false
run方法中是否存活   true
end ==false

我們可以發現在start之前,該線程被認為是沒有存活,然後run的時候,是存活的,等run方法執行完,又被認為是不存活的。

如何停止線程

判斷線程是否終止

JDK提供了一些方法來判斷線程是否終止 —— isInterrupted()和interrupted()

停止線程的方式

這個是得到線程信息中比較重要的一個方法了,因為這個和終止線程的方法相關聯。先說一下終止線程的幾種方式:

  1. 等待run方法執行完
  2. 線程對象調用stop()
  3. 線程對象調用interrupt(),在該線程的run方法中判斷是否終止,拋出一個終止異常終止。
  4. 線程對象調用interrupt(),在該線程的run方法中判斷是否終止,以return語句結束。

第一種就不說了,第二種stop()方法已經廢棄了,因為可能會產生如下原因:

  1. 強制結束線程,該線程應該做的清理工作,無法完成。
  2. 強制結束線程,該線程已操作的加鎖對象強制解鎖,造成數據不一致。
    具體的例子可以看StopLockThread以及他的測試類StopLockThreadTest

第三種,是目前推薦的終止方法,調用interrupt,然後在run方法中判斷是否終止。判斷終止的方式有兩種,一種是Thread類的靜態方法interrupted(),另一種是Thread的成員方法isInterrupted()。這兩個方法是有所區別的,第一個方法是會自動重置狀態的,如果連續兩次調用interrupted(),第一次如果是false,第二次一定是true。而isInterrupted()是不會的。
例子如下:

public class ExampleInterruptThread extends Thread{

    @Override
    public void run() {
        super.run();
        try{
            for(int i = 0 ; i < 50000000 ; i++){
                if (interrupted()){
                    System.out.println("已經是停止狀態,我要退出了");
                    throw new InterruptedException("停止.......");
                }
                System.out.println("i=" + (i + 1));
            }
        }catch (InterruptedException e){
            System.out.println("順利停止");
        }


    }
}

測試的代碼如下:

public class ExampleInterruptThreadTest extends TestCase {
    public void testRun() throws Exception {
        ExampleInterruptThread thread = new ExampleInterruptThread();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

第四種方法和第三種一樣,唯一的區別就是將上面的代碼中的拋出異常換成return,個人還是喜歡拋出異常,這裡處理的形式就比較多,比如列印信息,處理資源關閉或者捕捉之後再重新向上層拋出。
註意一點,我們上面拋出的異常是InterruptedException,這裡簡單說一下可能產生這個異常的原因,在原有線程sleep的情況下,調用interrupt終止線程,或者先終止線程,再讓線程sleep。

如何暫停線程

在JDK中提供了以下兩個方法用來暫停線程和恢複線程。

  • suspend()——暫停線程
  • resume()——恢複線程

這兩個方法和stop方法一樣是被廢棄的方法,其用法和stop一樣,暴力的暫停線程和恢複線程。這兩個方法之所以是廢棄的主要由以下兩個原因:

  1. 線程持有鎖定的公共資源的情況下,一旦被暫停,則公共資源無法被其他線程所持有。
  2. 線程強制暫停,導致該線程執行的操作沒有執行完全,這時訪問該線程的數據會出現數據不一致。

線程的一些其他用法

線程的其他的一些基礎用法如下:

  1. 線程讓步
  2. 設置線程的優先順序
  3. 守護線程

線程讓步

JDK提供yield()方法來讓線程放棄當前的CPU資源,將它讓給其他的任務去占用CPU時間,但是這也是隨機的事情,有可能剛放棄資源,又馬上占用時間片了。
具體的例子可以參考ExampleYieldThread以及他的測試類ExampleYieldThreadTest

設置線程的優先順序

我們可以設置線程的優先順序來讓CPU儘可能的將執行的資源給優先順序高的線程。Java設置了1-10這10個優先順序,又有三個靜態變數來提供三個優先順序:

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

我們可以通過setPriority來設置線程的優先順序,可以直接傳入上訴三個靜態變數,也可以直接傳入1-10的數字。設置後線程就會有不同的優先順序。如果我們不設置優先順序,會是什麼情況?
線程的優先順序是有繼承的特性,如果我們在A線程中啟動了B線程,則AB具有相同的優先順序。一般我們在main線程中啟動線程,就和main線程有一致的優先順序。main線程的優先順序預設是5。

下麵說一下優先順序的一些規則:

  1. 優先順序高的線程一般會比優先順序低的線程獲得更多的CPU資源,但是不代表優先順序高的任務一定先於優先順序低的任務先執行完。因為不同優先順序的線程中run方法內容可能不一樣。
  2. 優先順序高的線程一定會比優先順序低的線程執行的快。如果兩個線程是一樣的run方法,但是優先順序不一樣,確實優先順序高的線程先執行完。

線程守護

JDK中提供setDaemon的方法來設置一個線程變成守護線程。守護線程的特點是其他非守護線程執行完,守護線程就自動銷毀,典型的例子是GC回收器。
具體可以看ExampleDaemonThreadExampleDaemonThreadTest

總結

這篇文章主要總結了Java線程的一些基本的用法,關於線程安全,同步的知識,放到了第二篇。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 浮點數值不適用於禁止出現舍入誤差的金融計算中。例如,命令System.out.println(2.0-1.1)將列印出0.8999999999999999999999999,而不是人們想象的0.9。其主要原因是浮點數值採用二進位系統表示,而在二進位系統中無法精確的表示分數1/10。這就好像十進位無法 ...
  • 最近裝了python和PyCharm開發環境,但是在安裝numpy和matplotlib等包時出現了問題,現總結一下在windows平臺下的安裝方法。 由於現在找不到了工具包新版本的exe文件,所以採用了whl格式文件的安裝。本人事先安裝了python3.5.2,電腦是32位。 1、先安裝wheel ...
  • 模板方法模式由兩個角色組成:父類角色,子類角色。 父類角色:提供模板。 子類角色:為父類模板提供實現。 類圖: JAVA代碼: AbstractClass.java ConcreteClass.java Test.java 總結:模板方法模式,定義一個操作中的演算法骨架,而將一些步驟延遲到子類中去實現 ...
  • 1 .100以內的奇數和偶數 1 .100以內的奇數和偶數 var js = ""; var os = ""; for(var i=1;i<100;i++) { if(i%2 == 0) { os = os+""+i; } else { js = js+""+i; } } alert(os); al ...
  • os.name 輸出字元串指示正在使用的平臺。如果是window 則用'nt'表示,對於Linux/Unix用戶,它是'posix'。 os.getcwd() 函數得到當前Python腳本工作的目錄路徑。 os.listdir('path') 返回指定目錄下的所有文件和目錄名。 os.listdir ...
  • Python是開發社區中用於許多不同類型應用的強大編程語言。很多人都知道它是可以處理幾乎任何任務的靈活語言。因此,在Python應用中需要一個什麼樣的與語言本身一樣靈活的資料庫呢?那就是NoSQL,比如MongoDB。 英文原文:https://realpython.com/blog/python/ ...
  • 這是我參考的一篇文章《基於CAS的樂觀鎖實現》,講述的是一種需要CPU支持的執行技術CAS(Compare and Swap)。 首先理解什麼是原子性操作,意思是不能再拆分的操作,例如改寫一個值,讀取一個值都屬於原子性操作。 那麼CAS是兩個操作,先比較舊值,比較通過後再進行改寫,這種連合操作合併成 ...
  • 需要加入下麵的一個bean 如果是xml,自己對應轉換成xml配置即可(為什麼這樣做?我目前也不清楚,可能因為版本問題吧,後期再做瞭解) ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...