同步訪問共享的可變數據
同步的意義有兩個方面,之前一直以爲只是爲了操作的互斥性,保持狀態一致,理解太淺顯,需要深入研究。
一、保持對象狀態一致性,即同步可以阻止一個線程看到對象處於不一致的狀態中,當一個線程訪問同步對
象時,可阻止其他線程對該對象進行訪問,從而觀察到對象內部不一致的狀態;
二、保證進入同步方法或者同步代碼塊的每個線程,都看到由同一個鎖保護的之前的所有修改。
示例一、
如果期望1s中後,backgroudThread線程能夠正常退出,在某些機器上可能會失敗,因爲主進程將stopRequested設置爲true的狀態,backgroudThread不一定能及時看到。
public class StopThread
{
private static boolean stopRequested;
// public static synchronized void requestStop()
// {
// stopRequested = true;
// }
//
// public static synchronized boolean stopRequested()
// {
// return stopRequested;
// }
public static void main(String[] args) throws InterruptedException
{
Thread backgroudThread = new Thread(new Runnable()
{
public void run()
{
int i = 0;
while(!stopRequested)
{
i++;
System.out.println("i: " + i);
}
System.out.println("out!!!");
}
});
backgroudThread.start();
TimeUnit.SECONDS.sleep(1);
//requestStop();
stopRequested = true;
}
}
示例二、
用同步來解決這個問題,通過同步,backgroudThread線程每次讀的狀態值stopRequested都能保證是其他線程已經修改過的。
public class StopThread
{
private static boolean stopRequested;
public static synchronized void requestStop()
{
stopRequested = true;
}
public static synchronized boolean stopRequested()
{
return stopRequested;
}
public static void main(String[] args) throws InterruptedException
{
Thread backgroudThread = new Thread(new Runnable()
{
public void run()
{
int i = 0;
while(!stopRequested())
{
i++;
System.out.println("i: " + i);
}
System.out.println("out!!!");
}
});
backgroudThread.start();
TimeUnit.SECONDS.sleep(1);
requestStop();
}
}
另外,volatile也可以保證它所修飾的對象的修改都能被立即寫入主內存,從而達到同步的效果,如果只需要線程之間通信,而不用互斥,volatile是一種可以接受的同步形式,但使用要謹慎。
java語言規範保證所有基本數據類型的讀寫操作都是原子操作,long和double除外,因爲64位數值會被分成兩個32位值來操作。
最好的方法:要麼共享不可變的數據,要麼壓根不共享,即將可變數據限制在單個線程中。
注:java內存模型(JMM),深入理解java內存模型。