一. 線程管理之Thread基礎

不忘初心 砥礪前行, Tomorrow Is Another Day !html

相關文章

本文概要:java

  1. 進程與線程
  2. 線程的生命週期
  3. 線程易混淆的函數
  4. 鎖機制
  5. 線程同步的四種方式
  6. 對 volatile 關鍵字的理解

一. 進程和線程

1.1 基本概念

  • 進程

概念:進程是程序的實體,是受操做系統管理的基本運行單元.編程

  • 線程

概念:操做系統中最小調度單元,一個進程能夠擁有多個線程.bash

1.2 線程的生命週期

  • new : 新建狀態,當new Thread實例化時.
  • Runnable : 可運行狀態,當調用start方法時.
  • Running : 運行狀態,線程被cpu執行,調用了run方法時.
  • Blocked 阻塞狀態,當調用join()、sleep()、wait()時.分三種阻塞狀況
    • wait : 等待狀態,當調用wait方法時,此時須要調用它的notify方法去喚醒它,纔會重回可運行狀態.
    • timeWait : 超時等待狀態,當調用t.join(long)、Thread.sleep(long),obj.wait(long)時,超過指定時間,都會自動返回可運行狀態.
    • lock: 同步狀態,獲取對象的同步鎖,若該同步鎖被別的線程佔用時.
  • Dead : 銷燬狀態,線程執行完畢或者發生異常時.

1.3 線程易混淆的函數

  • Thread.sleep()/sleep(long millis),當前線程進阻塞狀態,不會釋放鎖
  • t.join()/join(long millis),在當前線程裏調用其它線程的join方法,當前線程進阻,不放鎖.
    • (至關於其餘線程插隊進來,須要等待其餘線程執行完畢纔可往下執行),
  • obj.wait()/wait(long timeout),當前線程進阻,放鎖.須要依靠notify()/notifyAll()喚醒或者等待時間到自動喚醒
  • obj.notify()/obj.nofiyAll喚醒在此對象監視器上阻塞的任意某個線程/全部線程.
  • Thread.yield(),當前線程不進阻,不放鎖.而是重置爲可運行狀態.
  • interrupt(),中斷線程.

二. 線程的建立

線程的建立有3種方式.多線程

2.1 繼承Thread,重寫run方法.

這種方式的本質也是實現Runnable接口.當咱們調用start方法時,並不會當即執行線程裏面代碼,而只是將線程狀態變爲可執行狀態,具體的執行時機由操做系統決定.併發

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("直接繼承Thread,重寫run方法");
    }

    public static void main(String[] args) {
        new MyThread().start();
    }
}
複製代碼

2.2 實現Runnable接口,重寫run方法.

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("實現Runnable接口,重寫run方法");
    }

    public static void main(String[] args) {
        new Thread(new MyRunnable()).start();
    }
}
複製代碼

2.3 實現Callable接口,重寫Call方法.

相比Runnable的三大功能.異步

  • 能夠拋出異常
  • 提供返回值
  • 經過Future異步任務統計,能夠對目標線程Call方法監視執行狀況,獲取執行完畢時的返回值結果.

關於ExecutorService與Future相關知識,將在線程池一篇文中詳細講解.這裏只須要知道Callable通常是和ExecutorService配合來使用的.ide

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("子線程正在幹活");
        Thread.sleep(3000);
        return "實現Callable接口,重寫Call方法";
    }

    public static void main(String[] args) throws Exception {
        MyCallable myCallable = new MyCallable();
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<String> future = executorService.submit(myCallable);
        executorService.shutdown();

        Thread.sleep(1000);//模擬正在幹活
        System.out.println("主線程正在幹活");
        //阻塞當前線程,等待返回結果.
        System.out.println("等待返回結果:" + future.get());
        System.out.println("主線程全部的活都幹完了");

    }
}

//調用輸出
子線程正在幹活
主線程正在幹活
等待返回結果:實現Callable接口,重寫Call方法
主線程全部的活都幹完了
複製代碼

三. 線程的同步

線程同步的目的就是爲了防止當多個線程對同一個數據對象進行存儲時,形成數據不一致的問題.函數

同步問題示例post

public class SyncThread extends Thread {
    private static final String TAG = "SyncThread";
    private Pay pay;

    public SyncThread(String name, Pay pay) {
        super(name);
        this.pay = pay;

    }
    
    @Override
    public void run() {
        while (isRunning) {
            pay.count();
        }
    }
}

Pay.java
    /**
     * 未同步時
     */
    public void count() {
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + ":>" + count--);
        } else {
            isRunning = false;
        }
    }
複製代碼
//未使用同步時
D: 線程3:>912
D: 線程1:>911
D: 線程2:>911 //此時已經出現數據不同
D: 線程1:>909
D: 線程3:>910
D: 線程2:>908
D: 線程1:>907
D: 線程3:>906
D: 線程2:>905
D: 線程1:>904
D: 線程2:>903
D: 線程3:>903
D: 線程1:>902
D: 線程2:>901
D: 線程3:>900

//使用同步時
D: 線程1:>1000
D: 線程2:>999
D: 線程2:>998
D: 線程1:>997
D: 線程2:>996
D: 線程1:>995
D: 線程2:>994
D: 線程1:>993
D: 線程2:>992
D: 線程1:>991
D: 線程2:>990
D: 線程1:>989
D: 線程2:>988
D: 線程1:>987
D: 線程2:>986
D: 線程1:>985
D: 線程2:>984
D: 線程1:>983
複製代碼

3.1 鎖機制

爲了解決異步的問題,JAVA提供了鎖的機制,synchronized 關鍵字.能夠很方便的實現線程的同步.

先理解如何進行手動加鎖,這樣更容易理解自動加鎖的機制.

3.1.1 認識重入鎖與條件對象
  1. 重入鎖:對資源進行手動枷鎖.
  • ReentrantLock() : 建立一個ReentrantLock實例
  • lock() : 得到鎖
  • unlock() : 釋放鎖
  1. 條件對象: 使線程知足某一條件後才能執行,不知足則進阻,放鎖.用於管理已得到鎖可是暫時沒做用的線程.
  • lock.newCondition : 得到一個條件對象.
  • condition.await() :進阻,放鎖.至關於obj.wait方法.
  • condition.signal/signalAll : 喚醒在此條件上阻塞的任意某個線程/全部線程.至關於obj.notify/notifyAll

僞代碼

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition;
lock.lock();
        try {
            if(count == 0){
                //進阻,放鎖
                condition.await();
            }
            //喚醒所以條件,而阻塞的全部線程
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
           lock.unlock(); 
        }

複製代碼
3.1.2 synchronized 關鍵字

資源互斥,線程數據同步,提供了自動加鎖.同步本質見java內存模型.

  • 對象鎖: 每個對象有一個內部鎖,而且只有一個內部條件.
    • 對應synchronized關鍵字,若是是多個線程訪問同個對象的sychronized塊,纔是同步的,可是訪問不一樣對象的話就是不一樣步的。
  • 類鎖: 是一個全局鎖
    • 對應static sychronized關鍵字,不管是多線程訪問單個對象仍是多個對象的sychronized塊,都是同步的

在實際開發中大多數狀況使用同步方法與同步代碼塊,實現同步,除非一些須要高度控制鎖的則使用重入鎖和條件對象.

3.2 實現同步的方式

線程同步的四種方式.

1. 同步方法

private synchronized void countSyncMethod() {
        if (count > 0) {
            Log.d(TAG, Thread.currentThread().getName() + ":>" + count--);
        } else {
            isRunning = false;
        }
    }
複製代碼

2. 同步代碼塊

private void countSyncCode() {
        synchronized (this) {
            if (count > 0) {
                Log.d(TAG, Thread.currentThread().getName() + ":>" + count--);
            } else {
                isRunning = false;
            }
        }
    }
複製代碼

3. 使用重入鎖

private void countSyncLock() {
        mLock.lock();
        try {
            if (count > 0) {
                Log.d(TAG, Thread.currentThread().getName() + ":>" + count--);
            } else {
                isRunning = false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }inally{
           mLock.unlock(); 
        }

    }
複製代碼

4. 使用特殊域變量(volatile)

3.3 volatile 關鍵字

先了解java內存模型與「三性」知識.

3.3.1 Java內存模型

java內存模型定義了線程和主內存之間的抽象關係.

  • 全部線程的共享變量在主內存中.
  • 每一個線程都有一個本地內存.

java內存模型基礎圖

線程在對變量進行存與取時,通常先改變工做內存的變量值,再在某個時機刷新到主存中去.這樣就會致使多線程併發時另外一個線程從主存中獲取到的不必定是最新的值.

3.3.2 Java併發編程的原子性、可見性、有序性
  • 原子性:對於基本數據類型只是簡單的讀取和賦值(將數字賦值給某個變量),僅有一個操做就是原子性操做,操做是不可中斷的.
  • 可見性:一個線程的修改對另一個線程是當即可見的.
    • 即volatile修飾的變量,若是在一個線程修改值,則會當即更新到主存中去,那麼另外一個線程會獲取到最新的值.
  • 有序性:編譯時和運行時會對指令進行從新排序,會影響多線程併發執行的正確性.

這樣當一個共享變量被volatile修飾時.

  1. 保證可見性,即一個線程對變量值進行了修改,另外一個線程能夠當即獲取到最新修改後的值.
  2. 保證有序性,即禁止指令重排序.
  3. 不保證原子性.
    • 因此不適用於修飾如自增自減等一些依賴於自身或者其餘變量值的變量時.
private int x;
private int y;

//依賴自身
x++;
//依賴其餘變量
if(x > y){
    x = y;
}
複製代碼

四. 線程的中斷

  • 關鍵字: interrupted
    中斷目標線程,並非指當即中止線程,而僅僅只是將線程的標識爲true,通常由目標線程本身去檢測並決定是否終止線程。

示例代碼

@Override
    public  void run() {
        //中斷目標線程,並非指當即中止線程,而僅僅只是將線程的標識爲true,通常由目標線程本身去檢測並決定是否終止線程.
        for (int j = 0; j <100000000 ; j++) {
            if (Thread.currentThread().isInterrupted()){
                System.out.println("Interrupted!已經中斷中止輸出.開始收尾工做1");
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                //處於阻塞狀態的線程,也會立馬被中斷,因此就會拋出此異常.
                System.out.println("Interrupted!已經中斷中止輸出.開始收尾工做2");
                return;
            }
            System.out.println("還沒中斷繼續輸出j:" + j);
        }
    }

複製代碼

關於Thread基礎相關就介紹到這裏了.接着下一篇介紹多線程編程中的線程池.Demo源碼在最後一篇一塊兒給出.

因爲本人技術有限,若有錯誤的地方,麻煩你們給我提出來,本人不勝感激,你們一塊兒學習進步.

參考連接:

相關文章
相關標籤/搜索