JAVA-Java基礎知識總結_第1頁
JAVA-Java基礎知識總結_第2頁
JAVA-Java基礎知識總結_第3頁
JAVA-Java基礎知識總結_第4頁
JAVA-Java基礎知識總結_第5頁
已閱讀5頁,還剩169頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1.JAVA的StringBuffer類

StringBuffer類和String一樣,也用來代表字符串,只是由于StringBuffer

的內部實現方式和String不同,所以StringBuffer在進行字符串處理時,不生

成新的對象,在內存使用上要優于String類。所以在實際使用時,

如果經常需要對一個字符串進行修改,例如插入、刪除等操作,使用

StringBuffer要更加適合一些。

在StringBuffer類中存在很多和String類一樣的方法,這些方法在功

能上和String類中的功能是完全一樣的。

但是有一個最顯著的區別在于,對于StringBuffer對象的每次修改都會改變對

象自身,這點是和String類最大的區別。

另外由于StringBuffer是線程安全的,關于線程的概念后續有專門的

章節進行介紹,所以在多線程程序中也可以很方便的進行使用,但是程序的

執行效率相對來說就要稍微慢一些。

1、StringBuffer對象的初始化

StringBuffer對象的初始化不像String類的初始化一樣,Java提供的有特殊的

語法,而通常情況下一般使用構造方法進行初始化。

例如:

StringBuffers=newStringBuffer();

這樣初始化出的StringBuffer對象是一個空的對象。

如果需要創建帶有內容的StringBuffer對象,則可以使用:

StringBuffers=newStringBuffer("abc’');

這樣初始化出的StringBuffer對象的內容就是字符串“abc”。

需要注意的是,StringBuffer和String屬于不同的類型,也不能直接進行強制

類型轉換,下面的代碼都是錯誤的:

StringBuffers="abc”;〃賦值類型不匹配

StringBuffers=(StringBuffer)^^abc^^;〃不存在繼承關系,無法進

行強轉

StringBuffer對象和String對象之間的互轉的代碼如下:

Strings="abc”;

StringBuffersbl=newStringBuffer(“123”);

StringBuffersb2=newStringBuffer(s);//String轉換為StringBuffer

Stringsi=sbl.toString();//StringBuffer轉換為String

2、StringBuffer的常用方法

StringBuffer類中的方法主要偏重于對于字符串的變化,例如追加、插入和刪

除等,這個也是StringBuffer和String類的主要區別。

a>append方法

publicStringBufferappend(boolear)b)

該方法的作用是追加內容到當前StringBuffer對象的末尾,類似于字符串的連

接。調用該方法以后,StringBuffer對象的內容也發生改變,例如:

StringBuffersb=newStringBuffer(''abc");

sb.append(true);

則對象sb的值將變成“abctrue”。

使用該方法進行字符串的連接,將比String更加節約內容,例如應用于數據

庫SQL語句的連接,例如:

StringBuffersb=newStringBuffer();

Stringuser="test”;

Stringpwd="123”;

sb.append(44select*fromuserinfowhereusername=")

.append(user)

.append^andpwd=")

.append(pwd);

這樣對象sb的值就是字符串“select*fromuserinfowhere

username=testandpwd=123n。

b>deleteCharAt方法

publicStringBufferdeleteCharAt(intindex)

該方法的作用是刪除指定位置的字符,然后將剩余的內容形成新的字符串。

例如:

StringBuffersb=newStringBufferC4Tesf,);

sb.deleteCharAt(l);

該代碼的作用刪除字符串對象sb中索引值為1的字符,也就是刪除第二個字

符,剩余的內容組成一個新的字符串。所以對象sb的值變為"Tst”。

還存在一個功能類似的delete方法:

publicStringBufferdelete(intstartjntend)

該方法的作用是刪除指定區間以內的所有字符,包含start,不包含end索引

值的區間。例如:

StringBuffersb=newStringBuffer(64TestString,,J;

sb.delete(1,4);

該代碼的作用是刪除索引值1(包括)到索引值4(不包括)之間的所有字符,剩余

的字符形成新的字符串。則對象sb的值是“TString)

c、insert方法

publicStringBufferinsert(intoffset,booleanb)

該方法的作用是在StringBuffer對象中插入內

容,然后形成新的字符串。例如:

StringBuffersb=new

StringBuffer("TestString");

sb.insert(4,false);

該示例代碼的作用是在對象sb的索引值4的位置插入false值,形成新的字符

串,則執行以后對象sb的值是"TestfalseString”。

d、reverse方法

publicStringBufferreverse()

該方法的作用是將StringBuffer對象中的內容反轉,然后形成新的字符串。例

如:

StringBuffersb=newStringBuffer("abc");

sb.reverse();

經過反轉以后,對象sb中的內容將變為"cba\

e^setCharAt方法

publicvoidsetCharAt(intindex,charch)

該方法的作用是修改對象中索引值為index位

置的字符為新的字符ch。例如:

StringBuffersb=new

StringBuffer(“abc”);

sb-setCharAtfl/D5);

則對象sb的值將變成“aDc”。

f、trimToSize方法

publicvoidtrimToSize()

該方法的作用是將StringBuffer對象的中存儲空間縮小到和字符串長度一樣的

長度,減少空間的浪費。

總之,在實際使用時,String和StringBuffer各有優勢和不足,可以

根據具體的使用環境,選擇對應的類型進行使用。

2.Java中replace。、replaceFirst()和replaceAII()區別

str.replace(str中被替換的,替換后的字符)

replace和replaceAII是JAVA中常用的替換字符的方法,它們的區別是:

1)replace的參數是char和CharSequence,即可以支持字符的替換,也支持字符串的替換

(CharSequence即字符串序列的意思,說白了也就是字符串);

2)replaceAII的參數是regex,即基于規則表達式的替換,比如,可以通過replaceAII("\\d",

“*")把?個字符串所有的數字字符都換成星號;

相同點是都是全部替換,即把源字符串中的某一字符或字符串全部換成指定的字符或字

符串,如果只想替換第一次出現的,可以使用replaceHrst。,這個方法也是基于規則表達式的

替換,但與replaceAII。不同的是,只替換第一次出現的字符串;

另外,如果replaceAII。和replaceFirst。所用的參數據不是基于規則表達式的,則與

replace。替換字符串的效果是一樣的,

即這兩者也支健待串的操作;

還有一點注意:

執行了替換操作后,源字符串的內容是沒有發生改變的(因為String類是final類型的不可

改寫,但可以把處理得到的結果賦值).

舉例如下:

Stringsrc=newString,'ab4332c43d");

System.out.println(src.replace(n3',,"f"));=>ab4f2c4fd.

System.out.println(src.replace(,3',,f'));=>ab4f2c4fd.

System.out.println(src.replaceAII("\\d",,,f,'));=>abffafcffd.

,,

System.out.println(src.replaceAII("a'J'f"));=>fb43fc23d.

System.out.println(src.replaceFirst(',\\d,"f',));=>abf32c43d

System.out.println(src.replaceFirst(',4",,'h"));=>abh32c43d.

如何將字符串中的“'“替換成“\\":

Stringmsgln;

StringmsgOut;

,,,w

msgOut=msgln.replaceAII('\\\\J"\\\\\\\\);

原因:

'在java中是一個轉義字符,所以需要用兩個代表一個。例如System.out.println("\\");

只打印出個

但是'''也是正則表達式中的轉義字符(replaceAII的參數就是正則表達式),需要用兩個代

表一個。所以:\\\\被java轉換成\\八\又被正則表達式轉換成\。

同樣

CODE:\\\\\\\\

Java:\\\\

Regex:\\

將字符串中的'/‘替換成'\'的幾種方式:

msgOut=msgln.replaceAII('7","\\\\");

msgOut=msgln.replace('7","\\");

3.window.location和window.open的區另

window.location=""跳轉后有后退功能

window.location.replace("")跳轉后沒有后退功

window.open("")要新的窗口打開鏈接

4.Class.forName的作用以及為什么要用它【轉】

Fbstedon2010-03-0310:24火之光閱讀(674)評論(0)編輯收藏.

Qass.forName(xxx.xx.xx)返回的是-,個類

首先你要明白在java里面任何class都要裝載在虛擬機上才能運行。這句話就是裝載類用的(和new

不一樣,要分清楚)。

至于什么時候用,你可以考慮一下這個問題,給你一個字符串變量,它代表一個類的包名和類名,你

怎么實例化它?只有你提到的這個方法了,不過要再加一點。

Aa=(A)Class.forName("pacage.A").newlnstance();

這和你

Aa=newA();

是一樣的效果。

關于補充的問題

答案是肯定的,jvm會執行靜態代碼段,你要記住一個概念,靜態代碼是和class綁定的,class裝載

成功就表示執行了你的靜態代碼了。而且以后不會再走這段靜態代碼了。

Qass.forName(xxx.xx.xx)返回的是-,個類

Qass.forName(xxx.xx.xx);的作用是要求JVM查找并加載指定的類,也就是說JVM會執行該類的靜態

代碼段

動態加載和創建Qass對象,比如想根據用戶輸入的字符串來創建對象

Stringstr=用戶輸入的字符串

Classt=Qass.forName(str);

t.newlnstance();

在初始化一個類,生成?個實例的時候,newlnstance。方法和new關鍵字除了一個是方法,一個是

關鍵字外,最主要有什么區別?它們的區別在于創建對象的方式不一樣,前者是使用類加載機制,后

者是創建一個新類。那么為什么會有兩種創建對象方式?這主要考慮到軟件的可伸縮、可擴展和可重

用等軟件設計思想。

Java中工廠模式經常使用newlnstance()方法來創建對象,因此從為什么要使用工廠模式上可以找到

具體答案。例如:

classc=Qass.forName("Example");

factory=(Examplelnterface)c.newlnstance();

其中Exampieinterface是Example的接口,可以寫成如下形式:

StringclassName="Example";

classc=Qass.forName(className);

factory=(Examplelnterface)c.newlnstance();

進一步可以寫成如下形式:

StringclassName=readfromXMlConfig;〃從xml配置文件中獲得字符串

classc=aass.forName(className);

factory=(Examplelnterface)c.newlnstance();

上面代碼已經不存在Example的類名稱,它的優點是,無論Example類怎么變化,上述代碼不變,

甚至可以更換Example的兄弟類Example2,Examples,Example4......,只要他們繼承

Exampleinterface就可以。

從JVM的角度看,我們使用關鍵字new創建一個類的時候,這個類可以沒有被加載。但是使用

newlnstance()方法的時候,就必須保證:1、這個類已經加載;2、這個類已經連接了。而完成上面

兩個步驟的正是Qass的靜態方法forName()所完成的,這個靜態方法調用了啟動類加教器,即加載

javaAPI的那個加載器。

現在可以看出,newlnstance()實際上是把new這個方式分解為兩步,即首先調用Class加載方法加

載某個類,然后實例化。這樣分步的好處是顯而易見的。我們可以在調用class的靜態加載方法

forName時獲得更好的靈活性,提供給了一種降耦的手段。

最后用最簡單的描述來區分new關鍵字和newlnstance。方法的區別:

newlnstance:弱類型。低效率。只能調用無參構造。

new:強類型。相對高效。能調用任何public構造。

5.Hibernate包作用詳解【轉】

Fastedon2009-12-2311:32火之光閱讀(168)評論(0)編輯收藏.

Hibernate?共包括了23個jar包,令人眼花繚亂。本文將詳細講解Hibernate每個jar包的作用,便于

你在應用中根據自己的需要進行取舍。

下載Hibernate,例如2.0.3穩定版本,解壓縮,可以看到一個hibernate2.jar和lib目錄下有22個jar

包:

?hibernate2.jar:

Hibernate的庫,沒有什么可說的,必須使用的jar包

?cglib-asm.jar:

CGLIB庫,Hibernate用它來實現P0字節碼的動態生成,非常核心的庫,必須使用的jar包

?dom4j.jar:

dom4j是一個Java的XMLAPI,類似于jdom,用來讀寫XML文件的。dom4j是一個非常非常優秀的JavaXML

API,具有性能優異、功能強大和極端易用使用的特點,同時它也是一個開放源代碼的軟件,可以在

SourceForge上找到它。在IBMdeveloperWorks上面可以找到一篇文章,對主流的JavaXMLAPI進行的

性能、功能和易用性的評測,dom4j無論在那個方面都是非常出色的。我早在將近兩年之前就開始使用

doin4j,直到現在。如今你可以看到越來越多的Java軟件都在使用doiMj來讀寫XML,特別值得一提的是

連Sun的JAXM也在用dom4jo這是必須使用的jar包,Hibernate用它來讀寫配置文件。

?odmg.jar:

0DMG是?個ORM的規范,Hibernate實現了ODMG規范,這是?個核心的庫,必須使用的jar包。

?commons-collections,jar:

ApacheCommons包中的一個,包含了一些Apache開發的集合類,功能比java,uti1.*強大。必須使用的

jar包。

?commons-beanutils.jar:

ApacheCommons包中的一個,包含了一些Bean_L具類類。必須使用的jar包。

?commons-lang,jar:

ApacheCommons包中的一個,包含了一些數據類型工具類,是java.lang.*的擴展。必須使用的jar包。

?commons-logging,jar:

ApacheCommons包中的一個,包含了日志功能,必須使用的jar包。這個包本身包含了一個SimpleLogger,

但是功能很弱。在運行的時候它會先在CLASSPATH找log4j,如果有,就使用log4j,如果沒有,就找JDKL4

帶的java.util,logging,如果也找不到就用SimpleLoggerocommons-logging,jar的出現是一個歷史的

的遺留的遺憾,當初Apache極力游說Sun把log4j加入JDK1.4,然而JDKL4項目小組已經接近發布JDK1.4

產品的時間了,因此拒絕了Apache的要求,使用自己的java.util,logging,這個包的功能比log4j差的

很遠,性能也一般。后來Apache就開發出來了commons-logging,jar用來兼容兩個logger。因此用

commons-logging,jar寫的log程序,底層的Logger是可以切換的,你可以選擇log4j,java.util,logging

或者它自帶的SimpleLogger0不過我仍然強烈建議使用log4j,因為log4j性能很高,log輸出信息時間

幾乎等于System,oul,而處理一條log平均只需要5us。你可以在Hibernate的src目錄卜找到Hibernate

已經為你準備好了的log4j的配置文件,你只需要到Apache網站去下載log4j就可以了。

commons-logging.jar也是必須的jar包。

使用Hibernate必須的jar包就是以上的這兒個,剩下的都是可選的。

?ant.jar:

Ant編譯工具的jar包,用來編譯Hibernate源代碼的。如果你不準備修改和編譯Hibernate源代碼,那

么就沒有什么用,可選的jar包

?optional,jar:

Ant的一個輔助包。

?c3p0.jar:

C3Po是?個數據庫連接池,Hibernate可以配置為使用C3Po連接池。如果你準備用這個連接池,就需要這

個jar包。

?proxool.jar:

也是一個連接池,同上。

?commons-pool,jar,commons-dbcp.jar:

DBCP數據庫連接池,Apache的Jakarta組織開發的,Tomcal4的連接池也是DBCP。

實際上Hibernate自己也實現了一個非常非常簡單的數據庫連接池,加上上面3個,你實際上可以在

Hibernate上選擇4種不同的數據庫連接池,選擇哪?個看個人的偏好,不過DBCP可能更通用?些。另外

強調一點,如果在EJB中使用Hibernate,一定要用AppServer的連接池,不要用以上4種連接池,否則

容器管理事務不起作用。

?connector,jar:

JCA規范,如果你在AppServer上把Hibernate配置為Connector的話,就需要這個jar。不過實際上一

般AppServer肯定會帶上這個包,所以實際上是多余的包。

?jaas.jar:

JAAS是用來進行權限驗證的,已經包含在JDK1.4里面了。所以實際上是多余的包。

?jcs.jar:

如果你準備在Hibernate中使用JCS的話,那么必須包括它,否則就不用。

?jdbc2_0-stdext.jar:

JDBC2.0的擴展包,一般來說數據庫連接池會用上它。不過AppServer都會帶匕所以也是多余的。

?jta.jar:

JTA規范,當Hibernate使用JTA的時候需要,不過AppServer都會帶上,所以也是多余的。

?junit.jar:

Junit包,當你運行Hibernate自帶的測試代碼的時候需要,否則就不用。

?xalan.jar,xerces.jar,xml-apis.jar:

Xerces是XML解析器,Xalan是格式化器,xml-apis實際上是JAXP。一般AppServer都會帶上,JDKL4

也包含了解析器,不過不是Xerces,是Crimson,效率比較差,不過Hibemate用XML只不過是讀取配置

文件,性能沒什么緊要的,所以也是多余的。

6.JAVA多線程的問題以及處理【轉】

Restedon2009-12-0317:43火之光閱讀(603)評論(1)編輯收藏.

124多線程問題及處理

多線程編程為程序開發帶來了很多的方便,但是也帶來了一些問題,

這些問題是在程序開發過程中必須進行處理的問題。

這些問題的核心是,如果多個線程同時訪問一個資源,例如變量、

文件等,時如何保證訪問安全的問題。在多線程編程中,這種會被多個線程

同時訪問的資源叫做臨界資源。

下面通過一個簡單的示例,演示多個線程訪問臨界資源時產生的問

題。在該示例中,啟動了兩個線程類DataThread的對象,該線程每隔200毫

秒輸出一次變量n的值,并將n的值減少1。變量n的值存儲在模擬臨界資源

的Data類中,該示例的核心是兩個線程類都使用同一個Data類的對象,這

樣Data類的這個對象就是一個臨界資源了。示例代碼如下:

packagesynl;

/**

*模擬臨界資源的類

*/

publicclassData{

publicintn;

publicData(){

n=60;

)

}

packagesynl;

/**

*測試多線程訪問時的問題

*/

publicclassTestMulThreadl{

publicstaticvoidmain(String[]args){

Datadata=newData();

DataThreaddl=newDataThread(dataJ線程:T);

DataThreadd2=newDataThread(dataJ線程2");

)

)

packagesynl;

/**

*訪問數據的線程

*/

publicclassDataThreadextendsThread{

Datadata;

Stringname;

publicDataThread(Datadata,Stringname){

this.data=data;

=name;

start();

)

publicvoidrun(){

try(

for(inti=0;i<10;i++){

System.out.printin(name+

":"+data.n);

data.n-;

Thread.sleep(200);

)

}catch(Exceptione){}

)

)

在運行時,因為不同情況下該程序的運行結果會出現不同,該程序

的一種執行結果為:

線程1:60

線程2:60

線程2:58

線程1:58

線程2:56

線程1:56

線程2:54

線程1:54

線程2:52

線程1:52

線程2:50

線程1:50

線程2:48

線程1:48

線程2:47

線程1:46

線程2:44

線程1:44

線程2:42

線程1:42

從執行結果來看,第一次都輸出60是可以理解的,因為線程在執行

時首先輸出變量的值,這個時候變量n的值還是初始值60,而后續的輸出就

比較麻煩了,在開始的時候兩個變量保持一致的輸出,而不是依次輸出n的

每個值的內容,而到將要結束時,線程2輸出47這個中間數值。

出現這種結果的原因很簡單:線程1改變了變量n的值以后,還沒有

來得及輸出,這個變量n的值就被線程2給改變了,所以在輸出時看的輸出都

是跳躍的,偶爾出現了連續。

出現這個問題也比較容易接受,因為最基本的多線程程序,系統只

保證線程同時執行,至于哪個先執行,哪個后執行,或者執行中會出現一個

線程執行到一半,就把CPU的執行權交給了另外一個線程,這樣線程的執行

順序是隨機的,不受控制的。所以會出現上面的結果。

這種結果在很多實際應用中是不能被接受的,例如銀行的應用,兩

個人同時取一個賬戶的存款,一個使用存折、一個使用卡,這樣訪問賬戶的

金額就會出現問題。或者是售票系統中,如果也這樣就出現有人買到相同座

位的票,而有些座位的票卻未售出。

在多線程編程中,這個是一個典型的臨界資源問題,解決這個問題

最基本,最簡單的思路就是使用同步關鍵字synchronized□

synchronized關鍵字是一個修飾符,可以修飾方法或代碼塊,其的

作用就是,對于同一個對象(不是一個類的不同對象),當多個線程都同時調

用該方法或代碼塊時,必須依次執行,也就是說,如果兩個或兩個以上的線

程同時執行該段代碼時,如果一個線程已經開始執行該段代碼,則另外一個

線程必須等待這個線程執行完這段代碼才能開始執行。就和在銀行的柜臺辦

理業務一樣,營業員就是這個對象,每個顧客就好比線程,當一個顧客開始

辦理時,其它顧客都必須等待,及口寸這個正在辦理的顧客在辦理過程中接了

一個電話(類比于這個線程釋放了占用CPU的時間,而處于阻塞狀態),其它

線程也只能等待。

使用synchronized關鍵字修改以后的上面的代碼為:

packagesyn2;

/**

*模擬臨界資源的類

*/

publicclassData2{

publicintn;

publicData2(){

n=60;

)

publicsynchronizedvoidaction(Stringname){

System.out.println(name++n);

n-;

)

)

packagesyn2;

/**

*測試多線程訪問時的問題

*/

publicclassTestMulThread2{

publicstaticvoidmain(String[]args){

Data2data=newData2();

Data2Threaddl=newData2Thread(data,"線程

1");

Data2Threadd2=newData2Thread(data,"線程

2");

)

)

packagesyn2;

/**

*訪問數據的線程

*/

publicclassData2ThreadextendsThread{

Data2data;

Stringname;

publicData2Thread(Data2data,Stringname){

this.data=data;

=name;

start();

)

publicvoidrun(){

try(

for(inti=O;i<10;i++){

data.action(name);

Thread.sleep(200);

)

}catch(Exceptione){}

)

}

該示例代碼的執行結果會出現不同,一種執行結果為:

線程1:60

線程2:59

線程2:58

線程1:57

線程2:56

線程1:55

線程2:54

線程1:53

線程2:52

線程1:51

線程2:50

線程1:49

線程1:48

線程2:47

線程2:46

線程1:45

線程2:44

線程1:43

線程2:42

線程1:41

在該示例中,將打印變量n的代碼和變量n變化的代碼組成一個專

門的方法action,并且使用修飾符synchronized修改該方法,也就是說對于一

個Data2的對象,無論多少個線程同時調用action方法時,只有一個線程完全

執行完該方法以后,別的線程才能夠執行該方法。這就相當于一個線程執行

到該對象的synchronized方法時,就為這個對象加上了一把鎖,鎖住了這個

對象,別的線程在調用該方法時,發現了這把鎖以后就繼續等待下去了。

如果這個例子還不能幫助你理解如何解決多線程的問題,那么下面再來看

一個更加實際的例子——衛生間問題。

例如火車上車廂的衛生間,為了簡單,這里只模擬一個衛生間,這

個衛生間會被多個人同時使用,在實際使用時,當一個人進入衛生間時則會

把衛生間鎖上,等出來時打開門,下一個人進去把門鎖上,如果有一個人在

衛生間內部則別人的人發現門是鎖的則只能在外面等待。從編程的角度來看,

這里的每個人都可以看作是一個線程對象,而這個衛生間對象由于被多個線

程訪問,則就是臨界資源,在一個線程實際使用時,使用synchronized關鍵

將臨界資源鎖定,當結束時,釋放鎖定。實現的代碼如下:

packagesyn3;

/**

*測試類

7

publicclassTestHuman{

publicstaticvoidmain(String[]args){

Toilett=newToilet();〃衛生間對象

Humanhl=newHuman("l",t);

Humanh2=newHuman("2",t);

Humanh3=newHuman("3",t);

}

)

packagesyn3;

/**

*人線程類,演示互斥

*/

publicclassHumanextendsThread{

Toilett;

Stringname;

publicHuman(Stringnamejoilett){

=name;

this.t=t;

start。;〃啟動線程

)

publicvoidrun(){

〃進入衛生間

t.enter(name);

)

}

packagesyn3;

/**

*衛生間,互斥的演示

*/

publicclassToilet{

publicsynchronizedvoidenter(Stringname){

System.out.println(name+"已進入!");

try(

Thread.sleep(2000);

}catch(Exceptione){}

System.out.println(name+"離開!");

)

)

該示例的執行結果為,不同次數下執行結果會有所不同:

1已進入!

1離開!

3已進入!

3離開!

2已進入!

2離開!

在該示例代碼中,Toilet類表示衛生間類,Human類模擬人,是該示

例中的線程類,TestHuman類是測試類,用于啟動線程。在TestHuman中,

首先創建一個Toilet類型的對象t,并將該對象傳遞到后續創建的線程對象中,

這樣后續的線程對象就使用同一個Toilet對象,該對象就成為了臨界資源。

下面創建了三個Human類型的線程對象,每個線程具有自己的名稱name參

數,模擬3個線程,在每個線程對象中,只是調用對象t中的enter方法,模

擬進入衛生間的動作,在enter方法中,在進入時輸出調用該方法的線程進入,

然后延遲2秒,輸出該線程離開,然后后續的一個線程進入,直到三個線程都

完成enter方法則程序結束。

在該示例中,同一個Toilet類的對象t的enter方法由于具有

synchronized修飾符修飾,則在多個線程同時調用該方法時,如果一個線程進

入到enter方法內部,則為對象t上鎖,直到enter方法結束以后釋放對該對

象的鎖定,通過這種方式實現無論多少個Human類型的線程,對于同一個對

象3任何時候只能有一個線程執行enter方法,這就是解決多線程問題的第

一種思路——互斥的解決原理。

12.4.2同步

使用互斥解決多線程問題是一種簡單有效的解決辦法,但是由于該

方法比較簡單,所以只能解決一些基本的問題,對于復雜的問題就無法解決

To

解決多線程問題的另外一種思路是同步。同步是另外一種解決問題

的思路,結合前面衛生間的示例,互斥方式解決多線程的原理是,當一個人

進入到衛生間內部時,別的人只能在外部時刻等待,這樣就相當于別的人雖

然沒有事情做,但是還是要占用別的人的時間,浪費系統的執行資源。而同

步解決問題的原理是,如果一個人進入到衛生間內部時,則別的人可以去睡

覺,不占用系統資源,而當這個人從衛生間出來以后,把這個睡覺的人叫醒,

則它就可以使用臨界資源了。所以使用同步的思路解決多線程問題更加有

效,更加節約系統的資源。

在常見的多線程問題解決中,同步問題的典型示例是“生產者-消費

者”模型,也就是生產者線程只負責生產,消費者線程只負責消費,在消費

者發現無內容可消費時則睡覺。下面舉一個比較實際的例子一一生活費問題。

生活費問題是這樣的:學生每月都需要生活費,家長一次預存一段

時間的生活費,家長和學生使用統一的一個帳號,在學生每次取帳號中一部

分錢,直到帳號中沒錢時通知家長存錢,而家長看到帳戶還有錢則不存錢,

直到帳戶沒錢時才存錢。在這個例子中,這個帳號被學生和家長兩個線程同

時訪問,則帳號就是臨界資源,兩個線程是同時執行的,當每個線程發現不

符合要求時則等待,并釋放分配給自己的CPU執行時間,也就是不占用系統

資源。實現該示例的代碼為:

packagesyn4;

/**

*測試類

*/

publicclassTestAccount{

publicstaticvoidmain(String[]args){

Accouta=newAccout();

StudentThreads=newStudentThread(a);

GenearchThreadg=newGenearchThread(a);

)

)

packagesyn4;

*模擬學生線程

*/

publicclassStudentThreadextendsThread{

Accouta;

publicStudentThread(Accouta){

this.a=a;

start();

)

publicvoidrun(){

try(

while(true){

Thread.sleep(2000);

a.getMoney();〃取錢

)

}catch(Exceptione){}

)

)

packagesyn4;

*家長線程

*/

publicclassGenearchThreadextendsThread{

Accouta;

publicGenearchThread(Accouta){

this.a=a;

start();

)

publicvoidrun(){

try(

while(true){

Thread.sleep(12000);

a.saveMoney。;〃存錢

}

}catch(Exceptione){}

)

}

packagesyn4;

/**

*銀行賬戶

*/

publicclassAccout{

intmoney=0;

*

*取錢

*如果賬戶沒錢則等待,否則取出所有錢提醒存錢

*/

publicsynchronizedvoidgetMoney(){

System.out.println("準備取錢!");

try(

iffmoney==0){

wait。;〃等待

)

〃取所有錢

System.out.printin("乘lj余:"+money);

money-=50;

〃提醒存錢

notify();

}catch(Exceptione){}

}

/**

*存錢

*如果有錢則等待,否則存入200提醒取錢

*/

publicsynchronizedvoidsaveMoney(){

System.out.println("準備存錢!");

try(

if(money!=0){

wait();〃等待

)

〃取所有錢

money=200;

System.out.printin("存入:"+money);

〃提醒存錢

notify();

}catch(Exceptione){}

)

)

該程序的一部分執行結果為:

準備取錢!

準備存錢!

存入:200

剩余:200

準備取錢!

剩余:150

準備取錢!

剩余:100

準備取錢!

剩余:50

準備取錢!

準備存錢!

存入:200

剩余:200

準備取錢!

剩余:150

準備取錢!

剩余:100

準備取錢!

剩余:50

準備取錢!

在該示例代碼中,TestAccount類是測試類,主要實現創建帳戶

Account類的對象,以及啟動學生線程StudentThread和啟動家長線程

GenearchThreado在StudentThread線程中,執行的功能是每隔2秒中取一次

錢,每次取50元。在GenearchThread線程中,執行的功能是每隔12秒存一次

錢,每次存200。這樣存款和取款之間不僅時間間隔存在差異,而且數量上也

會出現交叉。而該示例中,最核心的代碼是Account類的實現。

在Account類中,實現了同步控制功能,在該類中包含一個關鍵的

屬性money,該屬性的作用是存儲帳戶金額。在介紹該類的實現前,首先介

紹一下兩個同步方法——wait和notify方法的使用,這兩個方法都是Object

類中的方法,也就是說每個類都包含這兩個方法,換句話說,就是Java天生

就支持同步處理。這兩個方法都只能在synchronized修飾的方法或語句塊內

部采用被調用。其中wait方法的作用是使調用該方法的線程休眠,也就是使

該線程退出CPU的等待隊列,處于冬眠狀態,不執行動作,也不占用CPU排

隊的時間,notify方法的作用是喚醒一個任意該對象的線程,該線程當前處于

休眠狀態,至于喚醒的具體是那個則不保證。在Account類中,被StudentThread

調用的getMoney方法的功能是判斷當前金額是否是0,如果是則使

StudentThread線程處于休眠狀態,如果金額不是0,則取出50元,同時喚醒

使用該帳戶對象的其它一個線程,而被GenearchThread線程調用的

saveMoney方法的功能是判斷當前是否不為0,如果是則使GenearchThread

線程處于休眠狀態,如果金額是0,則存入200元,同時喚醒使用該帳戶對象

的其它一個線程。

如果還是不清楚,那就結合前面的程序執行結果來解釋一下程序執

行的過程:在程序開始執行時,學生線程和家長線程都啟動起來,所以輸出

“準備取錢”和“準備存錢”,然后學生線程按照該線程run方法的邏輯執

行,先延遲2秒,然后調用帳戶對象a中的getMoney方法,但是由于初始情

況下帳戶對象a中的money數值為0,所以學生線程就休眠了。在學生線程

執行的同時,家長線程也按照該線程的run方法的邏輯執行,先延遲12秒,

然后調用帳戶對象a中的saveMoney方法,由于帳戶a對象中的money為零,

條件不成立,所以執行存入200元,同時喚醒線程,由于使用對象a的線程現

在只有學生線程,所以學生線程被喚醒,開始執行邏輯,取出50元,然后喚

醒線程,由于當前沒有線程處于休眠狀態,所以沒有線程被喚醒。同時家長

線程繼續執行,先延遲12秒,這個時候學生線程執行了4次,耗時4X2秒=8秒,

就取光了帳戶中的錢,接著由于帳戶為0則學生線程又休眠了,一直到家長線

程延遲12秒結束以后,判斷帳戶為0,又存入了200元,程序繼續執行下去。

在解決多線程問題是,互斥和同步都是解決問題的思路,如果需要

形象的比較這兩種方式的區別的話,就看一下下面的示例。一個比較忙的老

總,桌子上有2部電話,在一部處于通話狀態時,另一部響了,老總拿其這部

電話說我在接電話,你等一下,而沒有掛電話,這種處理的方式就是互斥。

而如果老總拿其另一部電話說,我在接電話,等會我打給你,然后掛了電話,

這種處理的方式就是同步。兩者相比,互斥明顯占用系統資源(浪費電話費,

浪費別人的時間),而同步則是一種更加好的解決問題的思路。

12.4.3死鎖

多線程編程在實際的網絡程序開發中,在客戶端程序實現中使用的

比較簡單,但是在服務器端程序實現中卻不僅是大量使用,而且會出現比客

戶端更多的問題。

另外一個容易在服務器端出現的多線程問題是——死鎖。死鎖指兩

個或兩個以上的線程為了使用某個臨界資源而無限制的等待下去。還是以前

面衛生間的例子來說明死鎖,例如兩個人都同時到達衛生間,而且兩個人都

比較禮貌,第一個人和第二個人說:你先吧,第二個人和第一個人說:你先

吧。這兩個人就這樣一直在互相禮讓,誰也不進入,這種現象就是死鎖。這

里的兩個人就好比是線程,而衛生間在這里就是臨界資源,而由于這兩個線

程在一直謙讓,誰也不使用臨界資源。

死鎖不僅使程序無法達到預期實現的功能,而且浪費系統的資源,

所以在服務器端程序中危害比較大,在實際的服務器端程序開發中,需要注

意避免死鎖。

而死鎖的檢測比較麻煩,而且不一定每次都出現,這就需要在測試

服務器端程序時,有足夠的耐心,仔細觀察程序執行時的性能檢測,如果發

現執行的性能顯著降低,則很可能是發生了死鎖,然后再具體的查找死鎖出

現的原因,并解決死鎖的問題。

死鎖出現的最本質原因還是邏輯處理不夠嚴謹,在考慮時不是很周

全,所以一般需要修改程序邏輯才能夠很好的解決死鎖。

12.4.4線程優先級

在日常生活中,例如火車售票窗口等經常可以看到“XXX優先”,

那么多線程編程中每個線程是否也可以設置優先級呢?

在多線程編程中,支持為每個線程設置優先級。優先級高的線程在

排隊執行時會獲得更多的CPU執行時間,得到更快的響應。在實際程序中,

可以根據邏輯的需要,將需要得到及時處理的線程設置成較高的優先級,而

把對時間要求不高的線程設置成比較低的優先級。

在Thread類中,總計規定了三個優先級,分別為:

?MAX_PRIORITY------最高優先級

?NORM_PRIORITY——普通優先級,也是默認優先級

?MINPRIORITY-?最低優先級

在前面創建的線程對象中,由于沒有設置線程的優先級,則線程默認的優

先級是NORM_PRIORITY,在實際使用時,也可以根據需要使用Thread類中的

setPriority方法設置線程的優先級,該方法的聲明為:

publicfinalvoidsetPriority(intnewPriority)

假設t是一個初始化過的線程對象,需要設置t的優先級為最高,則實現

的代碼為:

t.setPriority(Thread.MAX_PRIORITY);

這樣,在該線程執行時將獲得更多的執行機會,也就是優先執行。如果由

于安全等原因,不允許設置線程的優先級,則會拋出SecurityException異常。

下面使用一個簡單的輸出數字的線程演示線程優先級的使用,實現的示例

代碼如下:

packagepriority;

/**

*測試線程優先級

*/

publicclassTestPriority{

publicstaticvoidmain(String[]args){

PrintNumberThreadpl=new

PrintNumberThread("高優先級)

PrintNumberThreadp2=new

PrintNumberThread("普通優先級”);

PrintNumberThreadp3=new

PrintNumberThread("低優先級”);

pl.setPriority(Thread.MAX_PRIORITY);

p2.setPriority(Thread.NORM_PRIORITY);

p3.setPriority(Thread.MIN_PRIORITY);

pl.start();

p2.start();

p3.start();

)

}

packagepriority;

/**

*輸出數字的線程

*/

publicclassPrintNumberThreadextendsThread{

Stringname;

publicPrintNumberThread(Stringname){

=name;

)

publicvoidrun(){

try(

for(inti=0;i<10;i++){

System.out.printin(name+

)

}catch(Exceptione){}

)

)

程序的一種執行結果為:

高優先級:o

高優先級:1

高優先級:2

普通優先級:0

高優先級:3

普通優先級:1

高優先級:4

普通優先級:2

高優先級:5

高優先級:6

高優先級:7

高優先級:8

高優先級:9

普通優先級:3

普通優先級:4

普通優先級:5

普通優先級:6

普通優先級:7

普通優先級:

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論