日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

5.Java SE 多线程

發布時間:2023/12/8 java 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 5.Java SE 多线程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Java SE 多線程

  • 創建多線程的四種方式
    • 繼承 Thread 類
    • 實現 Runnable 接口
    • 實現 Callable 接口 (JDK5.0 新增)
    • 線程池
  • 線程的 常用方法 和 生命周期
  • 鎖(synchronized、Lock:JDK5.0)
    • synchronized
    • Lock 接口:JDK5.0新增
  • 死鎖
    • 死鎖產生的原因
    • 死鎖產生的四個必要條件?
    • 如何防止死鎖?
  • 線程的通信
  • 線程通信應用
  • 相關面試題
    • sleep() 和 wait()的異同?
    • synchronized 與 Lock的異同?

創建多線程的四種方式

繼承 Thread 類

步驟:
① 用一個類繼承 Thread 類,重寫 run() 方法;run 方法中就是此線程需要執行的操作。
② 在需要用到的地方 new 一個 繼承了 Thread 類的子類的對象,調用 start() 方法啟動線程。start() 會調用 run() 方法中的代碼。

// ① public class Test extends Thread{@Overridepublic void run(){……} } // ② class Main{public static void main(String[] args){Test test = new Test();test.start();//只能調用start 方法 ,不能調用run,直接調用run()方法是和主線程一起共用一個線程,并沒有開啟一個線程。} }

實現 Runnable 接口

  • 相比 Thread 的優勢:
    ① 因為是接口,所以彌補了單繼承的局限性。
    ② 節省資源,因為 Thread 每開啟一個線程就需要new Test() ,也就是資源類。Runnable 可以只用new 一個 資源類。

  • 步驟:
    ① 用一個類實現 Runnable 接口,重寫 run 方法。
    ② 在需要的地方創建上述類的子類,把創建的類放入 new Thread(子類)中,調用 start 方法。

  • public class Test implements Runnable {@Overridepublic void run(){……} } class Main {public static void main(String[] args){Test test = new Test();new Thread(test).start();} }

    實現 Callable 接口 (JDK5.0 新增)

  • 誕生原因(優勢):Callable 接口的方式比 Rannable 接口的方式更強大:因為 call() 可以有返回值、可以拋出異常,Callable 還支持泛型。
  • 步驟:和 Runnable 差不多,只是 void -> Object(可變)、run -> call 、有 return Object、需要將資源類 放入 FutrueTask() 中,再放入 Thread 中。
  • public class Test implements Callable{@Overridepublic Object call() thorws Exception {……return Object;} } class Main{public static void main(String[] args){Test test= new Test();FutureTask futureTask = new FutureTask(test);new Thread(futureTask).start();try{Object obj = futureTast.get();//獲取返回值的方式}catch(ExecutionException e ){e.printStackTrace();}

    線程池

  • 優勢:
    ① 提高了響應速度(減少了創建新線程的時間)。
    ② 降低資源消耗 (重復利用線程池中線程,不需要每次都創建)
    ③ 便于線程管理,里面設有各種參數,如最大線程數、核心池大小、線程沒有任務時會保存多久終止等。暫時了解到這里,有興趣的看一下源碼就明白了。
  • class Test implements Runnable {// 也可以用 Callable@Overridepublic void run(){……} } class Main {public static void main(String[] args){ExecutorService servie = Executors.newFixedThreadPool(10);//參數表示 線程的最大數量 ThreadPoolExecutor service1 = (ThreadPoolExecutor) servie;service.execute(new Test());// 適用于 Runnableservice.submit(Callable callable);// 適用于 Callableservice.shutdown();// 關閉連接池} }

    線程的 常用方法 和 生命周期

  • 常用方法:
  • 測試Thread中的常用方法: 1. start():啟動當前線程;調用當前線程的run() 2. run(): 通常需要重寫Thread類中的此方法,將創建的線程要執行的操作聲明在此方法中 3. currentThread():靜態方法,返回執行當前代碼的線程 4. getName():獲取當前線程的名字 5. setName():設置當前線程的名字 6. yield():釋放當前cpu的執行權,執行機會讓給相同或者更高優先級的線程。 7. join():在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,直到線程b完全執行完以后,線程a才結束阻塞狀態。 8. stop():已過時。當執行此方法時,強制結束當前線程。 9. sleep(long millitime):讓當前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態,不釋放鎖。 10. isAlive():判斷當前線程是否存活線程的優先級: 1. MAX_PRIORITY:10 MIN _PRIORITY:1 NORM_PRIORITY:5 -->默認優先級 2.如何獲取和設置當前線程的優先級:getPriority():獲取線程的優先級setPriority(int p):設置線程的優先級說明:高優先級的線程要搶占低優先級線程cpu的執行權。但是只是從概率上講,高優先級的線程高概率的情況下被執行。并不意味著只有當高優先級的線程執行完以后,低優先級的線程才執行。
  • 生命周期
  • ① 就緒狀態(Runnable):該狀態的線程位于可運行的線程池中,等待獲取 CPU 的使用權。
    ② 運行狀態(Running):就緒狀態獲取了 CPU ,執行代碼。
    ③ 阻塞狀態(Blocked):因為某種原因放棄 CPU 的使用權,暫時停止運行。阻塞狀態分三種:
    (一)等待阻塞(wait):運行的線程執行 wait 方法,JVM 會把該線程放入等待池中。(wait 會釋放持有的鎖)。
    (二)同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用了,則JVM 會把該線程放入鎖池中。
    (三)其他阻塞:運行的線程執行 sleep、join 方法或者發出了 I/O 請求時,JVM 會把該線程設置為阻塞狀態。當 sleep 狀態超時、join 等待線程終止或者超時、或者 I/O 處理完畢時,線程重新轉入就緒狀態。(注意:sleep 是不會釋放持有鎖

    鎖(synchronized、Lock:JDK5.0)

    synchronized

  • 介紹:synchronized 是 Java 的關鍵字,用來給對象方法代碼塊加鎖;當他鎖定一個方法或者一個代碼塊時,同一時刻最多只能有一個線程執行這段代碼。它是解決線程安全問題的方式之一。
  • 看個例子:說買票過程中,出現了重票、錯票,也就是線程安全問題,那怎么解決呢?
    ① 分析原因:兩個線程操作了同一張票,導致了重票。
    ② 解決方法:也就是說,每賣一張票,只能有一個線程去操作,其他的線程必須等待。代碼如下:
  • class Window implements Runnable{private int ticket = 100;//賣一百張票;不要想把這里改為 static 會有用,兩個線程同時操作一張票(此時沒有加 synchronized)是指,線程一 還沒有執行到 ticket--,線程二已經到了輸出語句。@Overridepublic void run(){while(true){synchronized(this){//此時的 this 是唯一的 Window 對象(加鎖);也可以用其他對象if(ticket > 0){System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);ticket--;}else{break;}}}} } public class Main{public static void main(String[] args){Window w = new Window();Thread t1 = new Thread(w);Thread t2 = new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t1.start();t2.start();}

    ③ 上述代碼的 synchronized 是放在代碼塊的,此時稱為 同步代碼塊,我們也可以把它放在方法上,run 去調用這個方法就行了,此時稱為 同步方法多用這個) 其實差不多,看下代碼:

    class Window implements Runnable{ //局部 run()public void run(){while(true){show();}} //同步方法private synchronized void show(){//此時鎖的對象是調用 show() 的 對象,也就是 Windowif(ticket > 0)……} }

    Lock 接口:JDK5.0新增

  • 介紹:Lock 鎖比 synchronized 要靈活一些;前者需要手動啟動 (lock()),同步結束也需要手動釋放鎖(unlock());后者在執行完相應同步代碼后,自動釋放同步監視器(鎖)。因此 Lock 多用在同步代碼塊,此時已經進入了方法體,分配了相應資源。
  • 步驟:① 先 new 一個實現了 Lock 接口的類的 對象,也就是 ReentrantLock;② 就是上鎖,位置和 synchronized 差不多;只需要最后得 釋放鎖,因此一般用 lock 都在 try- finally 里面。
  • class Window implements Runnable{private int ticket = 100;private ReentrantLock lock = new ReentrantLock();@Overridepublic void run(){while(true){try{lock.lock();if(ticket > 0){System.out.println(Thread.currentThread().getName() + ":售票,票號為:" + ticket);ticket--;}else{break;}}finally{lock.unlock();}}} public class Main{public static void main(String[] args){Window w = new Window();Thread t1 = new Thread(w);Thread t2 = new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t1.start();t2.start();}}

    死鎖

  • 介紹:是指多個線程在運行過程中,因爭奪資源而造成的一種僵局,當處于這種僵持狀態時,若無外力作用,他們都無法再向前推進。最簡單的就是:不同的線程占用對方需要的同步資源不放棄,都在等著對方放棄自己需要的同步資源,就形成了死鎖;而且不會有異常,不會提示,只是所有線程都處于阻塞狀態,無法繼續。通俗來講就是,你綁了他老婆,他綁了你老婆,但是你和他都在等待對方放了自己的老婆,僵持。
  • 先看個例子:
  • public class DeadLock{public static void main(String[] args){StringBuffer s1 = new StringBuffer();// 拿來當鎖用StringBuffer s2 = new StringBuffer();// 拿來當鎖用new Thread(){@Overridepublic void run(){synchronized(s1){s1.append("a");s2.append("1");synchronized(s2){// 執行到這里時,s2 已經被 下面一個線程 占用s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();new Thread(){@Overridepublic void run(){synchronized(s2){s1.append("c");s2.append("3");synchronized(s1){// 這里又被上面 線程 占用,僵持了s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}.start();}}

    死鎖產生的原因

    前置知識:系統中資源可以分為兩類:可剝奪資源 和 不可剝奪資源
    ① 可剝奪資源:是指在某線程獲得這類資源后,該資源還可以被其他線程或者系統剝奪,CPU 和 主存均屬于可剝奪性資源。
    ② 不可剝奪資源:當系統把這類資源分配給某進程后,再不能強行收回,只能在進程用完后釋放;如打印機。
    產生死鎖的原因如下:

  • 競爭資源:
    ① 競爭不可剝奪資源;如系統中只有一臺打印機R1和一臺磁帶機R2,可供進程P1和P2共享。假定P1已占用了打印機R1,P2已占用了磁帶機R2,若P2繼續要求打印機R1,P2將阻塞;P1若又要求磁帶機R2,P1也將阻塞。于是,在P1和P2之間就形成了僵局,兩個進程都在等待對方釋放自己所需要的資源,但是它們又都因不能繼續獲得自己所需要的資源而不能繼續推進,從而也不能釋放自己所占有的資源,以致進入死鎖狀態。
  • ② 競爭臨時資源:通常消息通信順序進行不當,則會產生死鎖。
    2. 進程推進順序非法:
    若P1保持了資源R1,P2保持了資源R2,系統處于不安全狀態,因為這兩個進程再向前推進,便可能發生死鎖。例如,當P1運行到P1:Request(R2)時,將因R2已被P2占用而阻塞;當P2運行到P2:Request(R1)時,也將因R1已被P1占用而阻塞,于是發生進程死鎖。

    死鎖產生的四個必要條件?

  • 互斥條件:線程要求對所分配的資源進行排它性控制,即在一段時間內某一資源僅為一個線程鎖占用。
  • 請求并保持條件:當線程因請求資源而阻塞,對已獲得的資源保持不放。
  • 不剝奪條件:進程已獲得的資源在未使用完之前,不能剝奪,只能在使用完時自己釋放。
  • 環路等待條件:在發生死鎖時,必然存在一個進程—資源的環形鏈。環路上的每個進程都在等待下一個進程占有的資源。
  • 如何防止死鎖?

    ① 破壞四個必要條件;② 預防死鎖(有相關算法,這里暫不討論)

  • 破壞互斥條件:如果允許系統資源都能共享使用,則系統不會進入死鎖狀態。
  • 破壞請求條件并保持條件:采用預先靜態預分配,即進程運行前一次性申請完它所需要的物資,在物資為滿足前,不能投入運行;一旦運行后,這些物資就一直歸他所有,也不再發出其他物資請求。
  • 破壞不可剝奪條件:當一個保持了某些不可剝奪資源的進程,請求新的資源沒有辦法滿足時,它必須釋放已經保持的所有資源,待以后需要時在重新申請。
  • 破壞環路等待條件:采用順序資源分配法。首先給系統中的資源編號,規定每個進程必須按編號遞增的順序請求資源,同類資源一次申請完。
  • 線程的通信

    線程的通信涉及到三個方法

    wait():一旦執行此方法,當前線程就進入阻塞狀態,并釋放同步監視器(鎖)。 notify():一旦執行此方法,就會喚醒被 wait 的線程。如果有多個線程被 wait ,就換醒優先級最高的那個。 notifyAll():一旦執行此方法,就會喚醒所有被 wait 的線程。 說明: 這三個方法是定義在 java.lang.Object 類中; 必須使用在同步代碼塊或同步方法中; 它們的調用者必須是同步代碼塊或同步方法中的同步監視器。

    看個線程通信的例子:使用兩個線程打印 1-100。線程一,線程二 交替打印。

    public class Main {public static void main(String[] args) {Number number = new Number();Thread t1 = new Thread(number);Thread t2 = new Thread(number);t1.setName("線程一");t2.setName("線程二");t1.start();t2.start();}static class Number implements Runnable {private int num = 1;private Object obj = new Object();// 拿來當鎖@Overridepublic void run() {while (true) {synchronized (obj) {obj.notify();if (num <= 100) {System.out.println(Thread.currentThread().getName() + ":" + num++);try {obj.wait();//線程一 num++ 后 必須剎一腳; 上面線程二 進來又喚醒 線程一。} catch (InterruptedException e) {e.printStackTrace();}} else {break;}}}}} }

    線程通信應用

    必須掌握的經典例子:生產者 / 消費者 問題。
    問題:生產者(Productor)將產品交給店員(clerk),而消費者(Customer)從店員哪里取走產品,店員一次只能持有固定數量的產品(如:20),如果生產者試圖生產更多的產品,店員會叫生產者停一下,如果店中有空位放產品了再通知生產者生產;如果店中沒有產品了,店員會告訴消費者等一下,如果店中有產品了在通知消費者來取走產品。

    class Clerk{// 共享數據(店員/產品)private int productCount = 0;//生產產品public synchronized void produceProduct() {if(productCount < 20){productCount++;System.out.println(Thread.currentThread().getName() + ":開始生產第" + productCount + "個產品");notify();}else{//等待try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}//消費產品public synchronized void consumeProduct() {if(productCount > 0){System.out.println(Thread.currentThread().getName() + ":開始消費第" + productCount + "個產品");productCount--;notify();}else{//等待try {wait();} catch (InterruptedException e) {e.printStackTrace();}}} }class Producer extends Thread{//生產者private Clerk clerk;public Producer(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {System.out.println(getName() + ":開始生產產品.....");while(true){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}clerk.produceProduct();}} }class Consumer extends Thread{//消費者private Clerk clerk;public Consumer(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {System.out.println(getName() + ":開始消費產品.....");while(true){try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}clerk.consumeProduct();}} }public class ProductTest {public static void main(String[] args) {Clerk clerk = new Clerk();Producer p1 = new Producer(clerk);p1.setName("生產者1");Consumer c1 = new Consumer(clerk);c1.setName("消費者1");Consumer c2 = new Consumer(clerk);c2.setName("消費者2");p1.start();c1.start();c2.start();} }

    相關面試題

    sleep() 和 wait()的異同?

  • 相同點:都可以使當前的進程進入阻塞狀態。
  • 不同點:
    ① 聲明位置不同:Thread 類中聲明 sleep() ,Object 類中聲明 wait()。
    ② 調用的要求不同:sleep() 可以在任何需要的場景調用。wait() 必須使用在同步代碼塊或同步方法中。
    ③ 關于是否釋放同步監視器(鎖):如果兩個方法都使用在同步代碼塊或同步方法中,sleep() 不會釋放鎖,wait() 會釋放鎖。
  • synchronized 與 Lock的異同?

  • 相同:二者都可以解決線程安全問題
  • 不同:synchronized機制在執行完相應的同步代碼以后,自動的釋放同步監視器,Lock需要手動的啟動同步(lock()),同時結束同步也需要手動的實現(unlock())
  • 參考:https://www.bilibili.com/video/BV1Kb411W75N,https://blog.csdn.net/Amosstan/article/details/120161969,https://blog.csdn.net/hd12370/article/details/82814348

    總結

    以上是生活随笔為你收集整理的5.Java SE 多线程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。