26、多執行緒與並行

傾聽雪的聲音發表於2020-10-17

一、建立執行緒

1、有一個實現了runnable介面的任務類
2、利用介面來建立任務
3、將任務放線上程中執行(執行緒由thread類建立,任務就是程式碼,需要載入進執行緒)
4、呼叫執行緒的start方法來執行執行緒

程式碼示例:並行列印100個a,與100個b。

import java.lang.Thread;
public class text
{
    public static void main(String[] argc)
    {
    	//建立任務
        Runnable printA=new PrintChar('a',100);
        Runnable printB=new PrintChar('b',100);
	//建立執行緒並載入任務
        Thread thread1=new Thread(printA);
        Thread thread2=new Thread(printB);
	//啟動執行緒
        thread1.start();
        thread2.start();
    }
    static class PrintChar implements Runnable
    {
        private char data;
        private int times;
        PrintChar(char data,int times)
        {
            this.data=data;
            this.times=times;
        }
        @Override
        public void run()
        {
            for(int i=0;i<times;i++)
            {
                System.out.println(data);
            }
        }
    }
}

二、執行緒池與資料不一致性

ExcutorService來管理執行緒池
Excutors中提供兩個靜態方法來建立執行緒池,

newFixedThreadPool(int);//固定池中執行緒數目,返回ExcutorService
newCachedThreadPool();//返回ExcutorService
import java.lang.Thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class text{
    private static int balance;
    public static void main(String[] args) {
        ExecutorService executor= Executors.newCachedThreadPool();
        for(int i=0;i<1000;i++)
        {
            executor.execute(new AddThead());
            reduce();
        }
        executor.shutdown();//等所有執行緒結束就關閉執行緒池
        while (!executor.isTerminated())
        {
            System.out.println(balance);//直接輸出不行,主執行緒也是一個執行緒.主執行緒要迴圈檢查是否全部關閉了
        }
    }
    private static class AddThead implements Runnable
    {
        @Override
        public void run()
        {
            add();//使用內部類協助完成增加的多執行緒
            try
            {
                Thread.sleep(5);
            }
            catch (InterruptedException ex)
            {
            }
        }
    }
    public  static void add() {
        balance++;
    }
 }

輸出並不是1000,產生了資料不一致性

三、執行緒同步

一、synchronized關鍵字

在add()函式前加上synchronized就設立了臨界區,可避免資料不一致

public synchronized static void add() {
    balance++;
}

二、利用加鎖同步

synchronized關鍵字是隱式加鎖,Java還支援顯示加鎖。
一個鎖是一個Lock介面的例項,ReentrantLock是Lock的具體實現,用於建立相互排斥的鎖。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class text{
    private static int balance;
    public static void main(String[] args) {
        ExecutorService executor= Executors.newCachedThreadPool();
        for(int i=0;i<2000;i++)
        {
            executor.execute(new AddThead());
        }
        executor.shutdown();//等所有執行緒結束就關閉執行緒池
        while (!executor.isTerminated())
        {
            System.out.println(balance);//直接輸出不行,主執行緒也是一個執行緒.主執行緒要迴圈檢查是否全部關閉了
        }
    }
    private static class AddThead implements Runnable
    {
        //這裡一定要用static,不然每個物件都有一把鎖,都能自由訪問,相當於沒鎖。
        private static Lock lock=new ReentrantLock();
        @Override
        public void run()
        {
            lock.lock();
            add();
            lock.unlock();
        }
    }
    public static void add(){
        balance++;
    }
}

一定要注意有幾把鎖,不要一個執行緒裡一把,這樣鎖不住。

四、執行緒協作

條件是通過呼叫Lock物件的newCondition()方法建立的物件。有了條件物件,就可以利用

await():引起當前執行緒等待
signal():喚醒該鎖上的一個等待執行緒
signalAll():喚醒該鎖上所有的等待執行緒

方法來實現執行緒間的相互通訊。

程式設計示例:一個執行緒每隔1秒就存10元,每次存完都喚醒取錢的。另一個執行緒一直想取12元,不夠就睡眠,被喚醒就又嘗試取錢。
程式碼連結.

程式設計示例:執行緒模擬生成者消費者問題
上一個銀行賬戶存取款,在一個鎖上只產生了一個條件,在生成者消費者問題中一個鎖上有兩個條件,分別代表不同的狀態。它們相互喚醒。
生產者消費者程式碼連結.

總結

這兩個程式設計示例的套路都是,資源自己實現具有鎖的介面,多執行緒只管呼叫即可,某執行緒被什麼條件睡眠,就得這個條件被解才能醒來。條件可以被自己喚醒,也可以被同一鎖上的其它條件喚醒。

五、阻塞佇列

阻塞佇列是一種會阻塞執行緒的佇列,又能存東西,又自帶阻塞功能。執行緒A往阻塞佇列裡放東西,如果佇列滿了,則A被阻塞,如果A迴圈嘗試放,當佇列有位置時又可以放進去了。
取東西和放東西類似。
ArrayBlockingQueue:陣列實現阻塞佇列,建構函式必須指定佇列容量。
LinkedBlockingQueue:鏈式阻塞佇列,構造時可以不指定容量,這時可以在記憶體允許下放無數個元素。
PriorityBlockingQueue:優先佇列,也可以不指定容量。注意:這裡的優先是指元素位置的優先,不是阻塞執行緒誰優先。

阻塞佇列簡化生成者消費者:之前寫的生成者消費者程式碼時廢了好大的勁才寫了一個具有阻塞執行緒功能的Buffer類,現在有阻塞佇列了,就不用寫buffer類了。
程式碼連結.

佇列新增和取元素時用put和take,用offer和poll無阻塞功能

六、訊號量

訊號量操作:

Semaphore semaphore =new Semaphore(3);//允許三個執行緒
semaphore.acquire();//獲得一個訊號量
semaphore.release();//釋放一個訊號量

鎖與訊號量:
訊號量多是用來同步的,協調順序的。鎖是用來保護資源,互斥的(互斥也是一種同步)。只有一個許可的訊號量類似一個互斥鎖。

七、死鎖

發生執行緒之間相互等待資源,就叫發生了死鎖。有沒有死鎖可以使用資源分配圖來判斷(每個資源只供一份)。
死鎖示例.

八、同步合集

java合集中的類如線性表、集合、對映表等不是執行緒安全的。collections中提供了6個靜態方法來將合集轉化為同步版本。

相關文章