其實(shí)你已經(jīng)很好了,我認(rèn)為寫程序首先要有自己的思路,其次才是看你真正掌握的技能...比如一艘船如果有足夠大的馬力,但是缺少正確的方向..那樣子會(huì)裝上暗礁的,所以在編程方面業(yè)務(wù)邏輯是很重要的,接下來只要有一般的技能基礎(chǔ)就可以了;
成都創(chuàng)新互聯(lián)是一家專業(yè)提供平樂企業(yè)網(wǎng)站建設(shè),專注與成都做網(wǎng)站、網(wǎng)站設(shè)計(jì)、H5建站、小程序制作等業(yè)務(wù)。10年已為平樂眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)絡(luò)公司優(yōu)惠進(jìn)行中。
就拿你說的修改密碼來說吧:第一步:我首先要知道要修改人的ID,然后才能按照一定的方法修改數(shù)據(jù)庫中的表:一個(gè)update user set user_password=“要修改的密碼” where user_id=“指定修改人的ID” 再加上一定的連接數(shù)據(jù)庫的方法..程序員修改密碼的目的就達(dá)到了...加油!
這個(gè)問題其實(shí)涉及到socket套接字的緩沖機(jī)制,客戶端和服務(wù)器都綁定有輸入輸出流,在你發(fā)送的時(shí)候如果字節(jié)數(shù)沒有到達(dá)默認(rèn)的緩沖大小的時(shí)候會(huì)產(chǎn)生你這樣的問題,你只需要在發(fā)送完后flush刷新一下緩沖區(qū)就可以了
我認(rèn)為Java語言的10大問題是:
1、缺少閉包(closure):我想這個(gè)不需要解釋了。函數(shù)式編程已經(jīng)存在幾十年了,但最近幾年,它們獲得了越來越多的關(guān)注,最主要的原因,是它可以自然地編寫并行程序。我部分的同意Joshua Bloch強(qiáng)調(diào)在Java中引入閉包的問題需要再想一想(BGGA提議的方式真的很糟),至少閉包的缺失,使得在Java中做任何真正的函數(shù)式編程都是不可能的。
2、缺少一等函數(shù):這個(gè)問題與前一個(gè)有些關(guān)聯(lián),但我認(rèn)為它更糟糕。在Java里,要達(dá)到類似效果的唯一方式,是使用著名的、丑陋悲慘的單方法匿名內(nèi)部類,但這看上去的確是一個(gè)拙劣的方法。甚至在C#中,也通過代理機(jī)制,提供了一個(gè)更好的實(shí)現(xiàn)。
3、原生類型(Primitive types):如果在Java中一切皆對(duì)象,那是多么完美啊,但他們偏偏不這樣設(shè)計(jì)。因而,這一點(diǎn)導(dǎo)致了一些問題,比如,不能把一個(gè)int放到集合(Collection)里,這個(gè)在Java5中通過自動(dòng)裝箱特性得到了解決(下面會(huì)提到)。它也造成了傳值與傳引用上的困擾,原生類型數(shù)據(jù)是通過值傳給方法的(復(fù)制一份拷貝,然后傳給函數(shù)),而真正的對(duì)象是通過傳遞(譯注:其實(shí)是復(fù)制對(duì)象地址再傳遞,因此應(yīng)該也是傳值方式,只是由于函數(shù)內(nèi)部可通過這個(gè)對(duì)象地址訪問對(duì)象,因此效果上類似傳引用)。
4、自動(dòng)裝箱(Autoboxing)和自動(dòng)拆箱(autounboxing):這個(gè)特性是為了解決因原生類型的存在所導(dǎo)致的問題,在Java5引入的。它允許靜默地轉(zhuǎn)換原生類型到相應(yīng)的對(duì)象,但這常常導(dǎo)致其它的問題。比如Integer可以為null,但int不能,因此這時(shí)JVM只能拋出一個(gè)難以調(diào)試的空指針異常(NullPointerException)。此外,它還可能導(dǎo)致其它奇怪的行為,就像下面的例子,我們就很難理解,變量test為什么是false:
Intger a = new Integer(1024);
Intger b = new Integer(1024);
boolean test = a b || a == b || a b;
5、缺少范型具類化:范型是Java5引入的一個(gè)很酷的特征,但是為了保持與舊版本Java的兼容性,導(dǎo)致缺失某些重要的特性,尤其是不能在運(yùn)行時(shí)反省范型的類型。例如,你有一個(gè)方法,接受List參數(shù),如果傳進(jìn)來一個(gè)List,你卻不能知道運(yùn)行里該范型的確切類型。同理,你也不能創(chuàng)建范型數(shù)組。這意味著,盡管下面的代碼看起來很自然,但卻不編譯不了:
List[] listsOfStrings = new List[3];
6、不可避免的范型警告:你有發(fā)現(xiàn)過自己陷入不可能去掉的關(guān)于范型的警告么?如果你像我一樣大量使用范型,我打賭你碰到過。事實(shí)上,是這個(gè)問題的規(guī)?;Y狀,讓他們認(rèn)為需要引入一個(gè)特定的注解(@SuppressWarnings("unchecked"))來處理這種情況,我覺得,范型應(yīng)該可能被設(shè)計(jì)的更好。
7、不能傳void給方法調(diào)用:我得承認(rèn),這種給方法傳遞void的需求,乍一看有些怪異。我喜歡DSL,當(dāng)我實(shí)現(xiàn)自己的DSL庫(lambdaj)的一個(gè)特定特性時(shí),我不得不需要一個(gè)方法聲明成這樣的簽名:void doSomething(Object parameter),這里為這個(gè)方法傳進(jìn)來的參數(shù)parameter,是另一個(gè)方法調(diào)用的結(jié)果,它唯一的目的,是注冊調(diào)用(的對(duì)象)自身,以可以在以后執(zhí)行它。讓我吃驚的是,即使println方法返回void,看上去也并沒有一個(gè)好理由,不允許我把代碼寫成這樣,:
doSomething(System.out.println("test"));
8、沒有原生的代理機(jī)制:代理是一種非常有效和應(yīng)用廣泛的模式,但Java提供的代理機(jī)制,只針對(duì)接口,而不是具體類。這是為什么象cblib這樣提供這種機(jī)制的庫,被如此多的主流框架,如Spring和Hibernate,采用的原因。此外,由于cglib通過運(yùn)行時(shí)創(chuàng)建被代理類的子類來實(shí)現(xiàn)的,因此這些種方式有一個(gè)眾所周知的限制——不能代理final類,比如String。
9、差勁的Switch...case語句:Java規(guī)定,switch...case只能選擇int和enum(Java5開始)。這一點(diǎn)如果跟更現(xiàn)代的語言如Scala相比,看起來簡直太弱了。
10、受檢查異常(Checked exception):類似原生類型,受檢查異常也已經(jīng)成為Java的一個(gè)罪孽之源。它迫使程序員必須做下面兩件極其糟糕討厭的事情中的一個(gè):讓你的代碼里充斥大量的、糟糕難讀的、容易出錯(cuò)的try...catch語句,而這樣做的最大意義,只是將捕獲的異常,包裝成運(yùn)行時(shí)異常,然后再重新拋出;或者是讓大量的拋出聲明子句污染你的API,讓接口缺少靈活性和可擴(kuò)展性。
真正的問題是,這里我提到的這幾大主要問題,唯一的解決辦法,是要做一個(gè)痛苦的決擇,定義一套新的語言規(guī)范,放下當(dāng)前版本的向后兼容性。我猜他們永遠(yuǎn)也不會(huì)這么做,雖然我相信,如果編寫一個(gè)能夠自動(dòng)轉(zhuǎn)換舊Java源碼的程序,讓它們與假設(shè)的新版本兼容,并不是很困難。最后,這就是我決定開始尋找一個(gè)更好的JVM兼容語言的原因。
Java8在2014年三月發(fā)布了。我們打算將Pondus的所有生產(chǎn)服務(wù)器升級(jí)到這一新版本。從那時(shí)起,我們將大部分代碼庫遷移到lambda表達(dá)式、數(shù)據(jù)流和新的日期API上。我們也會(huì)使用Nashorn來把我們的應(yīng)用中運(yùn)行時(shí)發(fā)生改變的部分變成動(dòng)態(tài)腳本。
除了lambda,最實(shí)用的特性是新的數(shù)據(jù)流API。集合操作在任何我見過的代碼庫中都隨處可見。而且對(duì)于那些集合操作,數(shù)據(jù)流是提升代碼可讀性的好方法。
但是一件關(guān)于數(shù)據(jù)流的事情十分令我困擾:數(shù)據(jù)流只提供了幾個(gè)終端操作,例如reduce和findFirst屬于直接操作,其它的只能通過collect來訪問。工具類Collctors提供了一些便利的收集器,例如toList、toSet、joining和groupingBy。
例如,下面的代碼對(duì)一個(gè)字符串集合進(jìn)行過濾,并創(chuàng)建新的列表:
stringCollection
.stream()
.filter(e - e.startsWith( "a"))
.collect(Collectors.toList());
在遷移了300k行代碼到數(shù)據(jù)流之后,我可以說,toList、toSet、和groupingBy是你的項(xiàng)目中最常用的終止操作。所以我不能理解為什么不把這些方法直接集成到Stream接口上面,這樣你就可以直接編寫:
stringCollection
.stream()
.filter(e - e.startsWith( "a"))
.toList();
這在開始看起來是個(gè)小缺陷,但是如果你需要一遍又一遍地編寫這些代碼,它會(huì)非常煩人。
有toArray()方法但是沒有toList(),所以我真心希望一些便利的收集器可以在Java9中這樣添加到Stream接口中。是吧,Brian??_?
注:Stream.js是瀏覽器上的Java 8 數(shù)據(jù)流API的JavaScript接口,并解決了上述問題。所有重要的終止操作都可以直接在流上訪問,十分方便。詳情請(qǐng)見API文檔。
無論如何,IntelliJ IDEA聲稱它是最智能的Java IDE。所以讓我們看看如何使用IDEA來解決這一問題。
使用 IntelliJ IDEA 來幫忙
IntelliJ IDEA自帶了一個(gè)便利的特性,叫做實(shí)時(shí)模板(Live Template)。如果你還不知道它是什么:實(shí)時(shí)模板是一些常用代碼段的快捷方式。例如,你鍵入sout并按下TAB鍵,IDEA就會(huì)插入代碼段System.out.println()。更多信息請(qǐng)見這里。
如何用實(shí)時(shí)模板來解決上述問題?實(shí)際上我們只需要為所有普遍使用的默認(rèn)數(shù)據(jù)流收集器創(chuàng)建我們自己的實(shí)時(shí)模板。例如,我們可以創(chuàng)建.toList縮寫的實(shí)時(shí)模板,來自動(dòng)插入適當(dāng)?shù)氖占?collect(Collectors.toList())。
下面是它在實(shí)際工作中的樣子:
讓我們看看如何自己構(gòu)建它。首先訪問設(shè)置(Settings)并在左側(cè)的菜單中選擇實(shí)時(shí)模板。你也可以使用對(duì)話框左上角的便利的輸入過濾。
下面我們可以通過右側(cè)的+圖標(biāo)創(chuàng)建一個(gè)新的組,叫做Stream。接下來我們向組中添加所有數(shù)據(jù)流相關(guān)的實(shí)時(shí)模板。我經(jīng)常使用默認(rèn)的收集器toList、toSet、groupingBy 和 join,所以我為每個(gè)這些方法都創(chuàng)建了新的實(shí)時(shí)模板。
這一步非常重要。在添加新的實(shí)時(shí)模板之后,你需要在對(duì)話框底部指定合適的上下文。你需要選擇Java → Other,然后定義縮寫、描述和實(shí)際的模板代碼。
// Abbreviation: .toList
.collect(Collectors.toList())
// Abbreviation: .toSet
.collect(Collectors.toSet())
// Abbreviation: .join
.collect(Collectors.joining( "$END$"))
// Abbreviation: .groupBy
.collect(Collectors.groupingBy(e - $END$))
特殊的變量$END$指定在使用模板之后的光標(biāo)位置,所以你可以直接在這個(gè)位置上打字,例如,定義連接分隔符。
提示:你應(yīng)該開啟"Add unambiguous imports on the fly"(自動(dòng)添加明確的導(dǎo)入)選項(xiàng),便于讓IDEA自動(dòng)添加 java.util.stream.Collectors的導(dǎo)入語句。選項(xiàng)在 Editor → General → Auto Import中。
讓我們在實(shí)際工作中看看這兩個(gè)模板:
連接分組
Intellij IDEA中的實(shí)時(shí)模板非常靈活且強(qiáng)大。你可以用它來極大提升代碼的生產(chǎn)力。你知道實(shí)時(shí)模板可以拯救生活的其它例子嗎?請(qǐng)讓我知道!
作者:布客飛龍 segmentfault.com/a/1190000006033999
在Java平臺(tái)上進(jìn)行多線程編程的缺陷
就其自身來說,并發(fā)編程是一種技術(shù),提供了操作的同時(shí)執(zhí)行,不論是在單一系統(tǒng)上還是分布在大量系統(tǒng)上。這類操作實(shí)際是一些指令順序,例如單獨(dú)某個(gè)頂級(jí)任務(wù)的子任務(wù),這類操作能夠并行執(zhí)行,或者是作為線程,或者是作為進(jìn)程。線程和進(jìn)程之間的本質(zhì)區(qū)別在于:進(jìn)程通常是獨(dú)立的(例如獨(dú)立的地址空間),所以只能通過系統(tǒng)提供的進(jìn)程間通信機(jī)制進(jìn)行交互,而線程通常共享單一進(jìn)程的狀態(tài)信息,能夠直接共享系統(tǒng)資源和內(nèi)存中的對(duì)象。
可以使用下面兩種方法之一,通過多個(gè)進(jìn)程來實(shí)現(xiàn)并發(fā)。第一種方法是在同一個(gè)處理器上運(yùn)行進(jìn)程,由操作系統(tǒng)處理進(jìn)程之間的上下文環(huán)境切換。(可以理解,這種切換要比同一進(jìn)程內(nèi)多線程之間的上下文環(huán)境切換更慢。)第二種方法是構(gòu)建大規(guī)模的并行和復(fù)雜的分布式系統(tǒng),在不同的物理處理器上運(yùn)行多個(gè)進(jìn)程。
從內(nèi)建支持的角度來說,Java 語言通過線程提供并發(fā)編程;每個(gè) JVM 都能支持許多線程同時(shí)執(zhí)行??梢杂靡韵聝煞N方法之一在 Java 語言中創(chuàng)建線程:
繼承 java.lang.Thread 類。在這種情況下,已經(jīng)重寫的子類的 run() 方法必須包含實(shí)現(xiàn)線程運(yùn)行時(shí)行為的代碼。要執(zhí)行這個(gè)代碼,需要實(shí)例化子類對(duì)象,然后調(diào)用對(duì)象的 start() 方法,這樣就可以在內(nèi)部執(zhí)行 run() 方法了。
創(chuàng)建 Runnable 接口的定制實(shí)現(xiàn)。這個(gè)接口只包含一個(gè) run() 方法,在這個(gè)方法中,要放置應(yīng)用程序代碼。要執(zhí)行這個(gè)代碼,需要實(shí)例化實(shí)現(xiàn)類的對(duì)象,然后在創(chuàng)建新 Thread 時(shí),把對(duì)象作為構(gòu)造函數(shù)的參數(shù)傳入。然后調(diào)用新創(chuàng)建的線程對(duì)象的 start() 方法,開始執(zhí)行控制的新線程。
線程安全性和同步
如果 Java 對(duì)象中的某個(gè)方法能夠安全地運(yùn)行在多線程環(huán)境中,那么就稱該方法是 線程安全的。要獲得這種安全性,必須有一種機(jī)制,通過該機(jī)制,運(yùn)行同一方法的多個(gè)線程就能夠同步其操作,這樣,在訪問相同的對(duì)象或代碼行時(shí),就會(huì)只允許一個(gè)線程被處理。這種同步要求線程使用叫作 信號(hào) 的對(duì)象彼此進(jìn)行溝通。
有一種類型的信號(hào)叫作 互斥信號(hào) 或 互斥體。顧名思義,這個(gè)信號(hào)對(duì)象的擁有權(quán)是互斥的,也就是說,在任意指定時(shí)間,只有一個(gè)線程能夠擁有互斥體。其他想獲得所有權(quán)的線程會(huì)被阻塞,它們必須等待,直到擁有互斥體的線程釋放互斥體。如果多個(gè)線程按順序排隊(duì)等候同一互斥體,那么在當(dāng)前擁有者釋放它的時(shí)候,只有一個(gè)等候線程能夠得到它;其他線程將繼續(xù)阻塞。
在 1970 年代初,C.A.R. Hoare 和其他人共同開發(fā)了一個(gè)叫作 監(jiān)視器 的概念。一個(gè) 監(jiān)視器 就是一個(gè)代碼主體,它的訪問受到互斥體的保護(hù)。任何想執(zhí)行這個(gè)代碼的線程,都必須在代碼塊頂部得到關(guān)聯(lián)的互斥體,然后在底部再釋放它。因?yàn)樵谥付〞r(shí)間只有一個(gè)線程能夠擁有互斥體,所以這就有效地保證了只有擁有它的線程才能執(zhí)行監(jiān)視器的代碼塊。(受保護(hù)的代碼不需要相鄰 —— 例如,Java 語言中的每個(gè)對(duì)象都有一個(gè)與之關(guān)聯(lián)的監(jiān)視器。)
任何想在 Java 語言中進(jìn)行線程編程的開發(fā)人員,都會(huì)立即把上面的內(nèi)容當(dāng)成 synchronized 關(guān)鍵字所帶來的效果??梢源_保包含在 synchronized 塊中的 Java 代碼在指定時(shí)間只被一個(gè)線程執(zhí)行。在內(nèi)部,可以由運(yùn)行時(shí)將 synchronized 關(guān)鍵字轉(zhuǎn)換成某一種情況:所有的競爭線程都試圖獲得與它們(指線程)正在操作的對(duì)象實(shí)例關(guān)聯(lián)的那個(gè)(惟一的一個(gè))互斥體。成功得到互斥體的線程將運(yùn)行代碼,然后在退出 synchronized 塊時(shí)釋放互斥體。
等候和通知
wait/notify 構(gòu)造在 Java 語言的線程間通信機(jī)制中也扮演了重要的角色?;镜南敕ㄊ牵阂粋€(gè)線程需要的某個(gè)條件可以由另外一個(gè)線程促成。這樣,條件的 wait 就可以得到滿足。一旦條件為真,那么引發(fā)條件的線程就會(huì) notify 等候線程蘇醒,并從中止的地方繼續(xù)進(jìn)行。
wait/notify 機(jī)制要比 synchronized 機(jī)制更難理解和判斷。要想判斷出使用 wait/notify 的方法的行為邏輯,就要求判斷出使用它的所有方法的邏輯。一次判斷一個(gè)方法,把該方法和其他方法隔離開,是對(duì)整體系統(tǒng)行為得出錯(cuò)誤結(jié)論的可靠方式。顯然,這樣做的復(fù)雜性會(huì)隨著要判斷的方法的數(shù)量增長而迅速提高。
線程狀態(tài)
我前面提到過,必須調(diào)用新創(chuàng)建的線程的 start() 方法來啟動(dòng)它的執(zhí)行。但是,僅僅是調(diào)用 start() 方法并不意味著線程會(huì)立即開始運(yùn)行。這個(gè)方法只是把線程的狀態(tài)從 new 變成 runnable。只有在操作系統(tǒng)真正安排線程執(zhí)行的時(shí)候,線程狀態(tài)才會(huì)變成 running (從 runnable)。
典型的操作系統(tǒng)支持兩種線程模型 —— 協(xié)作式和搶占式。在協(xié)作式 模型中,每個(gè)線程對(duì)于自己對(duì) CPU 的控制權(quán)要保留多久、什么時(shí)候放棄有最終意見。在這個(gè)模型中,因?yàn)榭赡艽嬖谀硞€(gè)無賴線程占住控制權(quán)不放,所以其他線程可能永遠(yuǎn)無法得到運(yùn)行。在 搶占式 模型中,操作系統(tǒng)本身采用基于時(shí)鐘“滴答”的計(jì)時(shí)器,基于這個(gè)計(jì)時(shí)器,操作系統(tǒng)可以強(qiáng)制把控制權(quán)從一個(gè)線程轉(zhuǎn)移到另外一個(gè)線程。在這種情況下,決定哪個(gè)線程會(huì)得到下一次控制權(quán)的調(diào)度策略就有可能基于各種指標(biāo),例如相對(duì)優(yōu)先級(jí)、某個(gè)線程已經(jīng)等待執(zhí)行的時(shí)間長短,等等。
如果出于某些原因,處在 running 狀態(tài)的線程需要等候某個(gè)資源(例如,等候設(shè)備的輸入數(shù)據(jù)到達(dá),或者等候某些條件已經(jīng)設(shè)定的通知),或者在試圖獲得互斥體的時(shí)候被阻塞,因此線程決定睡眠,那么這時(shí)它可以進(jìn)入 blocked 狀態(tài)。當(dāng)睡眠周期到期、預(yù)期輸入到達(dá),或者互斥體當(dāng)前的擁有者將其釋放并通知等候線程可以再次奪取互斥體時(shí),阻塞的線程重新進(jìn)入 runnable 狀態(tài)。
當(dāng)線程的 run() 方法完成時(shí)(或者正常返回,或者拋出 RuntimeException 這樣的未檢測到異常),線程將終止。這時(shí),線程的狀態(tài)是 dead。當(dāng)線程死亡時(shí),就不能通過再次調(diào)用它的 start() 方法來重新啟動(dòng)它,如果那么做,則會(huì)拋出 InvalidThreadStateException 異常。
四個(gè)常見缺陷
正如我已經(jīng)展示過的,Java 語言中的多線程編程是通過語言支持的大量精心設(shè)計(jì)的構(gòu)造實(shí)現(xiàn)的。另外,還設(shè)計(jì)了大量設(shè)計(jì)模式和指導(dǎo)原則,來幫助人們了解這種復(fù)雜性帶來的許多缺陷。除此之外,多線程編程會(huì)很容易地在不經(jīng)意間把細(xì)微的 bug 帶進(jìn)多線程代碼,而且更重要的是,這類問題分析和調(diào)試起來非常困難。接下來要介紹的是用 Java 語言進(jìn)行多線程編程時(shí)將會(huì)遇到(或者可能已經(jīng)遇到過)的最常見問題的一個(gè)列表。
爭用條件
據(jù)說 爭用條件 存在于這樣的系統(tǒng)中:多個(gè)線程之間存在對(duì)共享資源的競爭,而勝出者決定系統(tǒng)的行為。Allen Holub 在他撰寫的文章 “programming Java threads in the real world” 提供了一個(gè)帶有這樣 bug 的簡單的多線程程序示例。在沖突的訪問請(qǐng)求之間進(jìn)行不正確同步的另一個(gè)更可怕的后果是 數(shù)據(jù)崩潰,此時(shí),共享的數(shù)據(jù)結(jié)構(gòu)有一部分由一個(gè)線程更新,而另一部分由另一個(gè)線程更新。在這種情況下,系統(tǒng)的行為不是按照勝出線程的意圖進(jìn)行,系統(tǒng)根本不按照任何一個(gè)線程的意圖行動(dòng),所以兩個(gè)線程最后都將以失敗告終。
死鎖
死鎖 的情況是指:線程由于等候某種條件變成真(例如資源可以使用),但是它等候的條件無法變成真,因?yàn)槟軌蜃寳l件變成真的線程在等候第一個(gè)線程“做某件事”。這樣,兩個(gè)線程都在等候?qū)Ψ较炔扇〉谝徊?,所以都無法做事。
活動(dòng)鎖
活動(dòng)鎖 與 死鎖 不同,它是在線程實(shí)際工作的時(shí)候發(fā)生的,但這時(shí)還沒有完成工作。這通常是在兩個(gè)線程交叉工作的時(shí)候發(fā)生,所以第一個(gè)線程做的工作被另一個(gè)線程取消。一個(gè)簡單的示例就是:每個(gè)線程已經(jīng)擁有了一個(gè)對(duì)象,同時(shí)需要另外一個(gè)線程擁有的另外一個(gè)對(duì)象??梢韵胂襁@樣的情況:每個(gè)線程放下自己擁有的對(duì)象,撿起另外一個(gè)線程放下的對(duì)象。顯然,這兩個(gè)線程會(huì)永遠(yuǎn)都運(yùn)行在上鎖這一步操作上,結(jié)果是什么都做不成。(常見的真實(shí)示例就是,兩個(gè)人在狹窄的走廊相遇。每個(gè)人都禮貌地讓到另一邊讓對(duì)方先行,但卻在相同的時(shí)間都讓到同一邊了,所以兩個(gè)人還都沒法通過。這種情況會(huì)持續(xù)一些時(shí)間,然后兩個(gè)人都從這邊閃到那邊,結(jié)果還是一點(diǎn)進(jìn)展也沒有。)
資源耗盡
資源耗盡,又稱為 線程耗盡,是 Java 語言的 wait/notify 原語無法保證 live-ness 的后果。Java 強(qiáng)制這些方法要擁有它們等候或通知的對(duì)象的鎖。在某個(gè)線程上調(diào)用的 wait() 方法在開始等候之前必須釋放監(jiān)視器鎖,然后在從方法返回并獲得通知之后,必須再次重新獲得鎖。因此,Java 語言規(guī)范在鎖本身之外,還描述了一套與每個(gè)對(duì)象相關(guān)的 等候集(wait set)。一旦線程釋放了對(duì)象上的鎖(在 wait 的調(diào)用之后),線程就會(huì)放在這個(gè)等候集上。
多數(shù) JVM 實(shí)現(xiàn)把等候線程放在隊(duì)列中。所以,如果在通知發(fā)生的時(shí)候,還有其他線程在等候監(jiān)視器,那么就會(huì)把一個(gè)新線程放在隊(duì)列尾部,而它并不是下一個(gè)獲得鎖的線程。所以,等到被通知線程實(shí)際得到監(jiān)視器的時(shí)候,通知該線程的條件可能已經(jīng)不再為真,所以它不得不再次 wait。這種情況可能無限持續(xù)下去,從而造成運(yùn)算工作上浪費(fèi)(因?yàn)橐磸?fù)把該線程放入等候集和從中取出)和線程耗盡。
貪心哲學(xué)家的寓言
演示這種行為的原型示例是 Peter Welch 教授描述的“聰明人沒有雞肉”。在這個(gè)場景中考慮的系統(tǒng)是一所由五位哲學(xué)家、一位廚師和一個(gè)食堂組成的學(xué)院。所有的哲學(xué)家(除了一位)都要想想(在代碼示例中,考慮的時(shí)間是 3 秒)之后才去食堂取飯。而“貪心的”哲學(xué)家則不想把時(shí)間浪費(fèi)在思考上 —— 相反,他一次又一次地回到食堂,企圖拿到雞肉來吃。
廚師按照一批四份的定量準(zhǔn)備雞肉,每準(zhǔn)備好一批,就送到食堂。貪心的哲學(xué)家不斷地去廚房,但他總是錯(cuò)過食物!事情是這樣的:他第一次到的時(shí)候,時(shí)間太早,廚師還沒開火。因此貪心的哲學(xué)家只好干等著(通過 wait() 方法調(diào)用)。在開飯的時(shí)候(通過 notify() 方法調(diào)用),貪心的哲學(xué)家再一次回到食堂排隊(duì)等候。但是這次,在他前來等候的時(shí)候,他的四位同事已經(jīng)到了,所以他在食堂隊(duì)列中的位置在他們后面。他的同事們把廚房送來的一批四份雞肉全部拿走了,所以貪心的哲學(xué)家又要在一邊等著了。 可憐(也可能是公平的) ,他永遠(yuǎn)處在這個(gè)循環(huán)之外。
驗(yàn)證的問題
一般來說,很難按照普通的規(guī)范對(duì) Java 編程的多線程程序進(jìn)行驗(yàn)證。同樣,開發(fā)自動(dòng)化工具對(duì)于常見的并發(fā)問題(例如死鎖、活動(dòng)鎖和資源耗盡)進(jìn)行完整而簡單的分析也不太容易——特別是在任意 Java 程序中或者在缺乏并發(fā)的正式模型的時(shí)候。
更糟的是,并發(fā)性問題出了名的變化多端、難于跟蹤。每個(gè) Java 開發(fā)人員都曾經(jīng)聽說過(或者親自編寫過)這樣的 Java 程序:經(jīng)過嚴(yán)格分析,而且正常運(yùn)行了相當(dāng)一段時(shí)間,沒有表現(xiàn)出潛在的死鎖。然后突然有一天,問題發(fā)生了,結(jié)果弄得開發(fā)團(tuán)隊(duì)經(jīng)歷許多的不眠之夜來試圖發(fā)現(xiàn)并修補(bǔ)根本原因。
一方面,多線程 Java 程序容易發(fā)生的錯(cuò)誤非常不明顯,有可能在任意什么時(shí)候發(fā)生。另一方面,完全有可能這些 bug 在程序中從不出現(xiàn)。問題取決于一些不可知的因素。多線程程序的復(fù)雜本質(zhì),使得人們很難有效地對(duì)其進(jìn)行驗(yàn)證。沒有一套現(xiàn)成的規(guī)則可以找出多線程代碼中的這類問題,也無法確切地證明這些問題不存在,這些導(dǎo)致許多 Java 開發(fā)人員完全避開多線程應(yīng)用程序的設(shè)計(jì)和開發(fā),即使用并發(fā)和并行的方式對(duì)系統(tǒng)進(jìn)行建模會(huì)非常棒,他們也不使用多線程。
確實(shí)想進(jìn)行多線程編程的開發(fā)人員通常準(zhǔn)備好了以下一個(gè)或兩個(gè)解決方案(至少是一部分):
長時(shí)間艱苦地測試代碼,找出所有出現(xiàn)的并發(fā)性問題,誠心地希望到應(yīng)用程序真正運(yùn)行地時(shí)候已經(jīng)發(fā)現(xiàn)并修復(fù)了所有這類問題。
大量運(yùn)行設(shè)計(jì)模式和為多線程編程建立的指導(dǎo)原則。但是,這類指導(dǎo)原則只在整個(gè)系統(tǒng)都按照它們的規(guī)范設(shè)計(jì)的時(shí)候才有效,沒有設(shè)計(jì)規(guī)則能夠覆蓋所有類型的系統(tǒng)。
雖然知道的人不多,但是對(duì)于編寫(然后驗(yàn)證)正確的多線程應(yīng)用程序這一問題,還有第三個(gè)選項(xiàng)。使用稱為通信順序進(jìn)程( Communicating Sequential Processes,CSP)的精確的線程同步的數(shù)學(xué)理論,可以在設(shè)計(jì)時(shí)最好地處理死鎖和活動(dòng)鎖之類的問題。CSP 由 C.A.R. Hoare 與 20 世紀(jì) 70 年代后期設(shè)計(jì),CSP 提供了有效的方法,證明用它的構(gòu)造和工具構(gòu)建的系統(tǒng)可以免除并發(fā)的常見問題。
結(jié)束語
在這份面向 Java 程序員的 CSP 全面介紹中,我把重點(diǎn)放在克服多線程應(yīng)用程序開發(fā)常見問題的第一步上,即了解這些問題。我介紹了 Java 平臺(tái)上目前支持的多線程編程構(gòu)造,解釋了它們的起源,討論了這類程序可能會(huì)有的問題。我還解釋了用正式理論在任意的、大型的和復(fù)雜的應(yīng)用程序中清除這些問題(即競爭冒險(xiǎn)、死鎖、活動(dòng)鎖和資源耗盡)或者證明這些問題不存在的困難。
樓上說的有些道理,但是個(gè)人感覺并非全對(duì)
JAVA為了夸平臺(tái)不得不舍棄很多東西,對(duì)于目前的游戲開發(fā)只針對(duì)于Windows進(jìn)行大跨步的發(fā)展,JAVA想要在這方面趕超C++等屬于微軟的語言難上加難。
原因很多,例如游戲開發(fā)中大部分的引擎是C++語言開發(fā)的,原因是為了更快速的速度而大量使用的底層API函數(shù)去操作顯卡,而JAVA調(diào)用API現(xiàn)在來講不是不可以,但始終不會(huì)有很好的效率,就像你用自己的東西只管用而你的鄰居要用肯定還要問你借一樣~甚至還要問你怎么用~
語言目前存在瓶頸不代表JAVA不會(huì)進(jìn)步,加油吧,JAVA的明天是美好的~