早早就聽說過開發方向的筆試面試都是以算法和數據結構這些基礎為主,我自恃著那么一丁點項目經驗,一直沒放在心上。
連日下來的筆試徹底印證了師兄們的話,筆試基本不過。最可惜的是淘寶,筆試中發揮不錯終于能進一面,一開始聊家常聊框架聊開源技術還聊得不錯,突然
間,連續問了三個問題:
1.多線程訪問hashtable和hashmap有什么不一樣?我只答出線程安全不一樣,具體怎么不一樣就有一句每一句了(回來google一
下,這種java基礎還真TM簡單,枉稱精通java了)
2.平衡二叉樹查找算法的復雜度?再次雷響,隨便蒙了個歸并排序的復雜度給他。
3.對于100W條數據的排序和查找有什么效率高的方式?完了,結結巴巴的說一通,最后自認不會……
原本以為技術面會比筆試好過的,回學校的路上我淚流滿面啊我。這些都不是RP問題了,鐵了心惡補數據結構……
Java線
程安全同步解決方案
1、 問題描述:
如果一個資源或對象可能被多個線程同時訪問,它就是一個共享資源;例如
類的成員變量,包括類變量和實例變量,再比如對一個文件進行寫操作等。一般情況下,對共享資源的訪問需要考慮線程安全的問題。
如果一個對象的完整生命周期只在一個線程內,則不需要考慮線程安全,例
如一個局部變量。下面為一個示例代碼:
- public class C1 {
- public static java.text.SimpleDateFormat
sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
- //其他代碼
-
}
假如在一個JSP中這樣的去調用:
- <a.jsp>:
- <%
- Java.util.Date date =
C1.sdf.parse(“2003-4-15”);
-
%>
則這樣的代碼不是線程安全的。因為java.text.SimpleDateFormat
不是線程安全的,a.jsp中的代碼將會有若干個線程同時執行,而都訪問的是同一個線程不安全的對象,這樣就不是一個線程安全的代碼。正確的寫法應該如
下:
- <a.jsp>:
- <%
- java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat("yyyy-MM-dd");
- Java.util.Date date =
sdf.parse(“2003-4-15”);
-
%>
2、 原因分析:
此時,sdf對象從創建到銷毀都位于一個方法中,相當于一個局部變量,不是一個共享資源,因此則沒有線程不安全的問題。
3、 解決方法或過程:
1) 如果對象是immutable,則是線程安全的,例如:String,可以放心使用。
2) 如果對象是線程安全的,則放心使用
3) 有
條件線程安全,對于Vector和Hashtable一般情況下是線程安全的,但是對于某些特殊情況,需要通過額外的synchronized保證線程安
全。
4) 使用synchronized關鍵字;
對于上例中可以改寫jsp代碼,在sdf上進行同步,而不需要每次創建一個新的對象來保證線程安全,代碼如下:
- <%
- synchronized(C1.sdf){
- Java.util.Date date =
C1.sdf.parse(“2003-4-15”);
- }
-
%>
這種寫法是在一個對象級別上進行同步,也就是說任何時候,對于這個對象,最多只能有一個線程在執行同步方法。
另外一種寫法是在Class級別上進行同步,寫法如下:
- public class C1 {
- public static java.text.SimpleDateFormat
sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
- public void method(){
- synchronized(C1.class){
- //synchronized code
- }
- }
-
}
這種寫法表示無論C1有多少個實例,在任何一個時間點,最多只能有一個線程和一個實例進入同步塊中。這種同步會比較大的影響性能。
5) 有些對象不能在多線程間共享,則只能在方法內部使用,或者只在一個線程內部使用。
synchronized詳解
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()――這樣,調用端得到的就是對象副本的引用了。