1. 程式人生 > >多執行緒和併發

多執行緒和併發

多執行緒

java執行緒分為六個狀態:
    初始狀態(new):新建立一個執行緒物件,但還沒有呼叫start()方法。
    執行狀態(runnable):java執行緒中將就緒(ready)和執行中(running)兩種狀態合起來叫做執行。
    阻塞狀態(blocked):表示執行緒被鎖阻塞了。
    等待狀態(waiting):表示執行緒需要等待其他執行緒做出一些特定動作(通知此執行緒或者終端)。
    超時等待(time_waiting):這個狀態不等於waiting,它可以定時來進行自動返回,這是等待做不到的。
    終止(terminnated):表示執行緒執行完畢,結束執行緒。
    ![執行緒執行狀態圖](https://img-blog.csdn.net/20180813135745340?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNzg0MTA1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

1. 初始狀態:實現runnable介面和繼承Thread可以得到執行緒類,new一個例項出來,執行緒就進入了初始狀態。
2. 就緒狀態:
a)就緒狀態只是說你有資格執行,排程程式沒有挑選到你,你就永遠是就緒狀態。
b)呼叫現成的start()方法,此執行緒進入就緒狀態。
c)當前執行緒sleep()方法結束,其他執行緒join()結束,等待使用者輸入完畢,某個執行緒拿到物件鎖,這些執行緒也將進入就緒狀態。
d)當前執行緒時間片用完了,呼叫當前執行緒的yield()方法,當前執行緒進入就緒狀態。
e)鎖池裡的執行緒拿到物件鎖後,進入就緒狀態。
3.執行中狀態:
執行緒排程程式從可執行池中選擇一個執行緒作為當前執行緒時執行緒所處的狀態。這也是執行緒進入執行狀態的唯一一種方式。
4.阻塞狀態:
阻塞狀態時執行緒阻塞在進入synchronzied關鍵字修飾的方法或程式碼塊(獲取鎖)時的狀態。
5.終止狀態
a)當執行緒的run()方法完成時,或者主執行緒main()方法完成時,我們就認為它終止了。這個執行緒物件也許是活的,但是,它已經不是一個單獨執行的執行緒了。執行緒一旦終止了,就不能復生。
b)在一個終止的執行緒上呼叫start()方法,或丟擲java.lang.lllegalThreadStateException異常。
6.等待佇列
a)呼叫obj中的wait(),notify()方法前,必須獲得obj鎖,也就是必須寫在synchronized(obj)程式碼段內。
b)與等待佇列相關的步驟和圖
等待順序圖


1)執行緒1獲取得到物件A的鎖,正在使用物件A。
2)執行緒1呼叫物件A的wait()方法。
3)執行緒1釋放物件A的鎖,並且馬上進入等待佇列。
4)鎖池中的物件爭奪物件A的鎖。
5)執行緒5獲得物件A的鎖,進入synchronized塊,使用物件A。
6)執行緒5呼叫物件A的notifyAll()方法,喚醒所有的執行緒,所有執行緒進入同步佇列。如果執行緒5呼叫A物件的notify()方法,那麼喚醒一個執行緒,不知道會喚醒誰,被喚醒的執行緒進入同步佇列。
7)notify()方法所在的synchronized結束,執行緒5釋放物件A的鎖。
8)同步佇列的執行緒爭搶物件鎖,但是執行緒1什麼時候搶到就未知了。
3. 那麼什麼是同步佇列呢?
a)當前執行緒想呼叫物件A的同步方法時,發現物件A的鎖被別的執行緒佔有,此時當前執行緒進入同步佇列。其實也就是說同步佇列中的執行緒都是想爭奪物件鎖的執行緒。
b)當一個執行緒被另一個執行緒喚醒的時候,此執行緒進入同步佇列,去爭奪物件鎖。
c)同步佇列是在同步的環境下才有的概念,一個物件對應一個同步佇列。
4. 簡述幾個常看見的方法:
a)Thread.sleep(long millis),一定是當前執行緒呼叫此方法,當前執行緒進入Time_waiting狀態,但不釋放物件鎖,millis後執行緒自動甦醒進入就緒狀態,作用:給其他執行緒執行機會的最佳方式。
b)Thread.yield(),一定是當前執行緒呼叫此方法,當前執行緒放棄獲取的cpu時間片,由執行狀態變會就緒狀態,讓OS再次選擇執行緒。作用:讓相同優先順序的執行緒輪流執行,但並不保證一定會輪流執行。實際中無法保證yield()達到讓步目的,因為讓步的執行緒還有可能被執行緒排程程式再次選中。Thread.yield()不會導致阻塞。
c)t.join()/t.join(long millis),當前執行緒裡呼叫其它執行緒t的join方法,當前執行緒進入TIME_WAITING/TIME_WAITING狀態,當前執行緒不釋放已經持有的物件鎖。執行緒t執行完畢或者millis時間到,當前執行緒進入就緒狀態。
d)obj.wait(),當前執行緒呼叫物件的wait()方法,當前執行緒釋放物件鎖,進入等待佇列。依靠notify()/notifyAll()喚醒或者wait(long timeout)timeout時間到自動喚醒。
e)obj.notify()喚醒在此物件監視器上等待的單個執行緒,選擇是任意性的。notifyAll()喚醒在此物件監視器上等待的所有執行緒。

Lock鎖

  1. synchronized和lock的優缺點
    a)java中實現多執行緒下物件的同步訪問方法:
    synchronized關鍵字
    Lock介面及其實現類ReentrantLock和讀寫鎖ReentrantReadWritLock
    b)差別:
    1)使用synchronized關鍵字,鎖的控制和釋放實在synchronized同步程式碼塊的開始位置和結束位置。而Lock實現同步時,鎖的獲取和釋放可以在不同的程式碼塊,不同的方法中。這一點是基於使用者手動獲取和釋放鎖的特性。
    2)lock介面提供了試圖獲取鎖的trylock()方法,在呼叫tryLock()獲取鎖失敗時候返回false,這樣執行緒可以執行其他的操作,而不至於使執行緒進入休眠。trylock()方法可進入一個long型的時間引數,允許在一定時間內獲取鎖。
    3)lock介面的實現類ReentrantReadWriteLock提供了讀鎖和寫鎖,允許多個執行緒獲得讀鎖。而只能有一個執行緒獲得寫鎖。讀鎖和寫鎖不能同時獲得。這實現了讀和寫分離。
    2.lock鎖詳解
    Reentrantlock可以讓程式碼塊原子執行,但是比synchronized更加強大,Reentrantlock具有嗅探鎖定,多路分支通知等功能。
    嗅探鎖定:獲取鎖的時候如果鎖已經被其他執行緒獲取到Reentrantlock可以進行指定等待時間獲取鎖或者多路分支通知。
    多路分支通知:縣城發生await時候,縣城可以選擇註冊在不同的監聽器Condition物件上,在適當的時候可以選擇指定的監聽器Condition物件上的執行緒進行signal通知,執行。
    程式碼例子:
//讓t3最後執行
public static void main(String[] args ) {
    TargetLock t1 = new TargetLock();

    Thread t1 = new Thread(t1,"哈哈");
    Thread t2 = new Thread(t1,"嘻嘻");
    Thread t3 = new Thread(t1,"呵呵");
    t1.start();
    t2.start();
    t3.start(); 
}

lock實現程式碼:

public calss TargetLock implements Runnable {
    //生成鎖物件  上鎖/解鎖
    //如果需要用到ReentrantLock等子類的特有的管理方法  需向下轉型
    private static ReentrantLock lock = new ReentrantLock();
    //獲取所對應的狀態  等待/喚醒(單個/所有)
    private static Condition cd = lock.newCondition();

    public run() {
        String ctName = Thread.currentThread().getName();
        //上鎖
        lock.lock();
        //由於在上鎖期間可能出現太多的不可控制因素,導致執行緒中斷或者損害
        //而形成死鎖狀態,為了處理這類的問題導致的死鎖,利用try..finally
        //結構保證解鎖操作一定會被執行
        try{
            //呵呵最後進入  之前進入的執行緒去等待佇列
            if(!ctName.equals("呵呵")) {
                System.out.println(ctName + "去等待了...");
                try {
                    cd.await();
                } catch (InterruptedException e) {
                    e.printStcakTrace();
                }
            } else {
                System.setProperty(ctName,"false");
            }

            // 如果呵呵已經完事 會喚醒所有等待執行緒 且需要執行緒之間通過不斷爭搶CPU來獲取執行權
            // 需在執行邏輯程式碼塊之前 提前解鎖

            if (Boolean.getBoolean("呵呵")) {
                if (lock.isLocked()) {
                    lock.unlock();
                }

            }

            // 執行的邏輯程式碼塊
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(ctName + "在執行...");
            }

            // 執行完畢去喚醒一個執行緒
//          cd.signal();

            // 喚醒所有執行緒 --> 嘻嘻 和 哈哈相互列印
            if (lock.isLocked()) {
                cd.signalAll();
            }

            // 當呵呵完事 給呵呵一個標識
            if (ctName.equals("呵呵")){
                System.setProperty(ctName, "true");
            }

        } finally {
            // 解鎖
            if (lock.isLocked()) {
            lock.unlock();
            }
        }
    }
}

ReentrantLock

//小程式例子
public class MyService {
    private Lock lock = new ReentrantLock();
    public void testMethod() {
        lock.lock();
        for(int i = 0; i < 5; i++) {
            System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1)));
        }
        lock.unlock();
    }
}

//ReentrantLock()的wait方法

public class MyWait {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    try {
        lock.lock();
        System.out.println("開始wait");
        condition.await();
        for(int i = 0; i < 5; i++) {
            System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1)));
        }
    }catch (InterruptedException e) {
        e.printStackTrace();
    }finally {
        lock.unlock();
    }
}

//使用signal方法喚醒wait執行緒

public void signal() {
    try {
        lock.lock();
        condition.signal();
    }catch (){
    }finally {
        lock.unlock();
    }
}

一個condition物件的signal(signalAll)方法和該物件的await方法一一對應,不能喚醒其他condition物件的await方法。
ReentrantLock類可以喚醒指定條件的執行緒,而object的喚醒是隨機的。

Condition和Object比較
Condition的await方法和Object的wait方法等效。
Condition的signal方法和Object的notify方法等效。
Condition的signalAll方法和Obkect的notifyAll方法等效。
lock的公平鎖和非公平鎖

//公平鎖指的是執行緒獲取鎖的順序是按照加鎖順序來的
Lock lock = new ReentrantLock(true);
//非公平鎖是搶鎖機制
Lock lock = new ReentrantLock(false);
**ReentrantLock的兩種常用方法**
    tryLock()能獲得鎖就返回true,不能就立即返回false
    tryLock(long timeout,TimeUnit unit)可以增加時間機制,如果超過該時間段還沒獲得鎖,就返回falselock()能獲得鎖就返回true,不能就一直等待獲取鎖。
    兩個執行緒分別執行lock()和lockInterruptibly(),中斷兩個執行緒,前者不會報異常,後者會丟擲異常。

ReentrantLock是互斥排他的,效率並不高
//一個小例子說明

public void read() {
        try {
            try {
                lock.readLock().lock();
                System.out.println("獲得讀鎖" + Thread.currentThread().getName()
                        + " " + System.currentTimeMillis());
                Thread.sleep(10000);
            } finally {
                lock.readLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void write() {
        try {
            try {
                lock.writeLock().lock();
                System.out.println("獲得寫鎖" + Thread.currentThread().getName()
                        + " " + System.currentTimeMillis());
                Thread.sleep(10000);
            } finally {
                lock.writeLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 }

Lock類也可以實現執行緒同步,而Lock獲得鎖需要執行lock方法,釋放鎖需要執行unLock方法。
Lock類可以建立Condition物件,Condition物件用來是執行緒等待和喚醒執行緒,需要注意的是Condition物件的喚醒的是用同一個Condition執行await方法的執行緒,所以也就可以實現喚醒指定類的執行緒。
Lock類分公平鎖和不公平鎖,公平鎖是按照加鎖順序來的,非公平鎖是不按順序的,也就是說先執行lock方法的鎖不一定先獲得鎖。
Lock類有讀鎖和寫鎖,讀讀共享,寫寫互斥,讀寫互斥。