關鍵字synchronized可以保證同一時刻,只有一個線程可以執行某個方法。
同步的概念
1、當一個對象被一個線程修改的時候,可以阻止另一個線程觀察到對象內部不一致的狀態;
2、同步不僅可以組織一個線程看到對象處於不一致的狀態,還可以保證進入同步方法或者同步代碼塊的每個線程,都看到由同一個鎖保護的之前所有的修改效果。
另外,java語言規範保證讀寫一個變量是原子的,除非這個變量是double或者long(JSL,17.4.7),即使沒有在保證同步的情況下也是如此。
"爲了提高性能,在讀寫原子數據的時候,應該避免使用同步。”這個建議是非常危險而且錯誤的。因爲,雖然讀寫原子數據都是原子操作,但是不保證一個線程的寫入的值對於另一個線程是完全可見的(值得一提的是,究竟什麼樣的原子變量必須進行同步,是需要看情況的)。因此,爲了在線程之間進行可靠的通信,也爲了互斥訪問,同步是必要的。
考慮一個線程妨礙另一個線程任務
public class Temp {
private static boolean stopRequested;
public static void main(String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (!stopRequested) {
i++;
}
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
實際上以上這段程序會永遠的運行下去,因爲沒有使用同步,無法保證後臺進程可以看到stopRequested值的改變。虛擬機將代碼: while (!stopRequested) {
i++;
}
轉變成了:if(!stopRequested)
while(true)
{
i++;
}
這樣一來,永遠都不會看到stopRequested的改變。 private static boolean stopRequested;
private static synchronized void requestStop() {
stopRequested = true;
}
private static synchronized boolean stopRequested() {
return stopRequested;
}
public static void main(String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (!stopRequested()) {
i++;
}
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
requestStop();
}
事實上,在本例中,只同步讀方法(這和下面的volatile類似),不同步寫方法,也是可以的。使用volatile
private static volatile boolean stopRequested;
public static void main(String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (!stopRequested) {
i++;
}
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
在使用volatile的時候要非常小心,以下的例子說明它可能會出現錯誤: private static volatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
return nextSerialNumber++;
}
儘管使用了volatile,但是由於++運算符不是原子的,因此在多線程的時候會出錯。++運算符執行兩項操作:1、讀取值;2、寫回新值(相當於原值+1)。如果第二個線程在第一個線程讀取舊值和寫會新值的時候讀取了這個域,就會產生錯誤,他們會得到相同的SerialNumber。解決方法可以是,加入synchronized並去掉volatile。進一步的,可以用Long來代替int,或者在快要溢出的時候,拋出異常。更好的是使用AtomicLong類。
private static final AtomicLong nextSerialNumber = new AtomicLong(0);
public static Long generateSerialNumber() {
return nextSerialNumber.incrementAndGet();
}