程式碼被多執行緒同時的呼叫是安全的稱之為線程安全。如果一段程式碼是線程安全的,那麼它就不包含競態條件。競態條件只是發生在多執行緒更新共享資源的時候。因此知道Java執行緒什麼時間執行共享的資源是重要的。
局部變數
局部變數儲存在每一個執行緒自己的堆疊中。那意味著局部變數在執行緒之間不會共享。那也意味著所有的局部原始變數是線程安全的。這裡有一個例子:
public void someMethod(){ long threadSafeInt = 0; threadSafeInt++; }
#局部物件參考
對於物件的局部參考是有點不同的。這個引用本身是不會共享的。然而,這個物件的參考是不能儲存在每一個執行緒的棧中。所有的物件都儲存在共享的堆中。
如果局部建立的物件沒有逸出它所建立的方法,它是執行緒安全的。事實上你也可以把他傳遞給其他的方法,並且只要傳遞的這個物件的它的方法對於其他的線程是不可用的。
這裡有一個例子:
public void someMethod(){ LocalObject localObject = new LocalObject(); localObject.callMethod(); method2(localObject); } public void method2(LocalObject localObject){ localObject.setValue("value"); }
#在這個範例中的LocalObject實例不能從這個方法中傳回,也不能傳遞給其他的物件。那個從someMethod方法的外部是可以存取的。每個執行someMethod方法的執行緒將會建立它自己的LocalObject實例,並且把它指派給localObject參考。因此這種使用是線程安全的。
事實上,這整個someMethod方法是線程安全的。甚至如果這個localObject實例被當作參數傳遞給相同類別的其他方法,或是其他的類,它是執行緒安全的使用。
當然,唯一的異常,如果這些方法中的一個使用LocalObject作為參數調用,在某種程度上儲存LocalObject實例,允許來自其他執行緒的存取。
物件成員變數
物件成員變數(欄位)連同物件一起儲存在堆疊上。因此,如果兩個執行緒呼叫相同物件實例的一個方法,以及這個方法更新物件成員變量,整個方法就不是執行緒安全的。這裡有一個例子:
public class NotThreadSafe{ StringBuilder builder = new StringBuilder(); public add(String text){ this.builder.append(text); } }
如果兩個執行緒在相同的NotThreadSafe實例上同時的呼叫add方法,它就會導致競態條件。例如:
NotThreadSafe sharedInstance = new NotThreadSafe(); new Thread(new MyRunnable(sharedInstance)).start(); new Thread(new MyRunnable(sharedInstance)).start(); public class MyRunnable implements Runnable{ NotThreadSafe instance = null; public MyRunnable(NotThreadSafe instance){ this.instance = instance; } public void run(){ this.instance.add("some text"); } }
#注意這兩個MyRunnable實例是怎麼樣分享相同的NotThreadSafe實例的。因此,當他們呼叫add方法的時候會導致競態條件。
然而,如果兩個執行緒在不同的實例上同時的呼叫add方法,它就不會導致競態條件。這裡是來自於之前的例子,稍微有些修改:
new Thread(new MyRunnable(new NotThreadSafe())).start(); new Thread(new MyRunnable(new NotThreadSafe())).start();
現在每一個執行緒都有一個他們自己的NotThreadSafe實例了,以至於他們呼叫這個add方法不會互相干擾。這個代碼不帶有競態條件。以至於,甚至一個物件不是線程安全的,它仍然可以用這種方式使用不會導致競態條件。
執行緒控制溢出規則
當嘗試決定是否你的程式碼對某個資源存取是執行緒安全的時候,你可以使用下列規則:
If a resource is created, used and disposed within the control of the same thread, and never escapes the control of this thread, the use of that resource is thread safe.
資源可以是任何共享的資源,像一個對象,數組,文件,資料庫連接,套接字等等。在Java中,你不能總是明確的銷毀對象,以至於「銷毀的」意味著物件遺失或空的引用。
甚至如果一個物件的使用是線程安全的,如果那個物件指向了一個共享的資源像一個檔案或資料庫,你的應用程式作為一個整體可能就不是線程安全的了。例如,如果線程1和線程2各自創建自己的資料庫連接,連接1和連接2,每一個他們自己的連接使用是線程安全的。但是這個連接指向的資料庫的使用可能不是線程安全的。例如,如果兩個執行緒執行像這樣的程式碼:
check if record X exists if not, insert record X
#如果兩個執行緒同時執行這個,以及他們正在檢查的這個記錄X放生在相同的記錄上,這裡就有一個風險,他們都會最終已插入而結束。如下所示:
Thread 1 checks if record X exists. Result = no Thread 2 checks if record X exists. Result = no Thread 1 inserts record X Thread 2 inserts record X
這個也會發生在操作在檔案或其他的共享資源的執行緒上。因此去區分被一個執行緒控制的物件是否是一個資源,或者只是這個資源(像資料庫連結)的引用是重要的。
以上就是Java 執行緒全和共享資源的內容,更多相關內容請關注PHP中文網(www.php.cn)!