簡體   English   中英

正確使用volatile變量和synchronized塊

[英]Proper use of volatile variables and synchronized blocks

我試圖在java(或一般)中繞過線程安全。 我有這個類(我希望符合POJO的定義),它也需要與JPA提供程序兼容:

    public class SomeClass {

        private Object timestampLock = new Object();
        // are "volatile"s necessary?
        private volatile java.sql.Timestamp timestamp;
        private volatile String timestampTimeZoneName;

        private volatile BigDecimal someValue;

        public ZonedDateTime getTimestamp() {
            // is synchronisation necessary here? is this the correct usage?
            synchronized (timestampLock) {
                return ZonedDateTime.ofInstant(timestamp.toInstant(), ZoneId.of(timestampTimeZoneName));
            }
        }

        public void setTimestamp(ZonedDateTime dateTime) {
            // is this the correct usage?
            synchronized (timestampLock) {
                this.timestamp = java.sql.Timestamp.from(dateTime.toInstant());
                this.timestampTimeZoneName = dateTime.getZone().getId();
            }
        }

        // is synchronisation required?
        public BigDecimal getSomeValue() {
            return someValue;
        }

        // is synchronisation required?
        public void setSomeValue(BigDecimal val) {
            someValue = val;
        }
    }

如代碼中注釋的行中所述,是否有必要將timestamptimestampTimeZoneName定義為volatile並且是應用的synchronized塊? 或者我應該只使用synchronized塊而不是將timestamptimestampTimeZoneName定義為volatile timestampTimeZoneNametimestamp不應錯誤地與另一個timestamp匹配。

這個鏈接說

對於聲明為volatile的所有變量,讀取和寫入都是原子的(包括長變量和雙變量)

我是否應該理解通過setter / getter訪問此代碼中的someValue是否是線程安全的,這要歸功於volatile定義? 如果是這樣,有沒有更好的(我不知道“更好”在這里可能意味着什么)實現這一目標的方法?

要確定是否需要同步,請嘗試想象一個可以使用上下文切換來破壞代碼的地方。

在這種情況下,如果上下文切換發生在我放置注釋的位置,那么在getTimestamp()您將從每個時間戳類型中讀取不同的值。

另外,雖然賦值是原子的,但這個表達式是java.sql.Timestamp.from(dateTime.toInstant()); 當然不是,所以你可以在dateTime.toInstant()from的調用之間進行上下文切換。 總之,你肯定需要同步塊。

synchronized (timestampLock) {
    this.timestamp = java.sql.Timestamp.from(dateTime.toInstant());
    //CONTEXT SWITCH HERE
    this.timestampTimeZoneName = dateTime.getZone().getId();
}

synchronized (timestampLock) {
    return ZonedDateTime.ofInstant(timestamp.toInstant(), ZoneId.of(timestampTimeZoneName));
}

在易變性方面,我很確定它們是必需的。 您必須保證每個線程肯定會獲得變量的最新版本。

這是揮之不去的合約。 雖然它可能被同步塊覆蓋,並且在這里實際上並不需要,但無論如何都要寫。 如果synchronized塊已經執行了volatile的工作,則VM將不會執行兩次保證。 這意味着不再需要揮發性物質,這是一個非常好的閃光燈,對程序員說:“我已經在多個線程中使用了”。


對於someValue :如果這里沒有同步塊,那么volatile肯定是必要的。 如果在一個線程中調用一個集合,則另一個線程沒有隊列告訴它可能已在此線程之外更新。 因此它可能使用舊的緩存值。 如果它采用單線程,JIT可以做很多有趣的優化。 可以簡單地破壞你的程序。

現在我不完全確定這里是否需要同步。 我的猜測是否定的。 無論如何我會添加它以保證安全。 或者你可以讓java擔心同步並使用http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicInteger.html

這里沒什么新東西,這只是@Cruncher已經說過的更明確的版本:

只要程序中的兩個或多個字段彼此一致,您就需要synchronized 假設您有兩個並行列表,並且您的代碼依賴於它們兩者的長度相同。 這被稱為不變量 ,兩個列表總是相同的長度。

如何編寫一個方法,追加(x,y),為列表添加一對新值而不會暫時破壞不變量? 你不能。 該方法必須將一個項添加到第一個列表,打破不變量,然后將另一個項添加到第二個列表,再次修復它。 別無他法。

在單線程程序中,臨時中斷狀態沒有問題,因為當append(x,y)正在運行時,沒有其他方法可以使用列表。 在多線程程序中不再是這樣。 在最壞的情況下,append(x,y)可以將x添加到x列表,然后調度程序可以在該確切時刻掛起線程以允許其他線程運行。 在附加(x,y)完成作業並使列表再次正確之前,CPU可以執行數百萬條指令。 在所有這段時間內,其他線程會看到破壞的不變量,並可能導致數據損壞或導致程序崩潰。

修復是為了使append(x,y)在某個對象上synchronized ,並且(這是重要的部分),對於使用列表在同一對象synchronized 每個其他方法 由於在給定時間只能在給定對象上synchronized一個線程,因此任何其他線程都不可能看到處於不一致狀態的列表。

因此,如果線程A調用append(x,y),並且線程B嘗試“同時”查看列表,那么線程B是否會線程A執行其工作之前之后看到列表的樣子? 這被稱為數據競賽 只有我到目前為止所描述的同步,沒有辦法知道哪個線程會贏。 到目前為止我們所做的就是保證一個特定的不變量。

如果哪個線程贏得比賽很重要,那么這意味着有一些更高級別的不變量也需要保護。 您還必須添加更多同步以保護該同步。 “線程安全” - 用兩個小詞來命名一個既寬又深的主題。

祝好運並玩得開心點!

    // is synchronisation required?
    public BigDecimal getSomeValue() {
        return someValue;
    }

    // is synchronisation required?
    public void setSomeValue(BigDecimal val) {
        someValue = val;
    }

我認為是的,您需要放置同步塊,因為考慮一個示例,其中一個線程正在設置該值,同時其他線程正在嘗試從getter方法讀取, 就像在示例中您將看到同步塊一樣。因此,如果您在方法中使用變量,則必須要求同步塊。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM