詳解Java多線程與並發
一、進程與線程
進程:是代碼在數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位。
線程:是進程的一個執行路徑,一個進程中至少有一個線程,進程中的多個線程共享進程的 資源。
雖然系統是把資源分給進程,但是CPU很特殊,是被分配到線程的,所以線程是CPU分配的基本單位。
二者關系:
一個進程中有多個線程,多個線程共享進程的堆和方法區資源,但是每個線程有自己的程序計數器和棧區域。
- 程序計數器:是一塊內存區域,用來記錄線程當前要執行的指令地址 。
- 棧:用於存儲該線程的局部變量,這些局部變量是該線程私有的,除此之外還用來存放線程的調用棧禎。
- 堆:是一個進程中最大的一塊內存,堆是被進程中的所有線程共享的。
- 方法區:則用來存放 NM 加載的類、常量及靜態變量等信息,也是線程共享的 。
二者區別:
- 進程:有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響。
- 線程:是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉。
1)簡而言之,一個程序至少有一個進程,一個進程至少有一個線程.
2)線程的劃分尺度小於進程,使得多線程程序的並發性高。
3)另外,進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高瞭程序的運行效率。
4)每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
5)從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分可以同時執行。但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別
二、並發與並行
並發:是指同一個時間段內多個任務同時都在執行,並且都沒有執行結束。並發任務強調在一個時間段內同時執行,而一個時間段由多個單位時間累積而成,所以說並發的多個任務在單位時間內不一定同時在執行 。
並行:是說在單位時間內多個任務同時在執行 。
在多線程編程實踐中,線程的個數往往多於CPU的個數,所以一般都稱多線程並發編程而不是多線程並行編程。
並發過程中常見的問題:
1、線程安全問題
多個線程同時操作共享變量1時,會出現線程1更新共享變量1的值,但是其他線程獲取到的是共享變量沒有被更新之前的值。就會導致數據不準確問題。
2、共享內存不可見性問題
Java內存模型(處理共享變量)
Java 內存模型規定,將所有的變量都存放在主內存中,當線程使用變量時,會把主內存裡面的變量復制到自己的工作空間或者叫作工作內存,線程讀寫變量時操作的是自己工作內存中的變量 。(如上圖所示)
(實際工作的java內存模型)
上圖中所示是一個雙核 CPU 系統架構,每個核有自己的控制器和運算器,其中控制器包含一組寄存器和操作控制器,運算器執行算術邏輔運算。CPU的每個核都有自己的一級緩存,在有些架構裡面還有一個所有CPU都共享的二級緩存。 那麼Java內存模型裡面的工作內存,就對應這裡的 Ll或者 L2 緩存或者 CPU 的寄存器
1、線程A首先獲取共享變量X的值,由於兩級Cache都沒有命中,所以加載主內存中X的值,假如為0。然後把X=0的值緩存到兩級緩存,線程A修改X的值為1,然後將其寫入兩級Cache,並且刷新到主內存。線程A操作完畢後,線程A所在的CPU的兩級Cache內和主內存裡面的X的值都是l。
2、線程B獲取X的值,首先一級緩存沒有命中,然後看二級緩存,二級緩存命中瞭,所以返回X=1;到這裡一切都是正常的,因為這時候主內存中也是X=l。然後線程B修改X的值為2,並將其存放到線程2所在的一級Cache和共享二級Cache中,最後更新主內存中X的值為2,到這裡一切都是好的。
3、線程A這次又需要修改X的值,獲取時一級緩存命中,並且X=l這裡問題就出現瞭,明明線程B已經把X的值修改為2,為何線程A獲取的還是l呢?這就是共享變量的內存不可見問題,也就是線程B寫入的值對線程A不可見。
synchronized 的內存語義:
這個內存語義就可以解決共享變量內存可見性問題。進入synchronized塊的內存語義是把在synchronized塊內使用到的變量從線程的工作內存中清除,這樣在synchronized塊內使用到該變量時就不會從線程的工作內存中獲取,而是直接從主內存中獲取。退出synchronized塊的內存語義是把在synchronized塊內對共享變量的修改刷新到主內存。會造成上下文切換的開銷,獨占鎖,降低並發性
Volatile的理解:
該關鍵字可以確保對一個變量的更新對其他線程馬上可見。當一個變量被聲明為volatile時,線程在寫入變量時不會把值緩存在寄存器或者其他地方,而是會把值刷新回主內存。當其他線程讀取該共享變量時-,會從主內存重新獲取最新值,而不是使用當前線程的工作內存中的值。volatile的內存語義和synchronized有相似之處,具體來說就是,當線程寫入瞭volatile變量值時就等價於線程退出synchronized同步塊(把寫入工作內存的變量值同步到主內存),讀取volatile變量值時就相當於進入同步塊(先清空本地內存變量值,再從主內存獲取最新值)。不能保證原子性
三、創建線程
1、繼承Thread類
重寫run方法:使用繼承方式的好處是,在run()方法內獲取當前線程直接使用this就可以瞭,無須使用Thread.currentThread()方法;不好的地方是Java不支持多繼承,如果繼承瞭Thread類,那麼就不能再繼承其他類。另外任務與代碼沒有分離,當多個線程執行一樣的任務時需要多份任務代碼。
public class ThreadRuning extends Thread{ public ThreadRuning(String name){ //重寫構造,可以對線程添加名字 super(name); } @Override public void run() { while(true){ System.out.println("good time"); //在run方法裡,this代表當前線程 System.out.println(this); } } public static void main(String[] args){ ThreadRuning threadRuning = new ThreadRuning("1111"); threadRuning.start(); } }
2、實現Runable接口
實現run方法:解決繼承Thread的缺點,沒有返回值
public class RunableTest implements Runnable { @Override public void run() { while (true) { System.out.println("good time"); } } public static void main(String[] args) { RunableTest runableTest1 = new RunableTest(); RunableTest runableTest2 = new RunableTest(); new Thread(runableTest1).start(); new Thread(runableTest1).start(); new Thread(runableTest2).start(); } }
3、實現Callable接口
實現call方法:
public class CallTest implements Callable { @Override public Object call() throws Exception { return "hello world"; } public static void main(String[] args){ FutureTask<String> futureTask = new FutureTask<String>(new CallTest()); new Thread(futureTask).start(); try { String result = futureTask.get(); System.out.println(result); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
使用繼承方式的好處是方便傳參,你可以在子類裡面添加成員變量,通過set方法設置參數或者通過構造函數進行傳遞,而如果使用Runnable方式,則隻能使用主線程裡面被聲明為final的變量。不好的地方是Java不支持多繼承,如果繼承瞭Thread類,那麼子類不能再繼承其他類,而Runable則沒有這個限制。前兩種方式都沒辦法拿到任務的返回結果,但是Callable方式可以
四、Thread類詳解
1、線程特性
1、線程能被標記為守護線程,也可以是用戶線程
2、每個線程均分配一個name,默認為(Thread-自增數字)的組合
3、每個線程都有優先級.高優先級線程優先於低優先級線程執行. 1-10,默認為5
4、main所在的線程組為main,構造線程的時候沒有現實的指定線程組,線程組默認和父線程一樣
5、當線程中的run()方法代碼裡面又創建瞭一個新的線程對象時,新創建的線程優先級和父線程優先級一樣.
6、當且僅當父線程為守護線程時,新創建的線程才會是守護線程.
7、當JVM啟動時,通常會有唯一的一個非守護線程(這一線程用於調用指定類的main()方法)
JVM會持續執行線程直到下面情況某一個發生為止:
1)類運行時exit()方法被調用 且 安全機制允許此exit()方法的調用.
2)所有非守護類型的線程均已經終止,or run()方法調用返回or在run()方法外部拋出瞭一些可傳播性的異常.
2、Init方法
/** * Initializes a Thread. * @param g 線程組 * @param target 執行對象 * @param name 線程名 * @param stackSize 新線程棧大小,為0表示忽略 * @param acc用於繼承的訪問控制上下文 * @param inheritThreadLocals如果值為true,從構造線程繼承可繼承線程局部變量的初始值 */ private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); //如果所屬線程組為null if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager //如果有安全管理,查詢安全管理需要做的工作 what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ //如果安全管理在線程所屬父線程組的問題上沒有什麼強制的要求 if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ //無論所屬線程組是否顯示傳入,都要進行檢查訪問. g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon();//如果父線程為守護線程,則此線程也被 設置為守護線程. this.priority = parent.getPriority();//獲取父進程的優先級 if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID 設置線程id*/ tid = nextThreadID(); }
3、構造方法
所有的構造方法都是調用init()方法
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } public Thread(Runnable target, AccessControlContext acc) { init(null, target, "Thread-" + nextThreadNum(), 0, acc, false); } public Thread(ThreadGroup group, Runnable target) { init(group, target, "Thread-" + nextThreadNum(), 0); } public Thread(String name) { init(null, null, name, 0); } public Thread(ThreadGroup group, String name) { init(group, null, name, 0); } public Thread(Runnable target, String name) { init(null, target, name, 0); } public Thread(ThreadGroup group, Runnable target, String name, long stackSize) { init(group, target, name, stackSize); }
4、線程狀態
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
NEW:狀態是指線程剛創建, 尚未啟動
RUNNABLE:狀態是線程正在正常運行中, 當然可能會有某種耗時計算/IO等待的操作/CPU時間片切換等, 這個狀態下發生的等待一般是其他系統資源, 而不是鎖, Sleep等
BLOCKED:這個狀態下, 是在多個線程有同步操作的場景, 比如正在等待另一個線程的synchronized 塊的執行釋放, 或者可重入的 synchronized塊裡別人調用wait() 方法, 也就是這裡是線程在等待進入臨界區
WAITING:這個狀態下是指線程擁有瞭某個鎖之後, 調用瞭他的wait方法, 等待其他線程/鎖擁有者調用 notify / notifyAll 一遍該線程可以繼續下一步操作, 這裡要區分 BLOCKED 和 WATING 的區別, 一個是在臨界點外面等待進入, 一個是在理解點裡面wait等待別人notify, 線程調用瞭join方法 join瞭另外的線程的時候, 也會進入WAITING狀態, 等待被他join的線程執行結束
TIMED_WAITING:這個狀態就是有限的(時間限制)的WAITING, 一般出現在調用wait(long), join(long)等情況下, 另外一個線程sleep後, 也會進入TIMED_WAITING狀態
TERMINATED:這個狀態下表示 該線程的run方法已經執行完畢瞭, 基本上就等於死亡瞭(當時如果線程被持久持有, 可能不會被回收)
(在很多文章中都寫瞭running狀態,其實源碼裡面隻有六種的,當自己寫一個線程通過while一直保持執行狀態,然後使用jconsole工具去查看線程的狀態,確實是Runable狀態)
Api文檔是這麼說的:
其實我們可以理解為兩種狀態,一個是running,表示正在執行,一個是runable,表示準備就緒瞭,隻是在等待其他的系統資源。然後我們就可以理解如下圖
5、Start方法
public synchronized void start() { /** * 此方法並不會被主要方法線程or由虛擬機創建的系統組線程所調用. * 任何向此方法添加的新功能方法在未來都會被添加到虛擬機中. * 0狀態值代表瞭NEW的狀態. */ if (threadStatus != 0) // 線程不能重復start throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); //本地方法 started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } private native void start0();
6、yield方法
public static native void yield();
是一個本地方法,提示線程調度器當前線程願意放棄當前CPU的使用。如果當前資源不緊張,調度器可以忽略這個提示。本質上線程狀態一直是RUNNABLE,但是我可以理解為RUNNABLE到RUNNING的轉換
7、sleep方法
/** * 此方法會引起當前執行線程sleep(臨時停止執行)指定毫秒數. * 此方法的調用不會引起當前線程放棄任何監聽器(monitor)的所有權(ownership). */ public static native void sleep(long millis) throws InterruptedException; public static void sleep(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } sleep(millis); }
sleep方法,有一個重載方法,sleep方法會釋放cpu的時間片,但是不會釋放鎖,調用sleep()之後從RUNNABLE狀態轉為TIMED_WAITING狀態
8、join方法
/** * 最多等待參數millis(ms)時長當前線程就會死亡.參數為0時則要持續等待. * 此方法在實現上:循環調用以this.isAlive()方法為條件的wait()方法. * 當線程終止時notifyAll()方法會被調用. * 建議應用程序不要在線程實例上使用wait,notify,notifyAll方法. */ public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; //如果等待時間<0,則拋出異常 if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } //如果等待時間為0 if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } } //等待時間單位為納秒,其它解釋都和上面方法一樣 public final synchronized void join(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } join(millis); } //方法功能:等待一直到線程死亡. public final void join() throws InterruptedException { join(0); }
join某個線程A,會使得線程B進入等待,知道線程A結束,或者到達給定的時間,那麼期間線程B處於BLOCKED的狀態,而不是線程A
五、其他方法
接下來聊一下Object類的wait,notify和notifyAll方法
1、wait方法
public final native void wait(long timeout) throws InterruptedException; //本地方法 參數為毫秒 public final void wait(long timeout, int nanos) throws InterruptedException {//參數為毫秒和納秒 if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); } public final void wait() throws InterruptedException { wait(0); }
可見wait()和wait(long timeout, int nanos)都在在內部調用瞭wait(long timeout)方法。
下面主要是說說wait(long timeout)方法
wait方法會引起當前線程阻塞,直到另外一個線程在對應的對象上調用notify或者notifyAll()方法,或者達到瞭方法參數中指定的時間。
調用wait方法的當前線程一定要擁有對象的監視器鎖。
wait方法會把當前線程T放置在對應的object上的等待隊列中,在這個對象上的所有同步請求都不會得到響應。線程調度將不會調用線程T,在以下四件事發生之前,線程T會被喚醒(線程T是在其代碼中調用wait方法的那個線程)
1、當其他的線程在對應的對象上調用notify方法,而在此對象的對應的等待隊列中將會任意選擇一個線程進行喚醒。
2、其他的線程在此對象上調用瞭notifyAll方法
3、其他的線程調用瞭interrupt方法來中斷線程T
4、等待的時間已經超過瞭wait中指定的時間。如果參數timeout的值為0,不是指真實的等待時間是0,而是線程等待直到被另外一個線程喚醒為止。
被喚醒的線程T會被從對象的等待隊列中移除並且重新能夠被線程調度器調度。之後,線程T會像平常一樣跟其他的線程競爭獲取對象上的鎖;一旦線程T獲得瞭此對象上的鎖,那麼在此對象上的所有同步請求都會恢復到之前的狀態,也就是恢復到wait被調用的情況下。然後線程T從wait方法的調用中返回。因此,當從wait方法返回時,對象的狀態以及線程T的狀態跟wait方法被調用的時候一樣。
線程在沒有被喚醒,中斷或者時間耗盡的情況下仍然能夠被喚醒,這叫做偽喚醒。雖然在實際中,這種情況很少發生,但是程序一定要測試這個能夠喚醒線程的條件,並且在條件不滿足時,線程繼續等待。換言之,wait操作總是出現在循環中,就像下面這樣:
synchronized(對象){ while(條件不滿足){ 對象.wait(); } 對應的邏輯處理 }
如果當前的線程被其他的線程在當前線程等待之前或者正在等待時調用瞭interrupt()中斷瞭,那麼會拋出InterruptedExcaption異常。直到這個對象上面的鎖狀態恢復到上面描述的狀態以前,這個異常是不會拋出的。
要註意的是,wait方法把當前線程放置到這個對象的等待隊列中,解鎖也僅僅是在這個對象上;當前線程在其他對象上面上的鎖在當前線程等待的過程中仍然持有其他對象的鎖。
這個方法應該僅僅被持有對象監視器的線程調用。 wait(long timeout, int nanos)方法的實現中隻要nanos大於0,那麼timeout時間就加上一毫秒,主要是更精確的控制時間,其他的跟wait(long timeout)一樣
2、notify方法
public final native void notify(); //本地方法
通知可能等待該對象的對象鎖的其他線程。由JVM(與優先級無關)隨機挑選一個處於wait狀態的線程。
在調用notify()之前,線程必須獲得該對象的對象級別鎖
執行完notify()方法後,不會馬上釋放鎖,要直到退出synchronized代碼塊,當前線程才會釋放鎖
notify()一次隻隨機通知一個線程進行喚醒
3、notifyAll()方法
public final native void notifyAll();//本地方法
和notify()差不多,隻不過是使所有正在等待池中等待同一共享資源的全部線程從等待狀態退出,進入可運行狀態
讓它們競爭對象的鎖,隻有獲得鎖的線程才能進入就緒狀態
每個鎖對象有兩個隊列:就緒隊列和阻塞隊列
– 就緒隊列:存儲將要獲得鎖的線程
– 阻塞隊列:存儲被阻塞的線程
六、實例
1、sleep
public class ThreadDemo1 { public static void main(String[] args) { MyThread mt = new MyThread(); //推薦 MyRunnable mr = new MyRunnable(); Thread t2 = new Thread(mr); mt.start();//啟動線程 t2.start(); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "-" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * 實現線程的第一種方式:繼承thread類 */ class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { if (this.isInterrupted()) { break; } System.out.println(Thread.currentThread().getName() + "-" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); this.interrupt(); } } } } /** * 實現線程的第二種方式:實現Runnable接口 */ class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "-" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
2、join和中斷(推薦用標記中斷)
public class ThreadDemo2 { public static void main(String[] args){ MyRunable2 mr2 = new MyRunable2(); Thread t = new Thread(mr2); // t.start(); MyRunable3 mr3 = new MyRunable3(); Thread t2 = new Thread(mr3); t2.start(); for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName()+"--"+i); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } if(i==20){ // try { //這些打開用來測試join // t.join();//讓t線程執行完畢 // } catch (InterruptedException e) { // e.printStackTrace(); // } // t.interrupt();//中斷線程,隻是作瞭一個中斷標記,用於測試interrupt方法 mr3.flag = false; //用於測試標記中斷 } } } } class MyRunable2 implements Runnable{ @Override public void run() { for (int i = 0; i < 50; i++) { if(Thread.interrupted()){//測試中斷狀態,此方法會把中斷狀態清除 //.... break; } System.out.println(Thread.currentThread().getName()+"--"+i); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } } } <br>//標記中斷 class MyRunable3 implements Runnable{ public boolean flag = true; public MyRunable3(){ flag = true; } @Override public void run() { int i=0; while(flag){ System.out.println(Thread.currentThread().getName()+"==="+(i++)); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } }
3、優先級和守護進程
public class ThreadDemo3 { public static void main(String[] args){ MyRunnable4 mr4 = new MyRunnable4(); Thread t = new Thread(mr4); t.setName("Thread-t"); //優先級高可以提高該線程搶點CPU時間片的概率大 t.setPriority(Thread.MAX_PRIORITY); //線程可以分成守護線程和 用戶線程,當進程中沒有用戶線程時,JVM會退出 t.setDaemon(true);//把線程設置為守護線程 System.out.println(t.isAlive()); t.start(); System.out.println(t.isAlive()); for (int i = 0; i < 50; i++) { System.out.println("main--"+i); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } if (i==5){ Thread.yield();//讓出本次CPU執行時間片 } } } } class MyRunnable4 implements Runnable{ @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("--"+i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
4、生產者與消費者
定義一個接口:
package threadtest.procon; public interface AbstractStorage { void consume(int num); void product(int num); }
定義一個類實現接口,用於存放生產的東西
package threadtest.procon; import java.util.LinkedList; public class Storage implements AbstractStorage{ private final int MAX_SIZE = 100; private LinkedList list = new LinkedList(); @Override public void consume(int num) { synchronized (list){ while (list.size()<num){ System.out.println("【要消費的產品數量】:" + num + "\t【庫存量】:"+ list.size() + "\t暫時不能執行消費任務!"); try { list.wait(2000); } catch (InterruptedException e) { e.printStackTrace(); } } for(int i=0;i<num;i++){ list.remove(); } System.out.println("【已經消費產品數】:" + num + "\t【現倉儲量為】:" + list.size()); list.notifyAll(); } } @Override public void product(int num) { synchronized (list){ while(list.size()+num > MAX_SIZE){ System.out.println("【要生產的產品數量】:" + num + "\t【庫存量】:" + list.size() + "\t暫時不能執行生成任務!"); try { list.wait(2000); } catch (InterruptedException e) { e.printStackTrace(); } } for(int i=0;i<num;i++){ list.add(new Object()); } System.out.println("【已經生產產品數】:" + num + "\t【現倉儲量為】:" + list.size()); list.notifyAll(); } } }
生產者類:
package threadtest.procon; public class Producer extends Thread { private int num; public AbstractStorage abstractStorage; public Producer(AbstractStorage abstractStorage){ this.abstractStorage = abstractStorage; } public void setNum(int num) { this.num = num; } public void produce(int num){ abstractStorage.product(num); } @Override public void run() { produce(num); } }
消費者類:
package threadtest.procon; public class Consumer extends Thread { private int num; public AbstractStorage abstractStorage; public Consumer(AbstractStorage abstractStorage){ this.abstractStorage = abstractStorage; } public void setNum(int num){ this.num = num; } public void consume(int num){ this.abstractStorage.consume(num); } @Override public void run() { consume(num); } }
測試類:
package threadtest.procon; public class Test { public static void main(String[] args){ AbstractStorage abstractStorage = new Storage(); // 生產者對象 Producer p1 = new Producer(abstractStorage); Producer p2 = new Producer(abstractStorage); Producer p3 = new Producer(abstractStorage); Producer p4 = new Producer(abstractStorage); Producer p5 = new Producer(abstractStorage); Producer p6 = new Producer(abstractStorage); Producer p7 = new Producer(abstractStorage); // 消費者對象 Consumer c1 = new Consumer(abstractStorage); Consumer c2 = new Consumer(abstractStorage); Consumer c3 = new Consumer(abstractStorage); // 設置生產者產品生產數量 p1.setNum(10); p2.setNum(20); p3.setNum(30); p4.setNum(40); p5.setNum(30); p6.setNum(20); p7.setNum(80); // 設置消費者產品消費數量 c1.setNum(50); c2.setNum(70); c3.setNum(20); c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); p6.start(); p7.start(); } }
以上就是詳解Java多線程與並發的詳細內容,更多關於Java 多線程 並發的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- java wait()/notify() 實現生產者消費者模式詳解
- Java Thread之Sleep()案例詳解
- Java中的線程生命周期核心概念
- Java中實現線程間通信的實例教程
- Java多線程:生產者與消費者案例