国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

整理總結Java多線程程序編寫的要點

2019-11-26 14:38:49
字體:
來源:轉載
供稿:網友

線程狀態圖

201613084914746.jpg (700×480)

線程共包括以下5種狀態。

1. 新建狀態(New)         : 線程對象被創建后,就進入了新建狀態。例如,Thread thread = new Thread()。

2. 就緒狀態(Runnable): 也被稱為“可執行狀態”。線程對象被創建后,其它線程調用了該對象的start()方法,從而來啟動該線程。例如,thread.start()。處于就緒狀態的線程,隨時可能被CPU調度執行。

3. 運行狀態(Running) : 線程獲取CPU權限進行執行。需要注意的是,線程只能從就緒狀態進入到運行狀態。

4. 阻塞狀態(Blocked)  : 阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:

    (01) 等待阻塞 -- 通過調用線程的wait()方法,讓線程等待某工作的完成。

    (02) 同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),它會進入同步阻塞狀態。

    (03) 其他阻塞 -- 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。

5. 死亡狀態(Dead)    : 線程執行完了或者因異常退出了run()方法,該線程結束生命周期。

實現多線程的方式Thread和Runnable
Thread:繼承thread類,實現run方法,在main函數中調用start方法啟動線程

Runnable:接口,實現Runnable接口,作為參數傳遞給Thread的構造函數,在main中調用start方法

例子:

class task implements Runnable {   private int ticket = 10;   @Override  public void run() {    for (int i = 0; i < 20; i++) {      if (this.ticket > 0) {        System.out.println(Thread.currentThread().getName()            + " sold ticket " + this.ticket--);      }    }  } };  public class RunnableTest {  public static void main(String[]args){    task mytask = new task();    Thread t1 = new Thread(mytask);    Thread t2 = new Thread(mytask);    Thread t3 = new Thread(mytask);    t1.start();    t2.start();    t3.start();  } }//ThreadTest.java 源碼class MyThread extends Thread {  private int ticket = 10;   public void run() {    for (int i = 0; i < 20; i++) {      if (this.ticket > 0) {        System.out.println(this.getName() + " 賣票:ticket"            + this.ticket--);      }    }  }} public class ThreadTest {  public static void main(String[] args) {    // 啟動3個線程t1,t2,t3;每個線程各賣10張票!    MyThread t1 = new MyThread();    MyThread t2 = new MyThread();    MyThread t3 = new MyThread();    t1.start();    t2.start();    t3.start();  }};


Thread與Runnable的區別

Thread 是類,而Runnable是接口;Thread本身是實現了Runnable接口的類。我們知道“一個類只能有一個父類,但是卻能實現多個接口”,因此Runnable具有更好的擴展性。此外,Runnable還可以用于“資源的共享”。即,多個線程都是基于某一個Runnable對象建立的,它們會共享Runnable對象上的資源。通常,建議通過“Runnable”實現多線程!

Thread的run與start

start() : 它的作用是啟動一個新線程,新線程會執行相應的run()方法。start()不能被重復調用。start()實際上是通過本地方法start0()啟動線程的。而start0()會新運行一個線程,新線程會調用run()方法。

run()   : run()就和普通的成員方法一樣,可以被重復調用。單獨調用run()的話,會在當前線程中執行run(),而并不會啟動新線程!run()就是直接調用Thread線程的Runnable成員的run()方法,并不會新建一個線程。

// Demo.java 的源碼class MyThread extends Thread{   public MyThread(String name) {    super(name);  }   public void run(){    System.out.println(Thread.currentThread().getName()+" is running");  } };  public class Demo {   public static void main(String[] args) {     Thread mythread=new MyThread("mythread");     System.out.println(Thread.currentThread().getName()+" call mythread.run()");    mythread.run();     System.out.println(Thread.currentThread().getName()+" call mythread.start()");    mythread.start();  } }

輸出:

main call mythread.run()main is runningmain call mythread.start()mythread is running


synchronized
在java中每個對象都有一個同步鎖,當我們調用對象的synchronized方法就獲取了對象鎖,synchronized(obj)就獲取了“obj這個對象”的同步鎖.不同線程對同步鎖的訪問是互斥的.某時間點對象的同步鎖只能被一個線程獲取到.通過同步鎖,我們就能在多線程中,實現對“對象/方法”的互斥訪問.  例如,現在有兩個線程A和線程B,它們都會訪問“對象obj的同步鎖”。假設,在某一時刻,線程A獲取到“obj的同步鎖”并在執行一些操作;而此時,線程B也企圖獲取“obj的同步鎖” ―― 線程B會獲取失敗,它必須等待,直到線程A釋放了“該對象的同步鎖”之后線程B才能獲取到“obj的同步鎖”從而才可以運行。

基本規則

第一條: 當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程對“該對象”的該“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。

第二條: 當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程仍然可以訪問“該對象”的非同步代碼塊。

第三條: 當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程對“該對象”的其他的“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。

synchronized 方法

public synchronized void foo1() {  System.out.println("synchronized methoed");}synchronized代碼塊public void foo2() {      synchronized (this) {    System.out.println("synchronized methoed");    }}

synchronized代碼塊中的this是指當前對象。也可以將this替換成其他對象,例如將this替換成obj,則foo2()在執行synchronized(obj)時就獲取的是obj的同步鎖。

synchronized代碼塊可以更精確的控制沖突限制訪問區域,有時候表現更高效率

實例鎖 和 全局鎖
實例鎖 -- 鎖在某一個實例對象上。如果該類是單例,那么該鎖也具有全局鎖的概念。實例鎖對應的就是synchronized關鍵字。

全局鎖 -- 該鎖針對的是類,無論實例多少個對象,那么線程都共享該鎖。全局鎖對應的就是static synchronized(或者是鎖在該類的class或者classloader對象上)。

pulbic class Something {    public synchronized void isSyncA(){}    public synchronized void isSyncB(){}    public static synchronized void cSyncA(){}    public static synchronized void cSyncB(){}}


(01) x.isSyncA()與x.isSyncB() 不能被同時訪問。因為isSyncA()和isSyncB()都是訪問同一個對象(對象x)的同步鎖!

(02) x.isSyncA()與y.isSyncA()  可以同時被訪問。因為訪問的不是同一個對象的同步鎖,x.isSyncA()訪問的是x的同步鎖,而y.isSyncA()訪問的是y的同步鎖。

(03) x.cSyncA()與y.cSyncB()不能被同時訪問。因為cSyncA()和cSyncB()都是static類型,x.cSyncA()相當于Something.isSyncA(),y.cSyncB()相當于Something.isSyncB(),因此它們共用一個同步鎖,不能被同時反問。

(04) x.isSyncA()與Something.cSyncA() 可以被同時訪問。因為isSyncA()是實例方法,x.isSyncA()使用的是對象x的鎖;而cSyncA()是靜態方法,Something.cSyncA()可以理解對使用的是“類的鎖”。因此,它們是可以被同時訪問的。


線程阻塞與喚醒wait,notify,notifyAll
在Object.java中,定義了wait(), notify()和notifyAll()等接口。wait()的作用是讓當前線程進入等待狀態,同時,wait()也會讓當前線程釋放它所持有的鎖。而notify()和notifyAll()的作用,則是喚醒當前對象上的等待線程;notify()是喚醒單個線程,而notifyAll()是喚醒所有的線程。

Object類中關于等待/喚醒的API詳細信息如下:

notify()       -- 喚醒在此對象監視器上等待的單個線程。

notifyAll()   -- 喚醒在此對象監視器上等待的所有線程。

wait()         -- 讓當前線程處于“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)。

wait(long timeout)      -- 讓當前線程處于“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。

wait(long timeout, int nanos)  -- 讓當前線程處于“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量”,當前線程被喚醒(進入“就緒狀態”)。

// WaitTest.java的源碼class ThreadA extends Thread{   public ThreadA(String name) {    super(name);  }   public void run() {    synchronized (this) {      System.out.println(Thread.currentThread().getName()+" call notify()");      // 喚醒當前的wait線程      notify();    }  }} public class WaitTest {   public static void main(String[] args) {     ThreadA t1 = new ThreadA("t1");     synchronized(t1) {      try {        // 啟動“線程t1”        System.out.println(Thread.currentThread().getName()+" start t1");        t1.start();         // 主線程等待t1通過notify()喚醒。        System.out.println(Thread.currentThread().getName()+" wait()");        t1.wait();         System.out.println(Thread.currentThread().getName()+" continue");      } catch (InterruptedException e) {        e.printStackTrace();      }    }  }}

輸出

main start t1main wait()t1 call notify()main continue

(01) 注意,圖中"主線程" 代表“主線程main”。"線程t1" 代表WaitTest中啟動的“線程t1”。 而“鎖” 代表“t1這個對象的同步鎖”。

(02) “主線程”通過 new ThreadA("t1") 新建“線程t1”。隨后通過synchronized(t1)獲取“t1對象的同步鎖”。然后調用t1.start()啟動“線程t1”。

(03) “主線程”執行t1.wait() 釋放“t1對象的鎖”并且進入“等待(阻塞)狀態”。等待t1對象上的線程通過notify() 或 notifyAll()將其喚醒。

(04) “線程t1”運行之后,通過synchronized(this)獲取“當前對象的鎖”;接著調用notify()喚醒“當前對象上的等待線程”,也就是喚醒“主線程”。

(05) “線程t1”運行完畢之后,釋放“當前對象的鎖”。緊接著,“主線程”獲取“t1對象的鎖”,然后接著運行。

t1.wait()是通過“線程t1”調用的wait()方法,但是調用t1.wait()的地方是在“主線程main”中。而主線程必須是“當前線程”,也就是運行狀態,才可以執行t1.wait()。所以,此時的“當前線程”是“主線程main”!因此,t1.wait()是讓“主線程”等待,而不是“線程t1”!

package thread.Test; public class NotifyAllTest {   private static Object obj = new Object();  public static void main(String[] args) {     ThreadA t1 = new ThreadA("t1");    ThreadA t2 = new ThreadA("t2");    ThreadA t3 = new ThreadA("t3");    t1.start();    t2.start();    t3.start();     try {      System.out.println(Thread.currentThread().getName()+" sleep(3000)");      Thread.sleep(3000);    } catch (InterruptedException e) {      e.printStackTrace();    }     synchronized(obj) {      System.out.println(Thread.currentThread().getName()+" notifyAll()");      obj.notifyAll();//在此喚醒t1.t2.t3    }  }   static class ThreadA extends Thread{     public ThreadA(String name){      super(name);    }     public void run() {      synchronized (obj) {        try {          // 打印輸出結果          System.out.println(Thread.currentThread().getName() + " wait");           //釋放obj對象鎖          obj.wait();           // 打印輸出結果          System.out.println(Thread.currentThread().getName() + " continue");        } catch (InterruptedException e) {          e.printStackTrace();        }      }    }  }}

輸出:

t1 waitmain sleep(3000)t3 waitt2 waitmain notifyAll()t2 continuet3 continuet1 continue


(01) 主線程中新建并且啟動了3個線程"t1", "t2"和"t3"。

(02) 主線程通過sleep(3000)休眠3秒。在主線程休眠3秒的過程中,我們假設"t1", "t2"和"t3"這3個線程都運行了。以"t1"為例,當它運行的時候,它會執行obj.wait()等待其它線程通過notify()或額nofityAll()來喚醒它;相同的道理,"t2"和"t3"也會等待其它線程通過nofity()或nofityAll()來喚醒它們。

(03) 主線程休眠3秒之后,接著運行。執行 obj.notifyAll() 喚醒obj上的等待線程,即喚醒"t1", "t2"和"t3"這3個線程。 緊接著,主線程的synchronized(obj)運行完畢之后,主線程釋放“obj鎖”。這樣,"t1", "t2"和"t3"就可以獲取“obj鎖”而繼續運行了!

notify,notifyall與鎖的關系

Object中的wait(), notify()等函數,和synchronized一樣,會對“對象的同步鎖”進行操作。

wait()會使“當前線程”等待,因為線程進入等待狀態,所以線程應該釋放它鎖持有的“同步鎖”,否則其它線程獲取不到該“同步鎖”而無法運行!

OK,線程調用wait()之后,會釋放它鎖持有的“同步鎖”;而且,根據前面的介紹,我們知道:等待線程可以被notify()或notifyAll()喚醒?,F在,請思考一個問題:notify()是依據什么喚醒等待線程的?或者說,wait()等待線程和notify()之間是通過什么關聯起來的?答案是:依據“對象的同步鎖”。

負責喚醒等待線程的那個線程(我們稱為“喚醒線程”),它只有在獲取“該對象的同步鎖”(這里的同步鎖必須和等待線程的同步鎖是同一個),并且調用notify()或notifyAll()方法之后,才能喚醒等待線程。雖然,等待線程被喚醒;但是,它不能立刻執行,因為喚醒線程還持有“該對象的同步鎖”。必須等到喚醒線程釋放了“對象的同步鎖”之后,等待線程才能獲取到“對象的同步鎖”進而繼續運行。

總之,notify(), wait()依賴于“同步鎖”,而“同步鎖”是對象鎖持有,并且每個對象有且僅有一個!這就是為什么notify(), wait()等函數定義在Object類,而不是Thread類中的原因。


線程讓步yield
線程讓步,使線程從執行狀態變為就緒狀態,從而讓其它具有相同優先級的等待線程獲取執行權;但是,并不能保證在當前線程調用yield()之后,其它具有相同優先級的線程就一定能獲得執行權;也有可能是當前線程又進入到“運行狀態”繼續運行。

yield 與wait

(01) wait()是讓線程由“運行狀態”進入到“等待(阻塞)狀態”,而不yield()是讓線程由“運行狀態”進入到“就緒狀態”。

(02) wait()是會線程釋放它所持有對象的同步鎖,而yield()方法不會釋放鎖。

(03) wait是object的方法,yield是Thread的方法

線程休眠 sleep
sleep() 的作用是讓當前線程休眠,即當前線程會從“運行狀態”進入到“休眠(阻塞)狀態”。sleep()會指定休眠時間,線程休眠的時間會大于/等于該休眠時間;在線程重新被喚醒時,它會由“阻塞狀態”變成“就緒狀態”,從而等待cpu的調度執行。

sleep與wait的區別

wait()的作用是讓當前線程由“運行狀態”進入“等待(阻塞)狀態”的同時,也會釋放同步鎖。而sleep()的作用是也是讓當前線程由“運行狀態”進入到“休眠(阻塞)狀態。(這個其實區別不大)

wait()會釋放對象的同步鎖,而sleep()則不會釋放鎖

 wait是object的方法,sleep是Thread的方法

join
讓主線程等待,子線程運行完畢,主線程才能繼續運行

interrupt
用來終止處于阻塞狀態的線程

@Overridepublic void run() {  try {    while (true) {      // 執行任務...    }  } catch (InterruptedException ie) {     // 由于產生InterruptedException異常,退出while(true)循環,線程終止!  }}

在while(true)中不斷的執行任務,當線程處于阻塞狀態時,調用線程的interrupt()產生InterruptedException中斷。中斷的捕獲在while(true)之外,這樣就退出了while(true)循環

終止處于運行狀態的線程

@Overridepublic void run() {    while (!isInterrupted()) {      // 執行任務...    }}

通用的終止線程的方式

@Overridepublic void run() {  try {    // 1. isInterrupted()保證,只要中斷標記為true就終止線程。    while (!isInterrupted()) {      // 執行任務...    }  } catch (InterruptedException ie) {     // 2. InterruptedException異常保證,當InterruptedException異常產生時,線程被終止。  }}


線程優先級
java 中的線程優先級的范圍是1~10,默認的優先級是5。“高優先級線程”會優先于“低優先級線程”執行。java 中有兩種線程:用戶線程和守護線程。可以通過isDaemon()方法來區別它們:如果返回false,則說明該線程是“用戶線程”;否則就是“守護線程”。用戶線程一般用戶執行用戶級任務,而守護線程也就是“后臺線程”,一般用來執行后臺任務。需要注意的是:Java虛擬機在“用戶線程”都結束后會后退出。

每個線程都有一個優先級?!案邇炏燃壘€程”會優先于“低優先級線程”執行。每個線程都可以被標記為一個守護進程或非守護進程。在一些運行的主線程中創建新的子線程時,子線程的優先級被設置為等于“創建它的主線程的優先級”,當且僅當“創建它的主線程是守護線程”時“子線程才會是守護線程”。

當Java虛擬機啟動時,通常有一個單一的非守護線程(該線程通過是通過main()方法啟動)。JVM會一直運行直到下面的任意一個條件發生,JVM就會終止運行:

(01) 調用了exit()方法,并且exit()有權限被正常執行。

(02) 所有的“非守護線程”都死了(即JVM中僅僅只有“守護線程”)。

守護進程

(01) 主線程main是用戶線程,它創建的子線程t1也是用戶線程。

(02) t2是守護線程。在“主線程main”和“子線程t1”(它們都是用戶線程)執行完畢,只剩t2這個守護線程的時候,JVM自動退出。

生產者消費者問題
(01) 生產者僅僅在倉儲未滿時候生產,倉滿則停止生產。

(02) 消費者僅僅在倉儲有產品時候才能消費,倉空則等待。

(03) 當消費者發現倉儲沒產品可消費時候會通知生產者生產。

(04) 生產者在生產出可消費產品時候,應該通知等待的消費者去消費。

線程之間的通信

方式:共享內存和消息傳遞

共享內存:線程A和線程B共享內存,線程A更新共享變量的值,刷新到主內存中,線程B去主內存中讀取線程A更新后的變量。整個通信過程必須通過主內存。同步是顯式進行的。

201613085415380.gif (570×496)

如果一個變量是volatile類型,則對該變量的讀寫就將具有原子性。如果是多個volatile操作或類似于volatile++這種復合操作,這些操作整體上不具有原子性。

volatile變量自身具有下列特性:

[可見性]:對一個volatile變量的讀,總是能看到(任意線程)對這個volatile變量最后的寫入。

[原子性]:對任意單個volatile變量的讀/寫具有原子性,但類似于volatile++這種復合操作不具有原子性。

volatile寫:當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存。

volatile讀:當讀一個volatile變量時,JMM會把該線程對應的本地內存置為無效。線程接下來將從主內存中讀取共享變量。

消息傳遞:消息的發送在消息的接受之前,同步隱式進行。

ThreadLocal

ThreadLocal 不是用來解決共享對象的多線程訪問問題的,一般情況下,通過ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的.ThreadLocal使得各線程能夠保持各自獨立的一個對象,并不是通過ThreadLocal.set()來實現的,而是通過每個線程中的new 對象 的操作來創建的對象,每個線程創建一個,不是什么對象的拷貝或副本。通過ThreadLocal.set()將這個新創建的對象的引用保存到各線程的自己的一個map中,每個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從自己的map中取出放進去的對象,因此取出來的是各自自己線程中的對象,ThreadLocal實例是作為map的key來使用的。 如果ThreadLocal.set()進去的東西本來就是多個線程共享的同一個對象,那么多個線程的ThreadLocal.get()取得的還是這個共享對象本身,還是有并發訪問問題。

ThreadLocal的一個實現

import java.util.Collections; import java.util.HashMap; import java.util.Map;  /** * 使用了ThreadLocal的類 * * @author leizhimin 2010-1-5 10:35:27 */ public class MyThreadLocal {      //定義了一個ThreadLocal變量,用來保存int或Integer數據     private com.lavasoft.test2.ThreadLocal<Integer> tl = new com.lavasoft.test2.ThreadLocal<Integer>() {         @Override         protected Integer initialValue() {             return 0;         }     };      public Integer getNextNum() {         //將tl的值獲取后加1,并更新設置t1的值         tl.set(tl.get() + 1);         return tl.get();     } }  class ThreadLocal<T> {     private Map<Thread, T> map = Collections.synchronizedMap(new HashMap<Thread, T>());      public ThreadLocal() {     }      protected T initialValue() {         return null;     }      public T get() {         Thread t = Thread.currentThread();         T obj = map.get(t);         if (obj == null && !map.containsKey(t)) {             obj = initialValue();             map.put(t, obj);         }         return obj;     }      public void set(T value) {         map.put(Thread.currentThread(), value);     }      public void remove() {         map.remove(Thread.currentThread());     } }

事實上ThreadLocal是這樣做的:

  public T get() {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null) {      ThreadLocalMap.Entry e = map.getEntry(this);      if (e != null)        return (T)e.value;    }    return setInitialValue();  }

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 佛学| 六盘水市| 年辖:市辖区| 楚雄市| 邯郸县| 原平市| 堆龙德庆县| 谢通门县| 辉南县| 荣成市| 湛江市| 仁寿县| 黑水县| 胶州市| 湟源县| 五河县| 榆中县| 新邵县| 手游| 武安市| 清苑县| 莆田市| 青冈县| 株洲市| 沁水县| 镇赉县| 日土县| 高雄市| 临泽县| 敦煌市| 孝昌县| 青田县| 山阳县| 郎溪县| 平罗县| 瓦房店市| 阳泉市| 梅河口市| 洪江市| 湘潭市| 武强县|