Java多線程基礎

1. 線程和進程的區別聯繫

進程:操作系統運行中的程序(進程和程序的區別是程序只是一個靜態的指令集合,而進程則是系統中一個活動的指令集合,加入了時間)。

線程:進程能夠執行多項任務,而每一項任務就相當於一個線程。

進程之間不能共享資源,但是線程之間可以共享資源。

2. 併發性和並行性區別

並行: 有多條指令在多個處理器上同時執行。

併發: 同一時刻只有一條指令執行,但是多個進程指令會被快速輪換執行,使得看起來好像有多個進程在同時執行。

3. 創建線程的方式

1) 繼承Thread類來創建線程類,然後要重寫run()方法。

2)實現Runnable接口來創建線程類,然後實現run()方法,將創建的實現Runnable接口的對象作爲Thread的target。

3)實現Callable接口,功能是Runnable的增強版本(java5之後提供)。接口中定義的方法有返回值,可以拋出異常。

實現Runnable或Callable接口

優點:

1)線程類只是實現了Runnable接口或Callable接口,它依然還可以繼承其他類。

2)多個線程可以同時共享一個target對象,十分適合多個相同線程來處理同一份資源。

缺點:

編碼可能較爲複雜,如果要訪問當前線程的話,則必須要使用Thread.currentThread()方法。

繼承Thread類

優點:
- 程序編寫起來簡單,如果需要訪問當前線程的話,不用使用Thread.currentThread()方法,直接使用this即可。

缺點:
- 線程類以及繼承了Thread類,不能再繼承其他父類了。

4. 線程的生命週期

線程的生命週期
線程5種狀態:

1)new(創建):線程通過new方法創建。

2)Runnable(就緒):當線程調用start()方法,線程進入就緒狀態,等待系統調度。

3)Running(運行): 當系統調度時,線程進入運行狀態。

4)Blocked(阻塞): 一個正在運行的線程因某些原因不能繼續運行時,它就進入阻塞狀態。

5)Dead(死亡):線程正常結束或異常退出。

線程阻塞的幾種情形:

1)處於運行狀態的線程若遇到sleep()方法,則線程進入睡眠狀態,不會讓出資源鎖,sleep()方法結束,線程轉爲就緒狀態,等待系統重新調度。
2)處於運行狀態的線程可能在等待io,也可能進入掛起狀態。io完成,轉爲就緒狀態。
3)處於運行狀態的線程調用yield()方法,線程轉爲就緒狀態。(yield只讓給權限比自己高的)。
4)處於運行狀態的線程遇到wait()方法(object的方法),線程處於等待狀態,需要notify()/notifyALL()來喚醒線程,喚醒後的線程處於鎖定狀態,獲取了“同步鎖”,之後,線程才轉爲就緒狀態。
5)處於運行的線程synchronized,加上後變成同步操作。處於鎖定狀態,獲取了“同步鎖”,之後,線程才轉爲就緒狀態。

5. 線程控制

1)join()方法(Thread類提供的靜態方法) java 線程方法join的簡單總結
說明:讓一個線程等待另一個線程執行完成。當前線程必須等待調用join()方法的線程執行完成後,才能繼續向下執行。

2)sleep()方法(Thread類提供的靜態方法)
說明:暫停當前線程,並進入阻塞狀態,這時,如果有其它線程,則其它線程會執行。

3)yield()方法(Thread類提供的靜態方法)
說明:暫停當前線程,將當前線程轉入就緒狀態,不是阻塞狀態,這時只有大於等於當前線程優先級狀態的線程才能獲得從就緒狀態轉入運行狀態的機會。

6. 線程同步

同步代碼塊

//格式
synchronized(obj) {
    ......
    //此處代碼爲同步代碼塊代碼
}

說明:上面格式中的obj就是同步監視器。當線程開始執行同步代碼塊之前,必須先得到同步監視器的鎖定。這樣可以保證同一時刻只有一個線程能執行當前代碼,從而保證線程安全。

同步方法

//格式
public synchronized void draw() {
    ......
}

說明:對synchronized修飾的實例方法(非static方法)來說,不需要顯示的指定同步監視器,同步方法的同步監視器是this,也就是調用該方法的對象。

釋放同步監視器的時機:

1.)當前線程的同步方法、同步代碼塊執行結束,當前線程就會釋放同步監視器。
2)當前線程的同步方法、同步代碼塊出現了未處理的Error和Exception,導致代碼塊異常結束了。
3)當前線程執行同步方法、同步代碼塊時,程序執行了同步監視器對象的wait()方法,則當前線程暫停,並釋放同步監視器。

java中的synchronized(同步代碼塊和同步方法的區別)

同步鎖Lock

通過顯示定義同步鎖對象來實現同步,此時同步鎖是由Lock對象來充當。(java5之後實現)

常用: ReentrantLock(可重入鎖)

//格式
class X {
    //定義鎖對象
    private final ReentrantLock lock = new ReentrantLock(); 
    //...
    //定義需要保證線程安全的方法
    public void m() {
        //加鎖
        lock.lock(); 
        try {
            //需要保證線程安全的代碼
            //...method body
        } finally {
            lock.unlock(); 
        }

    }
}

優點:使用同步鎖Lock比同步方法和同步代碼塊更加靈活!

7. 死鎖

兩個線程相互等待對方釋放同步監視器時就會發生死鎖。

死鎖很容易發生,特別是在系統中出現了多個同步監視器的情況下。

8. 線程間通訊

傳統線程通訊(對應同步代碼塊和同步方法情況)

調用Object類的wait()、notify()、notifyAll()方法來實現線程間通訊,這三個方法是jvm中的native方法。

wait():

調用某個對象的wait()方法能讓當前線程阻塞,並且當前線程必須擁有此對象的monitor(即鎖)

notify():

調用某個對象的notify()方法能夠喚醒一個正在等待這個對象的monitor的線程,如果有多個線程都在等待這個對象的monitor,則只能喚醒其中一個線程;。

notifyAll():

調用notifyAll()方法能夠喚醒所有正在等待這個對象的monitor的線程;

注意: 一個線程被喚醒不代表立即獲取了對象的monitor,只有等調用完notify()或者notifyAll()並退出synchronized塊,釋放對象鎖後,其餘線程纔可獲得鎖執行。

Condition(對應Lock的情況)(JDK1.5之後出現)

Condition類中提供了await()、signal()、signalAll()方法來對應wait()、notify()、notifyAll()方法,使用方法基本相同。

//格式
class X {
    //定義鎖對象
    private final Lock lock = new ReentrantLock(); 
    private final Condition cond = lock.newCondition();

    ....

    cond.await();
    cond.signal();
    cond.signalAll();
}

相比使用Object的wait()、notify(),使用Condition的await()、signal()這種方式實現線程間協作更加安全和高效。

BlockingQueue(java5提供)

它是Queue的子接口,作用不是作爲容器,而是作爲一個線程同步的工具。

特徵:

(經典的生產者消費者模型)

當生產者線程嘗試向BlockingQueue中放入元素時,如果隊列已經滿了,那麼線程就會被阻塞;

當消費者線程嘗試向BlockingQueue中取出元素時,如果隊列已空,那麼線程也會被阻塞;

對應put()方法和take()方法。

9. 線程池

線程池原理

1)線程池能夠提高系統性能

說明: 系統啓動線程成本高,因爲涉及到和操作系統的交互。這種情況下,使用線程池能夠提高系統性能。特別是需要創建大量生存期比較短的線程時候,才更需要使用線程池。

2)線程池的最大線程數參數可以控制系統中併發線程數目不會超過設置的線程數

說明: 當系統中包含大量併發線程時,會導致系統性能劇烈下降,甚至導致JVM崩潰。

過程: 線程池在系統啓動時就會創建大量空閒的線程,程序將一個Runnable對象或Callable對象傳給線程池,則線程池就會啓動一個線程來執行它們的run()或call()方法,當run()或call()方法執行結束之後,線程不會死亡,它會再次返回到線程池中從而成爲空閒狀態,等待下一個run()或call()方法。

Executors工廠類來產生線程池。

public class ThreadPoolTest {
    pulic static void main(String[] args) throws Exception {
        //創建一個具有固定線程數(6)的線程池
        ExecutorService pool = Executors.newFixedThreadPool(6);
        //使用Lambda表達式創建Runnable對象
        Runnable target = () -> {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "的i值爲:" + i);
            }
        };
        //向線程池中提交兩個線程
        pool.submit(target);
        pool.submit(target);
        //關閉線程池
        pool.shutdown();
    }  
}

9. ThreadLocal類

線程局部變量(ThreadLocal)爲每一個使用該變量的線程都提供一個變量值的副本,使每一個線程都可以獨立地改變自己的副本,而不會和其他線程的副本衝突。

ThreadLocal類方法 :

T get() : 取得當前線程副本數據值

void remove() :刪除當前線程副本數據值

void set(T value) :設置當前線程副本數據的值

ThreadLocal類是從另一個角度來解決多線程的併發訪問,ThreadLocal將需要併發訪問的資源複製多份,每個線程擁有一份資源,每個線程都擁有自己的資源副本,從而也就沒有必要對該變量進行同步了。

實用場景:

1)如果多個線程之間需要共享資源,來達到線程之間的通信功能,就使用同步機制。

2)如果只需要隔離多個線程之間的共享衝突,可以使用ThreadLocal。

10. Volatile關鍵字

深入分析volatile的實現原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章