1. 程式人生 > >java之初學線程

java之初學線程

指定時間 調度 .class new extends block 操作系統 引用 格式

線程

學習線程相關的筆記,前面寫過關於很多線程的使用,有興趣的可以去了解下

線程

概念理解

  • 並發 : 指兩個或多個事件在同一個時間段內發生(交替執行)。
  • 並行 : 指兩個或多個事件在同一時刻發生(同時發生)。
  • 進程 : 是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多
    個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創
    建、運行到消亡的過程。
  • 線程 : 進程內部的一個獨立執行單元;一個進程可以同時並發的運行多個線程,可以理解為一個進程便相當
    於一個單 CPU 操作系統,而線程便是這個系統中運行的多個任務(一個程序至少有一個進程,一個進程可以有多個線程)。

    線程和進程的區別 :

    1.進程:有獨立的內存空間,進程中的數據存放空間(堆空間和棧空間)是獨立的,至少有一個線程。
    2.線程:堆空間是共享的,棧空間是獨立的,線程消耗的資源比進程小的多。

多線程的原理

技術分享圖片

從圖中可以看出,線程的啟動實際上是開辟了新的空間,這樣的話jvm就可以在U盾謳歌棧之間相互切換,這個就是多線程的原理.

Runnable接口

Runnable是多線程的祖宗類,多線程都間接或直接的實現了這個接口.內部只有run方法,所以也可以看出run方法時多線程的核心.

Thread類

先了解一下Thread類的內部方法:

構造方法:

  • public Thread() :分配一個新的線程對象。
  • public Thread(String name) :分配一個指定名字的新的線程對象。
  • public Thread(Runnable target) :分配一個帶有指定目標新的線程對象。
  • public Thread(Runnable target,String name) :分配一個帶有指定目標新的線程對象並指定名字。

常用方法:

  • public String getName() :獲取當前線程名稱。
  • public void start() :導致此線程開始執行; Java虛擬機調用此線程的run方法。
  • public void run() :此線程要執行的任務在此處定義代碼。
  • public static void sleep(long millis) :使當前正在執行的線程以指定的毫秒數暫停(暫時停止執行)。
  • public static Thread currentThread() :返回對當前正在執行的線程對象的引用。

使用Thread來創建線程

  1. 創建Thread類的子類
  2. 在Thread類的子類中重寫run方法,設置線程任務
  3. 創建Thread類的子類
  4. 調用Thread類的方法start方法,開啟新的線程,執行run方法.(這需要註意,是調用start方法,而不是直接調用run方法)

void start() :導致此線程開始執行; Java虛擬機調用此線程的run方法。結果是兩個線程並發執行,當前線程(main)
和另一個線程(創建的新線程,執行其run方法),多次啟動一個線程是非法的.特別當線程已經結束後,不能重新啟動.java程序
是搶占式調度,哪個線程優先級高,哪個線程先執行;同一個優先級,隨機選擇一個.

獲取線程名稱

獲取線程的名稱有兩種方法:

  • 使用getName()獲取線程名稱,這個需要使用Thread類的子類對象來調用.
  • 使用Thread.currentThread().getName()獲取線程名稱(推薦使用)
設置線程名稱

設置線程的名稱也有兩種方式:

  • 使用setName()方法設置線程名稱,也是需要Thread類或者子類的對象調用.
  • 使用構造方法,可以使用有參構造來設置線程名稱.

Runnable接口

  1. 定義一個類,實現Runnable接口(任務類).
  2. 重寫Runnable的run方法
  3. 創建任務類對象
  4. 創建線程類對象,並將任務對象參數作為參數進行傳遞
  5. 使用線程類對象調用start方法啟動線程.

    這個過程中需要註意的是第四步,需要使用public Thread(Runnable target) :分配一個帶有指定目標新的線程對象。
    public Thread(Runnable target,String name) :分配一個帶有指定目標新的線程對象並指定名字。這兩個構造方法.

創建線程的方式

  • 實現Runnable接口

      /**
       * 使用實現Runnable接口實現多線程
       *
       * @author WZLOVE
       * @create 2018-07-16 19:46
       */
      public class MyRunnable implements Runnable{
    
          @Override
          public void run() {
              for (int i = 0; i < 100; i++) {
                  System.out.println(Thread.currentThread().getName() + ":" + i);
              }
          }
      }
  • 繼承Thread類

      /**
       * 使用繼承實現多線程
       *
       * @author WZLOVE
       * @create 2018-07-16 19:47
       */
      public class MyThread extends Thread{
    
          public MyThread() {
          }
    
          public MyThread(String name) {
              super(name);
          }
    
          @Override
          public void run() {
              for (int i = 0; i < 100; i++) {
                  System.out.println(getName() + ":" + i);
              }
          }
      }
  • 不同的方法實現多線程,創建對象使用的方式也不同:

      /**
       * @author WZLOVE
       * @create 2018-07-16 19:45
       */
      public class ThreadDemo {
    
          public static void main(String[] args) {
              // 繼承創建對象相對簡單
              MyThread mt = new MyThread("繼承");
              mt.start();
    
              // 實現接口創建對象
              // 創建自定義類對象
              MyRunnable mr = new MyRunnable();
              // 創建線程對象
              Thread thread = new Thread(mr,"實現接口");
              thread.start();
          }
      }

    註意點:

    1.實際上,Thread類也實現了Runnable接口.
    2.所有的多線程代碼都在run方法裏面
    3.所有的多線程代碼都是通過運行Thread的start()方法來運行的
    4.Runnable對象僅僅作為Thread對象的target,Runnable實現類裏包含的run()方法僅作為線程執行體。
    而實際的線程對象依然是Thread實例,只是該Thread線程負責執行其target的run()方法。

實現Runnable接口與繼承Thread類的不同點

使用接口比類是更有優勢的:

  • 可以避免java中的單繼承的局限性。
  • 增加程序的健壯性,實現解耦操作,代碼可以被多個線程共享,代碼和線程獨立(任務類和線程類進行分離,解耦)。
  • 但是實現接口不能直接使用Thread類的方法,但是可以通過獲取當前線程對象進行調用方法.
  • 適合多個相同的程序代碼的線程去共享同一個資源。
  • 線程池只能放入實現Runable或Callable類線程,不能直接放入繼承Thread的類。

線程的安全問題的產生

線程的安全問題的產生是由於多個線程對共享資源的訪問.簡單的說線程安全問題都是由全局變量及靜態變量引起的。
若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;
若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。
買電影票的例子(出現安全問題,運行一下):

package com.wzlove.thread.movie;

/**
 * 票數的任務類
 *
 * @author WZLOVE
 * @create 2018-07-17 14:35
 */
public class TicketRunnableImpl implements Runnable{
    /**
     * 定義共享資源
     */
    private  int ticket = 100;
    @Override
    public void run() {
        while  (true){
            if(ticket > 0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在賣第"+  ticket -- + "張票");
            }

        }
    }
}



package com.wzlove.thread.movie;

/**
 * 多線程安全問題的測試
 *
 * @author WZLOVE
 * @create 2018-07-17 14:37
 */
public class MovieDemo {

    public static void main(String[] args) {
        // 創建任務類對象
        TicketRunnableImpl ticketRunnable = new TicketRunnableImpl();
        // 創建線程對象
        Thread t1 = new Thread(ticketRunnable,"窗口1");
        Thread t2 = new Thread(ticketRunnable,"窗口2");
        Thread t3 = new Thread(ticketRunnable,"窗口3");
        // 開始線程
        t1.start();
        t2.start();
        t3.start();
    }
}

上面的情況就會出現線程安全問題.

線程的安全問題的解決

使用同步解決線程的安全問題.

  • 同步代碼塊
  • 同步方法
  • lock鎖機制

1.同步代碼塊

格式 :

    // 同步對象可一是任意對象,推薦使用this
    synchronized(同步對象){
        可能出現同步問題的代碼
    }
    

鎖對象可以是任意對象,但是鎖對象必須唯一.

2.同步方法

格式:

    // 同步方法也是有鎖對象的,也就是當前對象this
    修飾符 synchronized 返回值類型 方法名(參數列表){
        可能出現同步問題的代碼
    }

需要註意的是靜態同步方法內的同步鎖不是this(因為靜態代碼的執行在this之前產生),而是類的字節碼對象,也就是(類名.class)

3.Lock鎖機制

  • public void lock() :加同步鎖。
  • public void unlock() :釋放同步鎖。

使用步驟:

  1. 創建ReentrantLock的對象
  2. 在使用共享資源前使用lock方法進行加鎖
  3. 在使用共享資源結束後使用unlock方法釋放同步鎖

線程狀態的概述

線程狀態 導致狀態發生條件
new()新建 線程剛被創建,但是並未啟動。還沒調用start方法。
Runnable(可運行) 線程可以在java虛擬機中運行的狀態,可能正在運行自己代碼,也可能沒有,這取決於操 作系統處理器。
Blocked(鎖阻塞) 當一個線程試圖獲取一個對象鎖,而該對象鎖被其他的線程持有,則該線程進入Blocked狀 態;當該線程持有鎖時,該線程將變成Runnable狀態。
Waiting(無限等待) 一個線程在等待另一個線程執行一個(喚醒)動作時,該線程進入Waiting狀態。進入這個 狀態後是不能自動喚醒的,必須等待另一個線程調用notify或者notifyAll方法才能夠喚醒。
Timed Waiting(計時等待) 同waiting狀態,有幾個方法有超時參數,調用他們將進入Timed Waiting狀態。這一狀態 將一直保持到超時期滿或者接收到喚醒通知。帶有超時參數的常用方法有Thread.sleep 、 Object.wait。
Teminated(被終止) 因為run方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡。

狀態圖:
技術分享圖片

等待喚醒機制

Object類中的四個方法:

  • wait() : 使線程處於等待狀態,等待著其他的線程喚醒
  • wait(long millis) : 使線程處於等待狀態, 等待著其他的線程喚醒/等待時間到達
  • notify() : 喚醒其他的單個等待的線程
  • notifyAll() : 喚醒其他的所有等待的線程

sleep()與wait()

  • sleep:Thread類中的靜態方法,休眠指定的時間,在指定時間後自動喚醒,不回釋放鎖對象
  • wait : Object類中的方法,無限等待,等待其他線程的喚醒,必須釋放鎖對象

java之初學線程