Java線程中的同步,Java線程同步
1、對象與鎖
每一個Object類及其子類的執行個體都擁有一個鎖。其中,標量類型int,float等不是物件類型,但是標量類型可以通過其封裝類來作為鎖。單獨的成員變數是不能被標明為同步的。鎖只能用在使用了這些變數的方法上。成員變數可以被聲明為volatile,這種方式會影響該變數的原子性,可見度以及排序性。
類似的,持有標量變數元素的數組對象擁有鎖,但是其中的標量元素卻不擁有鎖。(也就是說,沒有辦法將數群組成員聲明為volatile類型的)。如果鎖住了一個數組並不代表其數群組成員都可以被原子的鎖定。也沒有能在一個原子操作中鎖住多個對象的方法。
Class執行個體本質上是個對象。正如下所述,在靜態同步方法中用的就是類對象的鎖。
2、同步方法和同步塊
使用synchronized關鍵字,有兩種文法結構:同步代碼塊和同步方法。同步代碼塊需要提供一個作為鎖的對象參數。這就允許了任意方法可以去鎖任一一個對象。但在同步代碼塊中使用的最普通的參數卻是this。
同步代碼塊被認為比同步方法更加的基礎。如下兩種聲明方式是等同的:
| 1 |
synchronized void f() { /* body */ } |
| 2 |
void f() { synchronized(this) { /* body */ } } |
synchronized關鍵字並不是方法簽名的一部分。所以當子類覆寫父類中的同步方法或是介面中聲明的同步方法的時候,synchronized修飾符是不會被自動繼承的,另外,構造方法不可能是真正同步的(儘管可以在構造方法中使用同步塊)。
同步執行個體方法在其子類和父類中使用同樣的鎖。但是內部類方法的同步卻獨立於其外部類, 然而一個非靜態內部類方法可以通過下面這種方式鎖住其外部類:
| 1 |
synchronized(OuterClass.this) { /* body */ } |
3、等待鎖與釋放鎖
使用synchronized關鍵字須遵循一套內建的鎖等待-釋放機制。所有的鎖都是塊結構的。當進入一個同步方法或同步塊的時候必須獲得該鎖,而退出的時候(即使是異常退出)必須釋放這個鎖。你不能忘記釋放鎖。
鎖操作是建立在獨立的線程上的而不是獨立的調用基礎上。一個線程能夠進入一個同步代碼的條件是當前鎖未被佔用或者是當前線程已經佔用了這個鎖,否則線程就會阻塞住。(這種可重新進入鎖或是遞迴鎖不同於POSIX線程)。這就允許一個同步方法可以去直接調用同一個鎖管理的另一個同步方法,而不需要被凍結(註:即不需要再經曆釋放鎖-阻塞-申請鎖的過程)。
同步方法或同步塊遵循這種鎖擷取/鎖釋放的機制有一個前提,那就是所有的同步方法或同步塊都是在同一個鎖對象上。如果一個同步方法正在執行中,其他的非同步方法也可以在任何時候執行。也就是說,同步不等於原子性,但是同步機制可以用來實現原子性。
當一個線程釋放鎖的時候,另一個線程可能正等待這個鎖(也可能是同一個線程,因為這個線程可能需要進入另一個同步方法)。但是關於哪一個線程能夠緊接著獲得這個鎖以及什麼時候,這是沒有任何保證的。另外,沒有什麼辦法能夠得到一個給定的鎖正被哪個線程擁有著。
除了鎖控制之外,同步也會對底層的記憶體系統帶來副作用。
4、靜態變數/方法
鎖住一個對象並不會原子性的保護該對象類或其父類的靜態成員變數。而應該通過同步的靜態方法或代碼塊來保證訪問一個靜態成員變數。靜態同步使用的是靜態方法鎖聲明的類對象所擁有的鎖。類C的靜態鎖可以通過內建的執行個體方法擷取到:synchronized(C.class) { /* body */ }
每個類所對應的靜態鎖和其他的類(包括其父類)沒有任何的關係。通過在子類中增加一個靜態同步方法來試圖保護父類中的靜態成員變數是無效的。應使用顯式的代碼塊來代替。
如下這種方式也是一種不好的實踐:
synchronized(getClass()) { /* body */ } // Do not use
這種方式,可能鎖住的實際中的類,並不是需要保護的靜態成員變數所對應的類(有可能是其子類)
Java虛擬機器在類載入和類初始化階段,內部獲得並釋放類鎖。除非你要去寫一個特殊的類載入器或者需要使用多個鎖來控制靜態初始順序,這些內部機制不應該幹擾普通類對象的同步方法和同步塊的使用。Java虛擬機器沒有什麼內部操作可以獨立的擷取你建立和使用的類對象的鎖。然而當你繼承java.*的類的時候,你需要特別小心這些類中使用的鎖機制
關於Java線程問題,推薦上海尚學堂Java線程相關技術文章,如:
《認知Java 同步塊(synchronized block)》;
《Java多線程爬蟲實現》;
《Java線程的知識要點概述》;
《Java 線程池(ThreadPoolExecutor)原理分析與使用》等等。
感謝閱讀上海Java培訓文章,更多請關注收藏,後續Java相關技術文章陸續奉上,轉載請註明出處!