在多線程中,可能有多個線程試圖訪問一個有限的資源,必須預防這種情況的發生。所以引入了同步機制:在線程使用一個資源時爲其加鎖,這樣其他的線程便不能訪問那個資源了,直到解鎖後纔可以訪問。
成員變量:
如果一個變量是成員變量,那麼多個線程對同一個對象的成員變量進行操作,這多個線程是共享一個成員變量的。
局部變量:
如果一個變量是局部變量,那麼多個線程對同一個對象進行操作,每個線程都會有一個該局部變量的拷貝。他們之間的局部變量互不影響。
實現了Runnable的線程類:
在main方法中用兩個線程操作同一個對象:
這裏如果把i變成成員變量,則輸出100個數字。
下面舉個例子,兩個線程共用一個Number對象,通過Number類的getNumber方法獲取數據,讀取數據並改寫時,發現了重複讀操作:
首先創建一個Number類:
線程類,在線程類中的私有屬性包含了Number類的引用:
在main函數中創建兩個線程類,包含了同一個Number類實例的引用:
這樣,當第一個線程讀取Number中的number變量時先保存下來再休眠0.1秒,然後第二個線程再讀取number變量並保存,此時兩個線程保存了同樣的數字,在修改時,也就導致修改了同一個數字兩次。
使用synchronized關鍵字,該關鍵字修飾的方法叫做同步方法。
Java中每個對象都有一個鎖或者稱爲監視器,當訪問某個對象的synchronized方法時,表示將該對象上鎖,而不僅僅是爲該方法上鎖。
這樣如果一個對象的synchronized方法被某個線程執行時,其他線程無法訪問該對象的任何synchronized方法(但是可以調用其他非synchronized的方法)。直至該synchronized方法執行完。
當調用一個對象的靜態synchronized方法時,它鎖定的並不是synchronized方法所在的對象,而是synchronized方法所在對象對應的Class對象。這樣,其他線程就不能調用該類的其他靜態synchronized方法了,但是可以調用非靜態的synchronized方法。
結論:執行靜態synchronized方法鎖方法所在對象,執行非靜態synchronized方法鎖方法所在對象對應的Class對象。
下面是多線程調用靜態的方法的例子,由於鎖定了方法所在對象對應的Class對象,其他線程無法調用該方法所在對象其他的靜態synchronized方法:
main方法中兩個線程分別調用同一個對象的兩個static synchronized方法:
一次只能調用一個靜態方法,直到執行完成。
通過使用synchronized同步代碼塊,鎖定一個對象,該對象作爲可執行的標誌從而達到同步的效果:
如果想要使用synchronized同步代碼塊達到和使用synchronized方法同樣的效果,可以鎖定this引用:
synchronized同步代碼塊只是鎖定了該代碼塊,代碼塊外面的代碼還是可以被訪問的。
synchronized方法是粗粒度的併發控制,某一個時刻只能有一個線程執行該synchronized方法。
synchronized同步代碼塊是細粒度的併發控制,只會將塊中的代碼同步,代碼塊之外的代碼可以被其他線程同時訪問。