多個線程可以共享進程的堆和方法區資源,既多個線程共享類變量。多個線程共享一個進程的變量時,如果線程對這個變量只有讀操作,沒有更新操作則這個線程沒有線程安全問題。如果線程需要對這個變量進行修改操作,則可能會因為數據更新不及時導致變量信息不準確而引發線程不安全。
當多個線程對同一個資源進行操作的時候就會有線程安全。解決線程安全的核心思想就是加鎖。加鎖有兩種方式:1.JVM提供的鎖,就是synchronized鎖,即同步代碼和同步代碼塊 2.jdk提供的各種鎖,如lock。
在JDK1.1版本中,所有的集合都是線程安全的。但是在1.2及以后的版本中就出現了一些線程不安全的集合,為什么版本升級反而會出現一些線程不安全的集合呢?因為線程不安全的集合普遍比線程安全的集合效率高的多。隨著業務的發展,特別是在WEB應用中,為了提高用戶體驗,減少用戶的等待時間,頁面的響應速度(也就是效率)是優先考慮的。而且對線程不安全的集合加鎖以后也能達到安全的效果(但是效率會低,因為會有鎖的獲取以及等待)。其實在JDK源碼中相同效果的集合線程安全的比線程不安全的就多了一個同步機制,但是效率上卻低了不止一點點,因為效率低,所以已經不太建議使用了。下面列舉一些常用的線程安全的集合。
Vector:就比ArrayList多了個同步化機制。
HashTable:就比HashMap多了個線程安全。
ConcurrentHashMap:是一種高效但是線程安全的集合。
Stack:棧,線程安全,繼承與Vector。
1)修飾實例方法: 作用于當前對象實例加鎖,進入同步代碼前要獲得 當前對象實例的鎖
synchronized void method() {
? //業務代碼
}
2)修飾靜態方法: 也就是給當前類加鎖,會作用于類的所有對象實例 ,進入同步代碼前要獲得 當前 class 的鎖。因為靜態成員不屬于任何一個實例對象,是類成員( static 表明這是該類的一個靜態資源,不管 new 了多少個對象,只有一份)。所以,如果一個線程 A 調用一個實例對象的非靜態 synchronized 方法,而線程 B 需要調用這個實例對象所屬類的靜態 synchronized 方法,是允許的,不會發生互斥現象,因為訪問靜態 synchronized 方法占用的鎖是當前類的鎖,而訪問非靜態 synchronized 方法占用的鎖是當前實例對象鎖。
synchronized void staic method() {
? //業務代碼
}
3)修飾代碼塊 :指定加鎖對象,對給定對象/類加鎖。synchronized(this|object) 表示進入同步代碼庫前要獲得給定對象的鎖。synchronized(類.class) 表示進入同步代碼前要獲得 當前 class 的鎖
synchronized(this) {
? //業務代碼
}
總結:synchronized 關鍵字加到 static 靜態方法和 synchronized(class) 代碼塊上都是是給 Class 類上鎖。
synchronized 關鍵字加到實例方法上是給對象實例上鎖。
盡量不要使用 synchronized(String a) 因為 JVM 中,字符串常量池具有緩存功能!
構造方法不能使用 synchronized 關鍵字修飾。構造方法本身就屬于線程安全的,不存在同步的構造方法一說。
public class Singleton {
private static Singleton uniqueInstance;
// 私有化構造方法
private Singleton() {
}
// 提供getInstance方法
public static Singleton getInstance() {
//先判斷對象是否已經實例過,沒有實例化過才進入加鎖代碼
if (uniqueInstance == null) {
//類對象加鎖
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
同步:功能調用時,在沒有得到結果之前,該調用就不返回或繼續執行后續操作。這時程序是阻塞的,只有接收到返回的值或消息后才往下執行其他的命令。因此,簡單來說,同步就是必須一件一件做事,等前一件事做完了才能做完下一件事。
異步:與同步相對,當一個異步過程調用發出后,調用者在沒有得到結果之前,就可以繼續執行后續操作,當這個調用完成后,一般通過狀態或者回調來通知調用者。
同步塊,這意味著同步塊之外的代碼是異步執行的,這比同步整個方法更提升代 碼的效率。請知道一條原則:同步的范圍越小越好。
當一個線程需要調用對象的wait()方法的時候,這個線程必須擁有該對象的鎖,接著它就會釋放這個對象鎖并進入等待狀態直到其他線程調用這個對象上的notify()方法。同樣的,當一個線程需要調用對象的notify()方法時,它會釋放這個對象的鎖,以便其他在等待的線程就可以得到這個對象鎖。由于所有的這些方法都需要線程持有對象的鎖,這樣就只能通過同步來實現,所以他們只能在同步方法或者同步塊中被調用。
同步就是協同步調,按預定的先后次序進行運行。如:你說完,我再說。這里的同步千萬不要理解成那個同時進行,應是指協同、協助、互相配合。線程同步是指多線程通過特定的設置(如互斥量,事件對象,臨界區)來控制線程之間的執行順序(即所謂的同步)也可以說是在線程之間通過同步建立起執行順序的關系,如果沒有同步,那線程之間是各自運行各自的!
線程互斥是指對于共享的進程系統資源,在各單個線程訪問時的排它性。當有若干個線程都要使用某一共享資源時,任何時刻最多只允許一個線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源。線程互斥可以看成是一種特殊的線程同步(下文統稱為同步)。