經常看到介紹 ArrayList 和HashMap是異步,Vector和HashTable是同步,這裏同步是線程安全的,異步不是線程安全的,舉例說明:
當創建一個Vector對象時候,
Vector ve=new Vector();
ve.add("1");
當在多線程程序中,第一個線程調用修改對象ve的時候,就爲其上了鎖,其他線程只有等待。
當創建一個ArrayList對象時候,
ArrayList list=new ArrayList();
list.add("1");
當在多線程程序中,第一個線程調用修改對象list的時候,沒有爲其上鎖,其他線程訪問時就會報錯。
eg:list.remove("1"),然後再由其他線程訪問list對象的1時就會報錯。
經常看到介紹 ArrayList 和HashMap是異步,Vector和HashTable是同步,這裏同步是線程安全的,異步不是線程安全的,舉例說明:
當創建一個Vector對象時候,
Vector ve=new Vector();
ve.add("1");
當在多線程程序中,第一個線程調用修改對象ve的時候,就爲其上了鎖,其他線程只有等待。
當創建一個ArrayList對象時候,
ArrayList list=new ArrayList();
list.add("1");
當在多線程程序中,第一個線程調用修改對象list的時候,沒有爲其上鎖,其他線程訪問時就會報錯。
eg:list.remove("1"),然後再由其他線程訪問list對象的1時就會報錯。
java 異步與同步應用
所謂異步輸入輸出機制,是指在進行輸入輸出處理時,不必等到輸入輸出處理完畢才返回。所以異步的同義語是非阻塞(None Blocking)。
網上有很多網友用很通俗的比喻 把同步和異步講解的很透徹 轉過來
舉個例子:普通B/S模式(同步)AJAX技術(異步)
同步:提交請求->等待服務器處理->處理完畢返回 這個期間客戶端瀏覽器不能幹任何事
異步: 請求通過事件觸發->服務器處理(這是瀏覽器仍然可以作其他事情)->處理完畢
同步就是你叫我去吃飯,我聽到了就和你去吃飯;如果沒有聽到,你就不停的叫,直到我告訴你聽到了,才一起去吃飯。
異步就是你叫我,然後自己去吃飯,我得到消息後可能立即走,也可能等到下班纔去吃飯。
所以,要我請你吃飯就用同步的方法,要請我吃飯就用異步的方法,這樣你可以省錢。
以通訊爲例
同步:發送一個請求,等待返回,然後再發送下一個請求
異步:發送一個請求,不等待返回,隨時可以再發送下一個請求
併發:同時發送多個請求
下面再轉一段關於java異步應用的文章
用異步輸入輸出流編寫Socket進程通信程序
在Merlin中加入了用於實現異步輸入輸出機制的應用程序接口包:java.nio(新的輸入輸出包,定義了很多基本類型緩衝(Buffer)), java.nio.channels(通道及選擇器等,用於異步輸入輸出),java.nio.charset(字符的編碼解碼)。通道 (Channel)首先在選擇器(Selector)中註冊自己感興趣的事件,當相應的事件發生時,選擇器便通過選擇鍵(SelectionKey)通知已註冊的通道。然後通道將需要處理的信息,通過緩衝(Buffer)打包,編碼/解碼,完成輸入輸出控制。
通道介紹:
這裏主要介紹ServerSocketChannel和 SocketChannel.它們都是可選擇的(selectable)通道,分別可以工作在同步和異步兩種方式下(注意,這裏的可選擇不是指可以選擇兩種工作方式,而是指可以有選擇的註冊自己感興趣的事件)。可以用channel.configureBlocking(Boolean )來設置其工作方式。與以前版本的API相比較,ServerSocketChannel就相當於ServerSocket (ServerSocketChannel封裝了ServerSocket),而SocketChannel就相當於Socket (SocketChannel封裝了Socket)。當通道工作在同步方式時,編程方法與以前的基本相似,這裏主要介紹異步工作方式。
所謂異步輸入輸出機制,是指在進行輸入輸出處理時,不必等到輸入輸出處理完畢才返回。所以異步的同義語是非阻塞(None Blocking)。在服務器端,ServerSocketChannel通過靜態函數open()返回一個實例serverChl。然後該通道調用 serverChl.socket().bind()綁定到服務器某端口,並調用register(Selector sel, SelectionKey.OP_ACCEPT)註冊OP_ACCEPT事件到一個選擇器中(ServerSocketChannel只可以註冊 OP_ACCEPT事件)。當有客戶請求連接時,選擇器就會通知該通道有客戶連接請求,就可以進行相應的輸入輸出控制了;在客戶端,clientChl實例註冊自己感興趣的事件後(可以是OP_CONNECT,OP_READ,OP_WRITE的組合),調用clientChl.connect (InetSocketAddress )連接服務器然後進行相應處理。注意,這裏的連接是異步的,即會立即返回而繼續執行後面的代碼。
選擇器和選擇鍵介紹:
選擇器(Selector)的作用是:將通道感興趣的事件放入隊列中,而不是馬上提交給應用程序,等已註冊的通道自己來請求處理這些事件。換句話說,就是選擇器將會隨時報告已經準備好了的通道,而且是按照先進先出的順序。那麼,選擇器是通過什麼來報告的呢?選擇鍵(SelectionKey)。選擇鍵的作用就是表明哪個通道已經做好了準備,準備幹什麼。你也許馬上會想到,那一定是已註冊的通道感興趣的事件。不錯,例如對於服務器端serverChl來說,可以調用key.isAcceptable()來通知serverChl有客戶端連接請求。相應的函數還有: SelectionKey.isReadable(),SelectionKey.isWritable()。一般的,在一個循環中輪詢感興趣的事件(具體可參照下面的代碼)。如果選擇器中尚無通道已註冊事件發生,調用Selector.select()將阻塞,直到有事件發生爲止。另外,可以調用 selectNow()或者select(long timeout)。前者立即返回,沒有事件時返回0值;後者等待timeout時間後返回。一個選擇器最多可以同時被63個通道一起註冊使用。
應用實例:
下面是用異步輸入輸出機制實現的客戶/服務器實例程序�D�D程序清單1(限於篇幅,只給出了服務器端實現,讀者可以參照着實現客戶端代碼):
1. public class NBlockingServer {
2. int port = 8000;
3. int BUFFERSIZE = 1024;
4. Selector selector = null;
5. ServerSocketChannel serverChannel = null;
6. HashMap clientChannelMap = null;//用來存放每一個客戶連接對應的套接字和通道
7.
8. public NBlockingServer( int port ) {
9. this.clientChannelMap = new HashMap();
10. this.port = port;
11. }
12.
13. public void initialize() throws IOException {
14. //初始化,分別實例化一個選擇器,一個服務器端可選擇通道
15. this.selector = Selector.open();
16. this.serverChannel = ServerSocketChannel.open();
17. this.serverChannel.configureBlocking(false);
18. InetAddress localhost = InetAddress.getLocalHost();
19. InetSocketAddress isa = new InetSocketAddress(localhost, this.port );
20. this.serverChannel.socket().bind(isa);//將該套接字綁定到服務器某一可用端口
21. }
22. //結束時釋放資源
23. public void finalize() throws IOException {
24. this.serverChannel.close();
25. this.selector.close();
26. }
27. //將讀入字節緩衝的信息解碼
28. public String decode( ByteBuffer byteBuffer ) throws
29. CharacterCodingException {
30. Charset charset = Charset.forName( "ISO-8859-1" );
31. CharsetDecoder decoder = charset.newDecoder();
32. CharBuffer charBuffer = decoder.decode( byteBuffer );
33. String result = charBuffer.toString();
34. return result;
35. }
36. //監聽端口,當通道準備好時進行相應操作
37. public void portListening() throws IOException, InterruptedException {
38. //服務器端通道註冊OP_ACCEPT事件
39. SelectionKey acceptKey =this.serverChannel.register( this.selector,
40. SelectionKey.OP_ACCEPT );
41. //當有已註冊的事件發生時,select()返回值將大於0
42. while (acceptKey.selector().select() > 0 ) {
43. System.out.println("event happened");
44. //取得所有已經準備好的所有選擇鍵
45. Set readyKeys = this.selector.selectedKeys();
46. //使用迭代器對選擇鍵進行輪詢
47. Iterator i = readyKeys.iterator();
48. while (i.hasNext()) {
49. SelectionKey key = (SelectionKey)i.next();
50. i.remove();//刪除當前將要處理的選擇鍵
51. if ( key.isAcceptable() ) {//如果是有客戶端連接請求
52. System.out.println("more client connect in!");
53. ServerSocketChannel nextReady =
54. (ServerSocketChannel)key.channel();
55. //獲取客戶端套接字
56. Socket s = nextReady.accept();
57. //設置對應的通道爲異步方式並註冊感興趣事件
58. s.getChannel().configureBlocking( false );
59. SelectionKey readWriteKey =
60. s.getChannel().register( this.selector,
61. SelectionKey.OP_READ|SelectionKey.OP_WRITE );
62. //將註冊的事件與該套接字聯繫起來
63. readWriteKey.attach( s );
64. //將當前建立連接的客戶端套接字及對應的通道存放在哈希表//clientChannelMap中
65. this.clientChannelMap.put( s, new
66. ClientChInstance( s.getChannel() ) );
67. }
68. else if ( key.isReadable() ) {//如果是通道讀準備好事件
69. System.out.println("Readable");
70. //取得選擇鍵對應的通道和套接字
71. SelectableChannel nextReady =
72. (SelectableChannel) key.channel();
73. Socket socket = (Socket) key.attachment();
74. //處理該事件,處理方法已封裝在類ClientChInstance中
75. this.readFromChannel( socket.getChannel(),
76. (ClientChInstance)
77. this.clientChannelMap.get( socket ) );
78. }
79. else if ( key.isWritable() ) {//如果是通道寫準備好事件
80. System.out.println("writeable");
81. //取得套接字後處理,方法同上
82. Socket socket = (Socket) key.attachment();
83. SocketChannel channel = (SocketChannel)
84. socket.getChannel();
85. this.writeToChannel( channel,"This is from server!");
86. }
87. }
88. }
89. }
90. //對通道的寫操作
91. public void writeToChannel( SocketChannel channel, String message )
92. throws IOException {
93. ByteBuffer buf = ByteBuffer.wrap( message.getBytes() );
94. int nbytes = channel.write( buf );
95. }
96. //對通道的讀操作
97. public void readFromChannel( SocketChannel channel, ClientChInstance clientInstance )
98. throws IOException, InterruptedException {
99. ByteBuffer byteBuffer = ByteBuffer.allocate( BUFFERSIZE );
100. int nbytes = channel.read( byteBuffer );
101. byteBuffer.flip();
102. String result = this.decode( byteBuffer );
103. //當客戶端發出”@exit”退出命令時,關閉其通道
104. if ( result.indexOf( "@exit" ) >= 0 ) {
105. channel.close();
106. }
107. else {
108. clientInstance.append( result.toString() );
109. //讀入一行完畢,執行相應操作
110. if ( result.indexOf( ""n" ) >= 0 ){
111. System.out.println("client input"+result);
112. clientInstance.execute();
113. }
114. }
115. }
116. //該類封裝了怎樣對客戶端的通道進行操作,具體實現可以通過重載execute()方法
117. public class ClientChInstance {
118. SocketChannel channel;
119. StringBuffer buffer=new StringBuffer();
120. public ClientChInstance( SocketChannel channel ) {
121. this.channel = channel;
122. }
123. public void execute() throws IOException {
124. String message = "This is response after reading from channel!";
125. writeToChannel( this.channel, message );
126. buffer = new StringBuffer();
127. }
128. //當一行沒有結束時,將當前字竄置於緩衝尾
129. public void append( String values ) {
130. buffer.append( values );
131. }
132. }
133.
134.
135. //主程序
136. public static void main( String[] args ) {
137. NBlockingServer nbServer = new NBlockingServer(8000);
138. try {
139. nbServer.initialize();
140. } catch ( Exception e ) {
141. e.printStackTrace();
142. System.exit( -1 );
143. }
144. try {
145. nbServer.portListening();
146. }
147. catch ( Exception e ) {
148. e.printStackTrace();
149. }
150. }
151. }
152.
小結:
從以上程序段可以看出,服務器端沒有引入多餘線程就完成了多客戶的客戶/服務器模式。該程序中使用了回調模式(CALLBACK),細心的讀者應該早就看出來了。需要注意的是,請不要將原來的輸入輸出包與新加入的輸入輸出包混用,因爲出於一些原因的考慮,這兩個包並不兼容。即使用通道時請使用緩衝完成輸入輸出控制。該程序在Windows2000,J2SE1.4下,用telnet測試成功
synchronized的一個簡單例子
public class TextThread
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO 自動生成方法存根
TxtThread tt = new TxtThread();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
}
}
class TxtThread implements Runnable
{
int num = 100;
String str = new String();
public void run()
{
while (true)
{
synchronized(str)
{
if (num>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
e.getMessage();
}
System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
}
}
}
}
}
上面的例子中爲了製造一個時間差,也就是出錯的機會,使用了Thread.sleep(10)
Java對多線程的支持與同步機制深受大家的喜愛,似乎看起來使用了synchronized關鍵字就可以輕鬆地解決多線程共享數據同步問題。到底如何?――還得對synchronized關鍵字的作用進行深入瞭解纔可定論。
總的說來,synchronized關鍵字可以作爲函數的修飾符,也可作爲函數內的語句,也就是平時說的同步方法和同步語句塊。如果再細的分類,synchronized可作用於instance變量、object reference(對象引用)、static函數和class literals(類名稱字面常量)身上。
在進一步闡述之前,我們需要明確幾點:
A.無論synchronized關鍵字加在方法上還是對象上,它取得的鎖都是對象,而不是把一段代碼或函數當作鎖――而且同步方法很可能還會被其他線程的對象訪問。
B.每個對象只有一個鎖(lock)與之相關聯。
C.實現同步是要很大的系統開銷作爲代價的,甚至可能造成死鎖,所以儘量避免無謂的同步控制。
接着來討論synchronized用到不同地方對代碼產生的影響:
假設P1、P2是同一個類的不同對象,這個類中定義了以下幾種情況的同步塊或同步方法,P1、P2就都可以調用它們。
1. 把synchronized當作函數修飾符時,示例代碼如下:
Public synchronized void methodAAA()
{
//….
}
這也就是同步方法,那這時synchronized鎖定的是哪個對象呢?它鎖定的是調用這個同步方法對象。也就是說,當一個對象P1在不同的線程中執行這個同步方法時,它們之間會形成互斥,達到同步的效果。但是這個對象所屬的Class所產生的另一對象P2卻可以任意調用這個被加了synchronized關鍵字的方法。
上邊的示例代碼等同於如下代碼:
public void methodAAA()
{
synchronized (this) // (1)
{
//…..
}
}
(1)處的this指的是什麼呢?它指的就是調用這個方法的對象,如P1。可見同步方法實質是將synchronized作用於object reference。――那個拿到了P1對象鎖的線程,纔可以調用P1的同步方法,而對P2而言,P1這個鎖與它毫不相干,程序也可能在這種情形下襬脫同步機制的控制,造成數據混亂:(
2.同步塊,示例代碼如下:
public void method3(SomeObject so)
{
synchronized(so)
{
//…..
}
}
這時,鎖就是so這個對象,誰拿到這個鎖誰就可以運行它所控制的那段代碼。當有一個明確的對象作爲鎖時,就可以這樣寫程序,但當沒有明確的對象作爲鎖,只是想讓一段代碼同步時,可以創建一個特殊的instance變量(它得是一個對象)來充當鎖:
class Foo implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance變量
Public void methodA()
{
synchronized(lock) { //… }
}
//…..
}
注:零長度的byte數組對象創建起來將比任何對象都經濟――查看編譯後的字節碼:生成零長度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。
3.將synchronized作用於static 函數,示例代碼如下:
Class Foo
{
public synchronized static void methodAAA() // 同步的static 函數
{
//….
}
public void methodBBB()
{
synchronized(Foo.class) // class literal(類名稱字面常量)
}
}
代碼中的methodBBB()方法是把class literal作爲鎖的情況,它和同步的static函數產生的效果是一樣的,取得的鎖很特別,是當前調用這個方法的對象所屬的類(Class,而不再是由這個Class產生的某個具體對象了)。
記得在《Effective Java》一書中看到過將 Foo.class和 P1.getClass()用於作同步鎖還不一樣,不能用P1.getClass()來達到鎖這個Class的目的。P1指的是由Foo類產生的對象。
可以推斷:如果一個類中定義了一個synchronized的static函數A,也定義了一個synchronized 的instance函數B,那麼這個類的同一對象Obj在多線程中分別訪問A和B兩個方法時,不會構成同步,因爲它們的鎖都不一樣。A方法的鎖是Obj這個對象,而B的鎖是Obj所屬的那個Class。
小結如下:
搞清楚synchronized鎖定的是哪個對象,就能幫助我們設計更安全的多線程程序。
還有一些技巧可以讓我們對共享資源的同步訪問更加安全:
1. 定義private 的instance變量+它的 get方法,而不要定義public/protected的instance變量。如果將變量定義爲public,對象在外界可以繞過同步方法的控制而直接取得它,並改動它。這也是JavaBean的標準實現方式之一。
2. 如果instance變量是一個對象,如數組或ArrayList什麼的,那上述方法仍然不安全,因爲當外界對象通過get方法拿到這個instance對象的引用後,又將其指向另一個對象,那麼這個private變量也就變了,豈不是很危險。 這個時候就需要將get方法也加上synchronized同步,並且,只返回這個private對象的clone()――這樣,調用端得到的就是對象副本的引用了。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/teedry/archive/2009/06/19/4281618.aspx