并发与多线程
并發(fā)
并發(fā)(concurrency)是指CPU在某個(gè)時(shí)間段內(nèi)交替處理多任務(wù)的能力。每個(gè)CPU不可能只顧著執(zhí)行某個(gè)進(jìn)程,而讓其他進(jìn)程一直等待被執(zhí)行。所以,CPU把可執(zhí)行時(shí)間均分成若干份,每個(gè)進(jìn)程執(zhí)行一份或多份時(shí)間后,記錄當(dāng)前的工作狀態(tài),釋放相關(guān)資源并進(jìn)入等待狀態(tài),讓其他進(jìn)程搶占CPU等資源。
在并發(fā)環(huán)境下,由于程序的封閉性被打破,出現(xiàn)了以下特點(diǎn):
1.并發(fā)程序之間有相互制約的關(guān)系。直接制約體現(xiàn)在一個(gè)程序需要另一個(gè)程序的計(jì)算結(jié)果;間接制約體現(xiàn)在多個(gè)進(jìn)程競爭共享資源。
2.并發(fā)程序的執(zhí)行過程是斷斷續(xù)續(xù)的。程序需要保留現(xiàn)場,記憶現(xiàn)場指令及執(zhí)行點(diǎn)。
3.當(dāng)并發(fā)數(shù)設(shè)置合理并且CPU擁有足夠的處理能力時(shí),并發(fā)會提高程序的運(yùn)行效率。
在Java編程中,并發(fā)主要與線程有關(guān)。
線程
線程是CPU調(diào)度和分派的基本單位,為了更充分地利用CPU資源,一般都會使用多線程進(jìn)行處理。多線程的作用是提高任務(wù)的平均執(zhí)行速度,但是會導(dǎo)致程序可解性變差,編程難度加大。所以,合適的線程數(shù)才能讓CPU資源被充分利用。
每一個(gè)線程都有自己的操作棧、程序計(jì)數(shù)器、局部變量表等資源。同一進(jìn)程內(nèi)的所有線程都可以共享該進(jìn)程的所有資源。
Java提供了兩種形式定義線程類:
1.實(shí)現(xiàn)Runnable接口并重寫其中的run()方法。
1 class Consumer implements Runnable { 2 3 private Store store; 4 5 public Consumer(Store store) { 6 this.store = store; 7 } 8 9 @Override 10 public void run() { 11 for (int i = 0; i < 1000; i++) { 12 store.getValue(); 13 } 14 } 15 16 } Consumer2.繼承Thread類并重寫其中的run()方法。
1 class Producer extends Thread { 2 3 private Store store; 4 5 public Producer(Store store) { 6 this.store = store; 7 } 8 9 @Override 10 public void run() { 11 for (int i = 0; i < 1000; i++) { 12 store.setValue((int) (Math.random() * 100)); 13 } 14 } 15 16 } Producer里氏代換原則對繼承的一個(gè)約束是子類不重寫父類的非抽象方法,而Thread類的run()方法不是一個(gè)抽象方法,所以繼承Thread類并重寫其中的run()方法就不符合里氏代換原則,該方式不推薦使用。相比之下,實(shí)現(xiàn)Runnable接口可以使編程更加靈活,對外暴露的細(xì)節(jié)也比較少,讓使用者專注于實(shí)現(xiàn)線程的run()方法。
線程狀態(tài)
線程的生命周期分為以下5種狀態(tài):
新建狀態(tài)
新建狀態(tài)是線程被創(chuàng)建且未啟動的狀態(tài)。也就是說,初始化一個(gè)線程對象時(shí),該對象進(jìn)入新建狀態(tài)。
線程對象的初始化分為2種:
1.如果是繼承Thread類的線程類,則該類線程對象可以直接通過new運(yùn)算進(jìn)行初始化。
2.如果是實(shí)現(xiàn)Runnable接口的線程類,則該類線程對象通過new運(yùn)算進(jìn)行初始化后需要包裝為一個(gè)Thread對象。
就緒狀態(tài)
就緒狀態(tài)是線程啟動后運(yùn)行之前的狀態(tài)。即啟動了的線程在準(zhǔn)備執(zhí)行run()方法時(shí)的狀態(tài)。
線程的啟動是指線程對象調(diào)用Thread的start()方法。
運(yùn)行狀態(tài)
運(yùn)行狀態(tài)是線程運(yùn)行時(shí)的狀態(tài),即啟動了的線程在執(zhí)行run()方法時(shí)的狀態(tài)。
阻塞狀態(tài)
阻塞狀態(tài)分以下3種情況:
同步阻塞:缺少資源無法繼續(xù)運(yùn)行。搶占到資源后會退出該狀態(tài)。
主動阻塞:主動讓出CPU執(zhí)行權(quán),即線程執(zhí)行Thread的sleep()方法之后的狀態(tài)。調(diào)用sleep()方法時(shí)會傳入一個(gè)long類型的參數(shù),表示睡眠的時(shí)間,單位為毫秒,時(shí)間結(jié)束時(shí)會退出該狀態(tài)。
等待阻塞:進(jìn)入睡眠,即線程執(zhí)行Object的wait()方法之后的狀態(tài)。其他線程執(zhí)行Object的notify()方法或notifyAll()方法之后會退出該狀態(tài)。
終止?fàn)顟B(tài)
終止?fàn)顟B(tài)是線程執(zhí)行結(jié)束或因異常退出后的狀態(tài)。
線程同步
線程同步機(jī)制的主要任務(wù)是,對多個(gè)相關(guān)線程在執(zhí)行次序上進(jìn)行協(xié)調(diào),使并發(fā)執(zhí)行的每個(gè)線程之間能按照一定的時(shí)序共享資源,并能很好地相互合作,從而使程序的執(zhí)行具有可再現(xiàn)性。
資源的共享分為兩種方式:
互斥共享方式:某些資源例如打印機(jī)、磁帶機(jī)等,一次只能給一個(gè)線程使用,當(dāng)一個(gè)線程申請?jiān)撡Y源時(shí),如果該資源有其他線程在使用,則該線程需要等待,直到資源被釋放之后才能申請。
同時(shí)訪問方式:某些資源例如磁盤設(shè)備等,一次可以給多個(gè)線程“同時(shí)”訪問,這種“同時(shí)”是宏觀上的,實(shí)際上還是多個(gè)線程交替訪問。
臨界資源指的是一段時(shí)間內(nèi)只能由一個(gè)線程訪問的資源,而臨界區(qū)指的是每個(gè)線程中訪問臨界資源的那部分代碼。顯然,若能保證每個(gè)線程互斥地進(jìn)入自己的臨界區(qū),便可以實(shí)現(xiàn)每個(gè)線程對臨界資源的互斥訪問。為此,需要在每個(gè)線程進(jìn)入臨界區(qū)前需要對訪問的臨界資源進(jìn)行檢查,如果它是空閑的,則進(jìn)入臨界區(qū);否則等待,直到臨界資源空閑。具體流程如下:
進(jìn)入?yún)^(qū):檢查臨界資源的狀態(tài),如果空閑,則將其狀態(tài)改為被訪問,并進(jìn)入臨界區(qū);如果被訪問,則循環(huán)等待,直到其狀態(tài)變?yōu)榭臻e。
臨界區(qū):訪問臨界資源。
退出區(qū):將臨界資源的狀態(tài)改為空閑,并釋放臨界資源。
Java提供synchronized關(guān)鍵字標(biāo)識方法或代碼塊,被標(biāo)識的方法稱為同步方法,被標(biāo)識的代碼塊稱為同步代碼塊。每個(gè)對象都有一個(gè)監(jiān)視器與之關(guān)聯(lián)。當(dāng)線程通過該對象執(zhí)行同步方法或同步代碼塊時(shí),它首先試圖獲取監(jiān)視器,如果獲取到監(jiān)視器,則鎖定該對象,防止其他線程通過該對象執(zhí)行同步方法或同步代碼塊,執(zhí)行結(jié)束后,解鎖該對象并釋放監(jiān)視器;如果獲取不到監(jiān)視器,表示有其他線程通過該對象執(zhí)行同步方法或同步代碼塊,則會進(jìn)入等待。所以,監(jiān)視器的作用就相當(dāng)于進(jìn)入?yún)^(qū)和退出區(qū)的作用。
例如:定義兩種線程——生產(chǎn)者(Producer)和消費(fèi)者(Consumer),生產(chǎn)者每次會產(chǎn)生一個(gè)數(shù),消費(fèi)者每次會取出一個(gè)數(shù)。Producer和Consumer線程對象通過同一個(gè)Store對象來調(diào)用Store的同步方法。
1 class Store { 2 3 private int value; 4 5 public synchronized int getValue() { 6 System.out.println("-取出" + value); 7 return value; 8 } 9 10 public synchronized void setValue(int value) { 11 this.value = value; 12 System.out.println("放入" + value); 13 } 14 15 } Store 1 @Test 2 void test() { 3 Store store = new Store(); 4 Thread producer = new Producer(store); // 繼承Thread類的線程類對象的初始化 5 Thread consumer = new Thread(new Consumer(store)); // 實(shí)現(xiàn)Runnable接口的線程類對象的初始化 6 producer.start(); 7 consumer.start(); 8 } test部分輸出結(jié)果:
當(dāng)Consumer線程對象調(diào)用getValue()方法時(shí),會獲取監(jiān)視器,鎖定Store對象,直到方法返回后解鎖Store對象,釋放監(jiān)視器;當(dāng)Producer線程對象調(diào)用setValue()方法時(shí)也是如此。所以在創(chuàng)建Producer和Consumer線程對象時(shí)需要傳入同一個(gè)Store對象。如果傳入不同的Store對象,每一個(gè)Store對象都有一個(gè)監(jiān)視器,則起不到鎖定的效果。
根據(jù)輸出結(jié)果可以發(fā)現(xiàn):取出多次數(shù)后才放入一次數(shù),放入多次數(shù)后才取出一次數(shù)。要實(shí)現(xiàn)放入一個(gè)數(shù)后取出一個(gè)數(shù)的效果,則需要添加一個(gè)標(biāo)識量。
改進(jìn):在Store類中添加一個(gè)mutex標(biāo)識量,當(dāng)mutex為true時(shí),表示Store內(nèi)存了一個(gè)數(shù),等待Consumer來取;為false時(shí),表示Store內(nèi)沒有數(shù),等待Producer生產(chǎn)數(shù)。
1 class Store { 2 3 private int value; 4 private boolean mutex; // mutex初始值為false,表示沒有數(shù) 5 6 public synchronized int getValue() { 7 while (! mutex) { // mutex為false時(shí)進(jìn)入等待 8 try { 9 wait(); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 } 14 System.out.println("-取出" + value); 15 mutex = false; // 取出數(shù)后將mutex置為false 16 notify(); 17 return value; 18 } 19 20 public synchronized void setValue(int value) { 21 while (mutex) { // mutex為true時(shí)進(jìn)入等待 22 try { 23 wait(); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 } 28 this.value = value; 29 System.out.println("放入" + value); 30 mutex = true; // 放入數(shù)后將mutex置為true 31 notify(); 32 } 33 34 } Store部分輸出結(jié)果:
轉(zhuǎn)載于:https://www.cnblogs.com/lqkStudy/p/11135153.html
總結(jié)
- 上一篇: 交友小经验
- 下一篇: 有没有朋友可以帮我解释一下贴水是什么意思