詳解Java多線(xiàn)程與并發(fā)
一、進(jìn)程與線(xiàn)程
進(jìn)程:是代碼在數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位。
線(xiàn)程:是進(jìn)程的一個(gè)執(zhí)行路徑,一個(gè)進(jìn)程中至少有一個(gè)線(xiàn)程,進(jìn)程中的多個(gè)線(xiàn)程共享進(jìn)程的 資源。
雖然系統(tǒng)是把資源分給進(jìn)程,但是CPU很特殊,是被分配到線(xiàn)程的,所以線(xiàn)程是CPU分配的基本單位。
二者關(guān)系:
一個(gè)進(jìn)程中有多個(gè)線(xiàn)程,多個(gè)線(xiàn)程共享進(jìn)程的堆和方法區(qū)資源,但是每個(gè)線(xiàn)程有自己的程序計(jì)數(shù)器和棧區(qū)域。
- 程序計(jì)數(shù)器:是一塊內(nèi)存區(qū)域,用來(lái)記錄線(xiàn)程當(dāng)前要執(zhí)行的指令地址 。
- 棧:用于存儲(chǔ)該線(xiàn)程的局部變量,這些局部變量是該線(xiàn)程私有的,除此之外還用來(lái)存放線(xiàn)程的調(diào)用棧禎。
- 堆:是一個(gè)進(jìn)程中最大的一塊內(nèi)存,堆是被進(jìn)程中的所有線(xiàn)程共享的。
- 方法區(qū):則用來(lái)存放 NM 加載的類(lèi)、常量及靜態(tài)變量等信息,也是線(xiàn)程共享的 。
二者區(qū)別:
- 進(jìn)程:有獨(dú)立的地址空間,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響。
- 線(xiàn)程:是一個(gè)進(jìn)程中的不同執(zhí)行路徑。線(xiàn)程有自己的堆棧和局部變量,但線(xiàn)程之間沒(méi)有單獨(dú)的地址空間,一個(gè)線(xiàn)程死掉就等于整個(gè)進(jìn)程死掉。
1)簡(jiǎn)而言之,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線(xiàn)程.
2)線(xiàn)程的劃分尺度小于進(jìn)程,使得多線(xiàn)程程序的并發(fā)性高。
3)另外,進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線(xiàn)程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率。
4)每個(gè)獨(dú)立的線(xiàn)程有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列和程序的出口。但是線(xiàn)程不能夠獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個(gè)線(xiàn)程執(zhí)行控制。
5)從邏輯角度來(lái)看,多線(xiàn)程的意義在于一個(gè)應(yīng)用程序中,有多個(gè)執(zhí)行部分可以同時(shí)執(zhí)行。但操作系統(tǒng)并沒(méi)有將多個(gè)線(xiàn)程看做多個(gè)獨(dú)立的應(yīng)用,來(lái)實(shí)現(xiàn)進(jìn)程的調(diào)度和管理以及資源分配。這就是進(jìn)程和線(xiàn)程的重要區(qū)別
二、并發(fā)與并行
并發(fā):是指同一個(gè)時(shí)間段內(nèi)多個(gè)任務(wù)同時(shí)都在執(zhí)行,并且都沒(méi)有執(zhí)行結(jié)束。并發(fā)任務(wù)強(qiáng)調(diào)在一個(gè)時(shí)間段內(nèi)同時(shí)執(zhí)行,而一個(gè)時(shí)間段由多個(gè)單位時(shí)間累積而成,所以說(shuō)并發(fā)的多個(gè)任務(wù)在單位時(shí)間內(nèi)不一定同時(shí)在執(zhí)行 。
并行:是說(shuō)在單位時(shí)間內(nèi)多個(gè)任務(wù)同時(shí)在執(zhí)行 。
在多線(xiàn)程編程實(shí)踐中,線(xiàn)程的個(gè)數(shù)往往多于CPU的個(gè)數(shù),所以一般都稱(chēng)多線(xiàn)程并發(fā)編程而不是多線(xiàn)程并行編程。
并發(fā)過(guò)程中常見(jiàn)的問(wèn)題:
1、線(xiàn)程安全問(wèn)題
多個(gè)線(xiàn)程同時(shí)操作共享變量1時(shí),會(huì)出現(xiàn)線(xiàn)程1更新共享變量1的值,但是其他線(xiàn)程獲取到的是共享變量沒(méi)有被更新之前的值。就會(huì)導(dǎo)致數(shù)據(jù)不準(zhǔn)確問(wèn)題。
2、共享內(nèi)存不可見(jiàn)性問(wèn)題
Java內(nèi)存模型(處理共享變量)
Java 內(nèi)存模型規(guī)定,將所有的變量都存放在主內(nèi)存中,當(dāng)線(xiàn)程使用變量時(shí),會(huì)把主內(nèi)存里面的變量復(fù)制到自己的工作空間或者叫作工作內(nèi)存,線(xiàn)程讀寫(xiě)變量時(shí)操作的是自己工作內(nèi)存中的變量 。(如上圖所示)
(實(shí)際工作的java內(nèi)存模型)
上圖中所示是一個(gè)雙核 CPU 系統(tǒng)架構(gòu),每個(gè)核有自己的控制器和運(yùn)算器,其中控制器包含一組寄存器和操作控制器,運(yùn)算器執(zhí)行算術(shù)邏輔運(yùn)算。CPU的每個(gè)核都有自己的一級(jí)緩存,在有些架構(gòu)里面還有一個(gè)所有CPU都共享的二級(jí)緩存。 那么Java內(nèi)存模型里面的工作內(nèi)存,就對(duì)應(yīng)這里的 Ll或者 L2 緩存或者 CPU 的寄存器
1、線(xiàn)程A首先獲取共享變量X的值,由于兩級(jí)Cache都沒(méi)有命中,所以加載主內(nèi)存中X的值,假如為0。然后把X=0的值緩存到兩級(jí)緩存,線(xiàn)程A修改X的值為1,然后將其寫(xiě)入兩級(jí)Cache,并且刷新到主內(nèi)存。線(xiàn)程A操作完畢后,線(xiàn)程A所在的CPU的兩級(jí)Cache內(nèi)和主內(nèi)存里面的X的值都是l。
2、線(xiàn)程B獲取X的值,首先一級(jí)緩存沒(méi)有命中,然后看二級(jí)緩存,二級(jí)緩存命中了,所以返回X=1;到這里一切都是正常的,因?yàn)檫@時(shí)候主內(nèi)存中也是X=l。然后線(xiàn)程B修改X的值為2,并將其存放到線(xiàn)程2所在的一級(jí)Cache和共享二級(jí)Cache中,最后更新主內(nèi)存中X的值為2,到這里一切都是好的。
3、線(xiàn)程A這次又需要修改X的值,獲取時(shí)一級(jí)緩存命中,并且X=l這里問(wèn)題就出現(xiàn)了,明明線(xiàn)程B已經(jīng)把X的值修改為2,為何線(xiàn)程A獲取的還是l呢?這就是共享變量的內(nèi)存不可見(jiàn)問(wèn)題,也就是線(xiàn)程B寫(xiě)入的值對(duì)線(xiàn)程A不可見(jiàn)。
synchronized 的內(nèi)存語(yǔ)義:
這個(gè)內(nèi)存語(yǔ)義就可以解決共享變量?jī)?nèi)存可見(jiàn)性問(wèn)題。進(jìn)入synchronized塊的內(nèi)存語(yǔ)義是把在synchronized塊內(nèi)使用到的變量從線(xiàn)程的工作內(nèi)存中清除,這樣在synchronized塊內(nèi)使用到該變量時(shí)就不會(huì)從線(xiàn)程的工作內(nèi)存中獲取,而是直接從主內(nèi)存中獲取。退出synchronized塊的內(nèi)存語(yǔ)義是把在synchronized塊內(nèi)對(duì)共享變量的修改刷新到主內(nèi)存。會(huì)造成上下文切換的開(kāi)銷(xiāo),獨(dú)占鎖,降低并發(fā)性
Volatile的理解:
該關(guān)鍵字可以確保對(duì)一個(gè)變量的更新對(duì)其他線(xiàn)程馬上可見(jiàn)。當(dāng)一個(gè)變量被聲明為volatile時(shí),線(xiàn)程在寫(xiě)入變量時(shí)不會(huì)把值緩存在寄存器或者其他地方,而是會(huì)把值刷新回主內(nèi)存。當(dāng)其他線(xiàn)程讀取該共享變量時(shí)-,會(huì)從主內(nèi)存重新獲取最新值,而不是使用當(dāng)前線(xiàn)程的工作內(nèi)存中的值。volatile的內(nèi)存語(yǔ)義和synchronized有相似之處,具體來(lái)說(shuō)就是,當(dāng)線(xiàn)程寫(xiě)入了volatile變量值時(shí)就等價(jià)于線(xiàn)程退出synchronized同步塊(把寫(xiě)入工作內(nèi)存的變量值同步到主內(nèi)存),讀取volatile變量值時(shí)就相當(dāng)于進(jìn)入同步塊(先清空本地內(nèi)存變量值,再?gòu)闹鲀?nèi)存獲取最新值)。不能保證原子性
三、創(chuàng)建線(xiàn)程
1、繼承Thread類(lèi)
重寫(xiě)run方法:使用繼承方式的好處是,在run()方法內(nèi)獲取當(dāng)前線(xiàn)程直接使用this就可以了,無(wú)須使用Thread.currentThread()方法;不好的地方是Java不支持多繼承,如果繼承了Thread類(lèi),那么就不能再繼承其他類(lèi)。另外任務(wù)與代碼沒(méi)有分離,當(dāng)多個(gè)線(xiàn)程執(zhí)行一樣的任務(wù)時(shí)需要多份任務(wù)代碼。
public class ThreadRuning extends Thread{ public ThreadRuning(String name){ //重寫(xiě)構(gòu)造,可以對(duì)線(xiàn)程添加名字 super(name); } @Override public void run() { while(true){ System.out.println("good time"); //在run方法里,this代表當(dāng)前線(xiàn)程 System.out.println(this); } } public static void main(String[] args){ ThreadRuning threadRuning = new ThreadRuning("1111"); threadRuning.start(); } }
2、實(shí)現(xiàn)Runable接口
實(shí)現(xiàn)run方法:解決繼承Thread的缺點(diǎn),沒(méi)有返回值
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、實(shí)現(xiàn)Callable接口
實(shí)現(xiàn)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(); } } }
使用繼承方式的好處是方便傳參,你可以在子類(lèi)里面添加成員變量,通過(guò)set方法設(shè)置參數(shù)或者通過(guò)構(gòu)造函數(shù)進(jìn)行傳遞,而如果使用Runnable方式,則只能使用主線(xiàn)程里面被聲明為final的變量。不好的地方是Java不支持多繼承,如果繼承了Thread類(lèi),那么子類(lèi)不能再繼承其他類(lèi),而Runable則沒(méi)有這個(gè)限制。前兩種方式都沒(méi)辦法拿到任務(wù)的返回結(jié)果,但是Callable方式可以
四、Thread類(lèi)詳解
1、線(xiàn)程特性
1、線(xiàn)程能被標(biāo)記為守護(hù)線(xiàn)程,也可以是用戶(hù)線(xiàn)程
2、每個(gè)線(xiàn)程均分配一個(gè)name,默認(rèn)為(Thread-自增數(shù)字)的組合
3、每個(gè)線(xiàn)程都有優(yōu)先級(jí).高優(yōu)先級(jí)線(xiàn)程優(yōu)先于低優(yōu)先級(jí)線(xiàn)程執(zhí)行. 1-10,默認(rèn)為5
4、main所在的線(xiàn)程組為main,構(gòu)造線(xiàn)程的時(shí)候沒(méi)有現(xiàn)實(shí)的指定線(xiàn)程組,線(xiàn)程組默認(rèn)和父線(xiàn)程一樣
5、當(dāng)線(xiàn)程中的run()方法代碼里面又創(chuàng)建了一個(gè)新的線(xiàn)程對(duì)象時(shí),新創(chuàng)建的線(xiàn)程優(yōu)先級(jí)和父線(xiàn)程優(yōu)先級(jí)一樣.
6、當(dāng)且僅當(dāng)父線(xiàn)程為守護(hù)線(xiàn)程時(shí),新創(chuàng)建的線(xiàn)程才會(huì)是守護(hù)線(xiàn)程.
7、當(dāng)JVM啟動(dòng)時(shí),通常會(huì)有唯一的一個(gè)非守護(hù)線(xiàn)程(這一線(xiàn)程用于調(diào)用指定類(lèi)的main()方法)
JVM會(huì)持續(xù)執(zhí)行線(xiàn)程直到下面情況某一個(gè)發(fā)生為止:
1)類(lèi)運(yùn)行時(shí)exit()方法被調(diào)用 且 安全機(jī)制允許此exit()方法的調(diào)用.
2)所有非守護(hù)類(lèi)型的線(xiàn)程均已經(jīng)終止,or run()方法調(diào)用返回or在run()方法外部拋出了一些可傳播性的異常.
2、Init方法
/** * Initializes a Thread. * @param g 線(xiàn)程組 * @param target 執(zhí)行對(duì)象 * @param name 線(xiàn)程名 * @param stackSize 新線(xiàn)程棧大小,為0表示忽略 * @param acc用于繼承的訪(fǎng)問(wèn)控制上下文 * @param inheritThreadLocals如果值為true,從構(gòu)造線(xiàn)程繼承可繼承線(xiàn)程局部變量的初始值 */ 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(); //如果所屬線(xiàn)程組為null if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager //如果有安全管理,查詢(xún)安全管理需要做的工作 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. */ //如果安全管理在線(xiàn)程所屬父線(xiàn)程組的問(wèn)題上沒(méi)有什么強(qiáng)制的要求 if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ //無(wú)論所屬線(xiàn)程組是否顯示傳入,都要進(jìn)行檢查訪(fǎng)問(wèn). 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();//如果父線(xiàn)程為守護(hù)線(xiàn)程,則此線(xiàn)程也被 設(shè)置為守護(hù)線(xiàn)程. this.priority = parent.getPriority();//獲取父進(jìn)程的優(yōu)先級(jí) 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 設(shè)置線(xiàn)程id*/ tid = nextThreadID(); }
3、構(gòu)造方法
所有的構(gòu)造方法都是調(diào)用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、線(xiàn)程狀態(tài)
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
NEW:狀態(tài)是指線(xiàn)程剛創(chuàng)建, 尚未啟動(dòng)
RUNNABLE:狀態(tài)是線(xiàn)程正在正常運(yùn)行中, 當(dāng)然可能會(huì)有某種耗時(shí)計(jì)算/IO等待的操作/CPU時(shí)間片切換等, 這個(gè)狀態(tài)下發(fā)生的等待一般是其他系統(tǒng)資源, 而不是鎖, Sleep等
BLOCKED:這個(gè)狀態(tài)下, 是在多個(gè)線(xiàn)程有同步操作的場(chǎng)景, 比如正在等待另一個(gè)線(xiàn)程的synchronized 塊的執(zhí)行釋放, 或者可重入的 synchronized塊里別人調(diào)用wait() 方法, 也就是這里是線(xiàn)程在等待進(jìn)入臨界區(qū)
WAITING:這個(gè)狀態(tài)下是指線(xiàn)程擁有了某個(gè)鎖之后, 調(diào)用了他的wait方法, 等待其他線(xiàn)程/鎖擁有者調(diào)用 notify / notifyAll 一遍該線(xiàn)程可以繼續(xù)下一步操作, 這里要區(qū)分 BLOCKED 和 WATING 的區(qū)別, 一個(gè)是在臨界點(diǎn)外面等待進(jìn)入, 一個(gè)是在理解點(diǎn)里面wait等待別人notify, 線(xiàn)程調(diào)用了join方法 join了另外的線(xiàn)程的時(shí)候, 也會(huì)進(jìn)入WAITING狀態(tài), 等待被他join的線(xiàn)程執(zhí)行結(jié)束
TIMED_WAITING:這個(gè)狀態(tài)就是有限的(時(shí)間限制)的WAITING, 一般出現(xiàn)在調(diào)用wait(long), join(long)等情況下, 另外一個(gè)線(xiàn)程sleep后, 也會(huì)進(jìn)入TIMED_WAITING狀態(tài)
TERMINATED:這個(gè)狀態(tài)下表示 該線(xiàn)程的run方法已經(jīng)執(zhí)行完畢了, 基本上就等于死亡了(當(dāng)時(shí)如果線(xiàn)程被持久持有, 可能不會(huì)被回收)
(在很多文章中都寫(xiě)了running狀態(tài),其實(shí)源碼里面只有六種的,當(dāng)自己寫(xiě)一個(gè)線(xiàn)程通過(guò)while一直保持執(zhí)行狀態(tài),然后使用jconsole工具去查看線(xiàn)程的狀態(tài),確實(shí)是Runable狀態(tài))
Api文檔是這么說(shuō)的:
其實(shí)我們可以理解為兩種狀態(tài),一個(gè)是running,表示正在執(zhí)行,一個(gè)是runable,表示準(zhǔn)備就緒了,只是在等待其他的系統(tǒng)資源。然后我們就可以理解如下圖
5、Start方法
public synchronized void start() { /** * 此方法并不會(huì)被主要方法線(xiàn)程or由虛擬機(jī)創(chuàng)建的系統(tǒng)組線(xiàn)程所調(diào)用. * 任何向此方法添加的新功能方法在未來(lái)都會(huì)被添加到虛擬機(jī)中. * 0狀態(tài)值代表了NEW的狀態(tài). */ if (threadStatus != 0) // 線(xiàn)程不能重復(fù)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();
是一個(gè)本地方法,提示線(xiàn)程調(diào)度器當(dāng)前線(xiàn)程愿意放棄當(dāng)前CPU的使用。如果當(dāng)前資源不緊張,調(diào)度器可以忽略這個(gè)提示。本質(zhì)上線(xiàn)程狀態(tài)一直是RUNNABLE,但是我可以理解為RUNNABLE到RUNNING的轉(zhuǎn)換
7、sleep方法
/** * 此方法會(huì)引起當(dāng)前執(zhí)行線(xiàn)程sleep(臨時(shí)停止執(zhí)行)指定毫秒數(shù). * 此方法的調(diào)用不會(huì)引起當(dāng)前線(xiàn)程放棄任何監(jiān)聽(tīng)器(monitor)的所有權(quán)(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方法,有一個(gè)重載方法,sleep方法會(huì)釋放cpu的時(shí)間片,但是不會(huì)釋放鎖,調(diào)用sleep()之后從RUNNABLE狀態(tài)轉(zhuǎn)為T(mén)IMED_WAITING狀態(tài)
8、join方法
/** * 最多等待參數(shù)millis(ms)時(shí)長(zhǎng)當(dāng)前線(xiàn)程就會(huì)死亡.參數(shù)為0時(shí)則要持續(xù)等待. * 此方法在實(shí)現(xiàn)上:循環(huán)調(diào)用以this.isAlive()方法為條件的wait()方法. * 當(dāng)線(xiàn)程終止時(shí)notifyAll()方法會(huì)被調(diào)用. * 建議應(yīng)用程序不要在線(xiàn)程實(shí)例上使用wait,notify,notifyAll方法. */ public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; //如果等待時(shí)間<0,則拋出異常 if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } //如果等待時(shí)間為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; } } } //等待時(shí)間單位為納秒,其它解釋都和上面方法一樣 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); } //方法功能:等待一直到線(xiàn)程死亡. public final void join() throws InterruptedException { join(0); }
join某個(gè)線(xiàn)程A,會(huì)使得線(xiàn)程B進(jìn)入等待,知道線(xiàn)程A結(jié)束,或者到達(dá)給定的時(shí)間,那么期間線(xiàn)程B處于BLOCKED的狀態(tài),而不是線(xiàn)程A
五、其他方法
接下來(lái)聊一下Object類(lèi)的wait,notify和notifyAll方法
1、wait方法
public final native void wait(long timeout) throws InterruptedException; //本地方法 參數(shù)為毫秒 public final void wait(long timeout, int nanos) throws InterruptedException {//參數(shù)為毫秒和納秒 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); }
可見(jiàn)wait()和wait(long timeout, int nanos)都在在內(nèi)部調(diào)用了wait(long timeout)方法。
下面主要是說(shuō)說(shuō)wait(long timeout)方法
wait方法會(huì)引起當(dāng)前線(xiàn)程阻塞,直到另外一個(gè)線(xiàn)程在對(duì)應(yīng)的對(duì)象上調(diào)用notify或者notifyAll()方法,或者達(dá)到了方法參數(shù)中指定的時(shí)間。
調(diào)用wait方法的當(dāng)前線(xiàn)程一定要擁有對(duì)象的監(jiān)視器鎖。
wait方法會(huì)把當(dāng)前線(xiàn)程T放置在對(duì)應(yīng)的object上的等待隊(duì)列中,在這個(gè)對(duì)象上的所有同步請(qǐng)求都不會(huì)得到響應(yīng)。線(xiàn)程調(diào)度將不會(huì)調(diào)用線(xiàn)程T,在以下四件事發(fā)生之前,線(xiàn)程T會(huì)被喚醒(線(xiàn)程T是在其代碼中調(diào)用wait方法的那個(gè)線(xiàn)程)
1、當(dāng)其他的線(xiàn)程在對(duì)應(yīng)的對(duì)象上調(diào)用notify方法,而在此對(duì)象的對(duì)應(yīng)的等待隊(duì)列中將會(huì)任意選擇一個(gè)線(xiàn)程進(jìn)行喚醒。
2、其他的線(xiàn)程在此對(duì)象上調(diào)用了notifyAll方法
3、其他的線(xiàn)程調(diào)用了interrupt方法來(lái)中斷線(xiàn)程T
4、等待的時(shí)間已經(jīng)超過(guò)了wait中指定的時(shí)間。如果參數(shù)timeout的值為0,不是指真實(shí)的等待時(shí)間是0,而是線(xiàn)程等待直到被另外一個(gè)線(xiàn)程喚醒為止。
被喚醒的線(xiàn)程T會(huì)被從對(duì)象的等待隊(duì)列中移除并且重新能夠被線(xiàn)程調(diào)度器調(diào)度。之后,線(xiàn)程T會(huì)像平常一樣跟其他的線(xiàn)程競(jìng)爭(zhēng)獲取對(duì)象上的鎖;一旦線(xiàn)程T獲得了此對(duì)象上的鎖,那么在此對(duì)象上的所有同步請(qǐng)求都會(huì)恢復(fù)到之前的狀態(tài),也就是恢復(fù)到wait被調(diào)用的情況下。然后線(xiàn)程T從wait方法的調(diào)用中返回。因此,當(dāng)從wait方法返回時(shí),對(duì)象的狀態(tài)以及線(xiàn)程T的狀態(tài)跟wait方法被調(diào)用的時(shí)候一樣。
線(xiàn)程在沒(méi)有被喚醒,中斷或者時(shí)間耗盡的情況下仍然能夠被喚醒,這叫做偽喚醒。雖然在實(shí)際中,這種情況很少發(fā)生,但是程序一定要測(cè)試這個(gè)能夠喚醒線(xiàn)程的條件,并且在條件不滿(mǎn)足時(shí),線(xiàn)程繼續(xù)等待。換言之,wait操作總是出現(xiàn)在循環(huán)中,就像下面這樣:
synchronized(對(duì)象){ while(條件不滿(mǎn)足){ 對(duì)象.wait(); } 對(duì)應(yīng)的邏輯處理 }
如果當(dāng)前的線(xiàn)程被其他的線(xiàn)程在當(dāng)前線(xiàn)程等待之前或者正在等待時(shí)調(diào)用了interrupt()中斷了,那么會(huì)拋出InterruptedExcaption異常。直到這個(gè)對(duì)象上面的鎖狀態(tài)恢復(fù)到上面描述的狀態(tài)以前,這個(gè)異常是不會(huì)拋出的。
要注意的是,wait方法把當(dāng)前線(xiàn)程放置到這個(gè)對(duì)象的等待隊(duì)列中,解鎖也僅僅是在這個(gè)對(duì)象上;當(dāng)前線(xiàn)程在其他對(duì)象上面上的鎖在當(dāng)前線(xiàn)程等待的過(guò)程中仍然持有其他對(duì)象的鎖。
這個(gè)方法應(yīng)該僅僅被持有對(duì)象監(jiān)視器的線(xiàn)程調(diào)用。 wait(long timeout, int nanos)方法的實(shí)現(xiàn)中只要nanos大于0,那么timeout時(shí)間就加上一毫秒,主要是更精確的控制時(shí)間,其他的跟wait(long timeout)一樣
2、notify方法
public final native void notify(); //本地方法
通知可能等待該對(duì)象的對(duì)象鎖的其他線(xiàn)程。由JVM(與優(yōu)先級(jí)無(wú)關(guān))隨機(jī)挑選一個(gè)處于wait狀態(tài)的線(xiàn)程。
在調(diào)用notify()之前,線(xiàn)程必須獲得該對(duì)象的對(duì)象級(jí)別鎖
執(zhí)行完notify()方法后,不會(huì)馬上釋放鎖,要直到退出synchronized代碼塊,當(dāng)前線(xiàn)程才會(huì)釋放鎖
notify()一次只隨機(jī)通知一個(gè)線(xiàn)程進(jìn)行喚醒
3、notifyAll()方法
public final native void notifyAll();//本地方法
和notify()差不多,只不過(guò)是使所有正在等待池中等待同一共享資源的全部線(xiàn)程從等待狀態(tài)退出,進(jìn)入可運(yùn)行狀態(tài)
讓它們競(jìng)爭(zhēng)對(duì)象的鎖,只有獲得鎖的線(xiàn)程才能進(jìn)入就緒狀態(tài)
每個(gè)鎖對(duì)象有兩個(gè)隊(duì)列:就緒隊(duì)列和阻塞隊(duì)列
- 就緒隊(duì)列:存儲(chǔ)將要獲得鎖的線(xiàn)程
- 阻塞隊(duì)列:存儲(chǔ)被阻塞的線(xiàn)程
六、實(shí)例
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();//啟動(dòng)線(xiàn)程 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(); } } } } /** * 實(shí)現(xiàn)線(xiàn)程的第一種方式:繼承thread類(lèi) */ 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(); } } } } /** * 實(shí)現(xiàn)線(xiàn)程的第二種方式:實(shí)現(xiàn)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和中斷(推薦用標(biāo)記中斷)
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 { //這些打開(kāi)用來(lái)測(cè)試join // t.join();//讓t線(xiàn)程執(zhí)行完畢 // } catch (InterruptedException e) { // e.printStackTrace(); // } // t.interrupt();//中斷線(xiàn)程,只是作了一個(gè)中斷標(biāo)記,用于測(cè)試interrupt方法 mr3.flag = false; //用于測(cè)試標(biāo)記中斷 } } } } class MyRunable2 implements Runnable{ @Override public void run() { for (int i = 0; i < 50; i++) { if(Thread.interrupted()){//測(cè)試中斷狀態(tài),此方法會(huì)把中斷狀態(tài)清除 //.... break; } System.out.println(Thread.currentThread().getName()+"--"+i); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } } } <br>//標(biāo)記中斷 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、優(yōu)先級(jí)和守護(hù)進(jìn)程
public class ThreadDemo3 { public static void main(String[] args){ MyRunnable4 mr4 = new MyRunnable4(); Thread t = new Thread(mr4); t.setName("Thread-t"); //優(yōu)先級(jí)高可以提高該線(xiàn)程搶點(diǎn)CPU時(shí)間片的概率大 t.setPriority(Thread.MAX_PRIORITY); //線(xiàn)程可以分成守護(hù)線(xiàn)程和 用戶(hù)線(xiàn)程,當(dāng)進(jìn)程中沒(méi)有用戶(hù)線(xiàn)程時(shí),JVM會(huì)退出 t.setDaemon(true);//把線(xiàn)程設(shè)置為守護(hù)線(xiàn)程 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執(zhí)行時(shí)間片 } } } } 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、生產(chǎn)者與消費(fèi)者
定義一個(gè)接口:
package threadtest.procon; public interface AbstractStorage { void consume(int num); void product(int num); }
定義一個(gè)類(lèi)實(shí)現(xiàn)接口,用于存放生產(chǎn)的東西
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("【要消費(fèi)的產(chǎn)品數(shù)量】:" + num + "\t【庫(kù)存量】:"+ list.size() + "\t暫時(shí)不能執(zhí)行消費(fèi)任務(wù)!"); try { list.wait(2000); } catch (InterruptedException e) { e.printStackTrace(); } } for(int i=0;i<num;i++){ list.remove(); } System.out.println("【已經(jīng)消費(fèi)產(chǎn)品數(shù)】:" + num + "\t【現(xiàn)倉(cāng)儲(chǔ)量為】:" + list.size()); list.notifyAll(); } } @Override public void product(int num) { synchronized (list){ while(list.size()+num > MAX_SIZE){ System.out.println("【要生產(chǎn)的產(chǎn)品數(shù)量】:" + num + "\t【庫(kù)存量】:" + list.size() + "\t暫時(shí)不能執(zhí)行生成任務(wù)!"); try { list.wait(2000); } catch (InterruptedException e) { e.printStackTrace(); } } for(int i=0;i<num;i++){ list.add(new Object()); } System.out.println("【已經(jīng)生產(chǎn)產(chǎn)品數(shù)】:" + num + "\t【現(xiàn)倉(cāng)儲(chǔ)量為】:" + list.size()); list.notifyAll(); } } }
生產(chǎn)者類(lèi):
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); } }
消費(fèi)者類(lèi):
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); } }
測(cè)試類(lèi):
package threadtest.procon; public class Test { public static void main(String[] args){ AbstractStorage abstractStorage = new Storage(); // 生產(chǎn)者對(duì)象 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); // 消費(fèi)者對(duì)象 Consumer c1 = new Consumer(abstractStorage); Consumer c2 = new Consumer(abstractStorage); Consumer c3 = new Consumer(abstractStorage); // 設(shè)置生產(chǎn)者產(chǎn)品生產(chǎn)數(shù)量 p1.setNum(10); p2.setNum(20); p3.setNum(30); p4.setNum(40); p5.setNum(30); p6.setNum(20); p7.setNum(80); // 設(shè)置消費(fèi)者產(chǎn)品消費(fèi)數(shù)量 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多線(xiàn)程與并發(fā)的詳細(xì)內(nèi)容,更多關(guān)于Java 多線(xiàn)程 并發(fā)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot與velocity的結(jié)合的示例代碼
本篇文章主要介紹了SpringBoot與velocity的結(jié)合的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03使用RestTemplate 調(diào)用遠(yuǎn)程接口上傳文件方式
這篇文章主要介紹了使用RestTemplate 調(diào)用遠(yuǎn)程接口上傳文件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Spring BeanPostProcessor接口使用詳解
本篇文章主要介紹了Spring BeanPostProcessor接口使用詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01Java中字符數(shù)組和字符串與StringBuilder和字符串轉(zhuǎn)換的講解
今天小編就為大家分享一篇關(guān)于Java中字符數(shù)組和字符串與StringBuilder和字符串轉(zhuǎn)換的講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03Java圖像之自定義角度旋轉(zhuǎn)(實(shí)例)
這篇文章主要介紹了Java圖像之自定義角度旋轉(zhuǎn)(實(shí)例),需要的朋友可以參考下2017-09-09