本文目錄一覽:
java是線程安全的嗎
線程安全就是多線程訪問時,採用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可使用。不會出現數據不一致或者數據污染。(Vector,HashTab;le)
線程不安全就是不提供數據訪問保護,有可能出現多個線程先後更改數據造成所得到的數據是臟數據。(ArrayList,LinkedList,HashMap等)
概念:
如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是線程安全的。或者說:一個類或者程序所提供的介面對於線程來說是原子操作或者多個線程之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。線程安全問題都是由全局變數及靜態變數引起的。
若每個線程中對全局變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全局變數是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。
例子:
比如一個 ArrayList 類,在添加一個元素的時候,它可能會有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在單線程運行的情況下,如果 Size = 0,添加一個元素後,此元素在位置 0,而且 Size=1;
而如果是在多線程情況下,比如有兩個線程,線程 A 先將元素存放在位置 0。但是此時 CPU 調度線程A暫停,線程 B
得到運行的機會。線程B也向此 ArrayList 添加元素,因為此時 Size 仍然等於 0
(注意哦,我們假設的是添加一個元素是要兩個步驟哦,而線程A僅僅完成了步驟1),所以線程B也將元素存放在位置0。然後線程A和線程B都繼續運行,都增加
Size 的值。
那好,我們來看看 ArrayList 的情況,元素實際上只有一個,存放在位置 0,而 Size 卻等於 2。這就是「線程不安全」了。
安全性:
線程安全性不是一個非真即假的命題。 Vector 的方法都是同步的,因為java會有相應的機制是同一時刻只有一個線程對這個變數操作。並且
Vector 明確地設計為在多線程環境中工作。但是它的線程安全性是有限制的,即在某些方法之間有狀態依賴(類似地,如果在迭代過程中 Vector
被其他線程修改,那麼由 Vector.iterator() 返回的
iterator會拋出ConcurrentModifiicationException)。
對於 Java 類中常見的線程安全性級別,沒有一種分類系統可被廣泛接受,不過重要的是在編寫類時盡量記錄下它們的線程安全行為。
Bloch 給出了描述五類線程安全性的分類方法:不可變、線程安全、有條件線程安全、線程兼容和線程對立。只要明確地記錄下線程安全特性,那麼您是否使用這種系統都沒關係。這種系統有其局限性
– 各類之間的界線不是百分之百地明確,而且有些情況它沒照顧到 –
但是這套系統是一個很好的起點。這種分類系統的核心是調用者是否可以或者必須用外部同步包圍操作(或者一系列操作)。下面幾節分別描述了線程安全性的這五種類別。
不可變
不可變的對象一定是線程安全的,並且永遠也不需要額外的同步。因為一個不可變的對象只要構建正確,其外部可見狀態永遠也不會改變,永遠也不會看到它處於不一致的狀態。Java
類庫中大多數基本數值類如 Integer 、 String 和 BigInteger 都是不可變的。
需要注意的是,對於Integer,該類不提供add方法,加法是使用+來直接操作。而+操作是不具線程安全的。這是提供原子操作類AtomicInteger的原。
線程安全
線程安全的對象具有在上面「線程安全」一節中描述的屬性 –
由類的規格說明所規定的約束在對象被多個線程訪問時仍然有效,不管運行時環境如何排線程都不需要任何額外的同步。這種線程安全性保證是很嚴格的 –
許多類,如 Hashtable 或者 Vector 都不能滿足這種嚴格的定義。
有條件的線程安全
有條件的線程安全類對於單獨的操作可以是線程安全的,但是某些操作序列可能需要外部同步。條件線程安全的最常見的例子是遍歷由 Hashtable
或者 Vector 或者返回的迭代器 – 由這些類返回的 fail-fast
迭代器假定在迭代器進行遍歷的時候底層集合不會有變化。為了保證其他線程不會在遍歷的時候改變集合,進行迭代的線程應該確保它是獨佔性地訪問集合以實現遍歷的完整性。通常,獨佔性的訪問是由對鎖的同步保證的
– 並且類的文檔應該說明是哪個鎖(通常是對象的內部監視器(intrinsic monitor))。
如果對一個有條件線程安全類進行記錄,那麼您應該不僅要記錄它是有條件線程安全的,而且還要記錄必須防止哪些操作序列的並發訪問。用戶可以合理地假設其他操作序列不需要任何額外的同步。
線程兼容
線程兼容類不是線程安全的,但是可以通過正確使用同步而在並發環境中安全地使用。這可能意味著用一個 synchronized
塊包圍每一個方法調用,或者創建一個包裝器對象,其中每一個方法都是同步的(就像 Collections.synchronizedList()
一樣)。也可能意味著用 synchronized
塊包圍某些操作序列。為了最大程度地利用線程兼容類,如果所有調用都使用同一個塊,那麼就不應該要求調用者對該塊同步。這樣做會使線程兼容的對象作為變數實例包含在其他線程安全的對象中,從而可以利用其所有者對象的同步。
許多常見的類是線程兼容的,如集合類 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 類 Connection 和 ResultSet 。
線程對立
線程對立類是那些不管是否調用了外部同步都不能在並發使用時安全地呈現的類。線程對立很少見,當類修改靜態數據,而靜態數據會影響在其他線程中執行的其他類的行為,這時通常會出現線程對立。線程對立類的一個例子是調用 System.setOut() 的類。
幾個關於java線程安全的老問題。
1.錯,只有當多個線程訪問並修改同一個帶屬性的對象時才產生線程安全問題
2.在多線程共享的情況下成立
3.仍然會有線程安全問題,除非不使用任何共享屬性
4.沒看出問題
5.對的
java如何實現靜態變數多線程安全問題
public class A { public void method01(){ //dosomething } public void method02(){ //dosomething } //public void method03(){ public static void method03(){ // 靜態方法里才可以有今天變數 static Map map = new TreeMap(); static String x=「」; // 如果你的 method4,5,6是非同步的, 5,6得到的值就是不確定的, // 有可能是4之前的值,也有可能是4的賦值 method4(){ 這裡面會給map和x賦值 } method5(){ 這裡面會用到map和x } method6(){ 這裡面會用到map和x } }}
在多線程中使用靜態方法是否有線程安全問題
總的結論:java是線程安全的,即對任何方法(包括靜態方法)都可以不考慮線程衝突,但有一個前提,就是不能存在全局變數。如果存在全局變數,則需要使用同步機制。
如下通過一組對比例子從頭講解:
在多線程中使用靜態方法會發生什麼事?也就是說多線程訪問同一個類的static靜態方法會發生什麼事?是否會發生線程安全問題?
public class Test {
public static void operation(){
// … do something
}
}
事實證明只要在靜態函數中沒有處理多線程共享數據,就不存在著多線程訪問同一個靜態方法會出現資源衝突的問題。下面看一個例子:
public class StaticThread implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
StaticAction.print();
}
public static void main(String[] args) {
for (int i = 0; i 100; i++) {
new Thread(new StaticThread()).start();
}
}
}
public class StaticAction {
public static int i = 0;
public static void print() {
int sum = 0;
for (int i = 0; i 10; i++) {
System.out.print(“step ” + i + ” is running.”);
sum += i;
}
if (sum != 45) {
System.out.println(“Thread error!”);
System.exit(0);
}
System.out.println(“sum is ” + sum);
}
}
實際執行的結果顯示各個線程對靜態方法的訪問是交叉執行的,但是這並不影響各個線程靜態方法print()中sum值的計算。也就是說,在此過程中沒有使用全局變數的靜態方法在多線程中是安全的,靜態方法是否引起線程安全問題主要看該靜態方法是否對全局變數(靜態變數static member)進行修改操作。
在多線程中使用同一個靜態方法時,每個線程使用各自的實例欄位(instance field)的副本,而共享一個靜態欄位(static field)。所以說,如果該靜態方法不去操作一個靜態成員,只在方法內部使用實例欄位(instance field),不會引起安全性問題。
但是,如果該靜態方法操作了一個靜態變數,則需要靜態方法中採用互斥訪問的方式進行安全處理。我們來看一下沒有使用互斥訪問的話會產生怎樣的問題:public class StaticAction {
public static int i = 0;
public static void incValue() {
int temp = StaticAction.i;
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
temp++;
StaticAction.i = temp;
}
}
public class StaticThread implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
StaticAction.incValue();
}
public static void main(String[] args) {
for (int i = 0; i 100; i++) {
new Thread(new StaticThread()).start();
}
try {
Thread.sleep(1000); //預留足夠的時間讓上面的線程跑完
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(StaticAction.i);
}
}
實際運行結果顯示i值為隨機的數字。為了實現互斥訪問,這時我們需要加入一個synchronized關鍵字。代碼修改如下:
public class StaticAction {
public static int i = 0;
public synchronized static void incValue() {
int temp = StaticAction.i;
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
temp++;
StaticAction.i = temp;
}
}
public class StaticThread implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
StaticAction.incValue();
}
public static void main(String[] args) {
for (int i = 0; i 100; i++) {
new Thread(new StaticThread()).start();
}
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(StaticAction.i);
}
}
運行結果則必然是100。
加入synchronized關鍵字的靜態方法稱為同步靜態方法。
在訪問同步靜態方法時,會獲取該類的「Class」對象,所以當一個線程進入同步的靜態方法中時,線程監視器獲取類本身的對象鎖,其它線程不能進入這個類的任何靜態同步方法。它不像實例方法,因為多個線程可以同時訪問不同實例同步實例方法。這個其實就是操作系統中的用信號量實現進程的互斥與同步問題,如果涉及在同一個類中有多個靜態方法中處理多線程共享數據的話,那就變成用信號量解決生產者-消費者問題。也就是說,靜態方法是一份臨界資源,對靜態方法的訪問屬於進入臨界區;對靜態變數的修改是一份臨界資源,對靜態變數的修改屬於進入臨界區。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/289525.html