【工作隨手記】併發之synchronized

synchronized對於java同學肯定都是耳熟能詳的必修課了。但是不管對於新手還是老手都有一些容易搞錯的點。這裏權做一點記錄。

鎖的是代碼還是對象?

同步塊一般有兩種寫法。
1是直接加以方法體上。

public synchronized void incr3(){
            i++;
    }

2是塊小鎖粒度的加到代碼塊

public void incr() throws InterruptedException { 
        synchronized (SyncIncrDemo.class){
            i++;
        }
    }

不管怎樣,鎖的都是對象。對於這段代碼來說,誰拿到這個鎖對象,就能優先執行鎖定的代碼,且對其它線程互斥。
代碼2顯示鎖定的類對象,那麼代碼1在方法體上鎖對象是誰呢?

debug下打印堆棧信息可以很明顯的看到代碼塊1裏面的鎖是類的實例對象 locked <0x218> (a com.alpha.data.util.SyncIncrDemo)

Full thread dump

"main@1" prio=5 tid=0x1 nid=NA waiting
java.lang.Thread.State: WAITING
at java.lang.Object.wait(Object.java:-1)
at java.lang.Thread.join(Thread.java:1252)
at java.lang.Thread.join(Thread.java:1326)
at com.alpha.data.util.SyncIncrDemo.main(SyncIncrDemo.java:59)

"Thread-1@530" prio=5 tid=0xf nid=NA sleeping
java.lang.Thread.State: TIMED_WAITING
at java.lang.Thread.sleep(Thread.java:-1)
at com.alpha.data.util.SyncIncrDemo.incr2(SyncIncrDemo.java:33)
- locked <0x217> (a com.alpha.data.util.SyncIncrDemo)
at com.alpha.data.util.SyncIncrDemo.run(SyncIncrDemo.java:49)
at java.lang.Thread.run(Thread.java:748)

"Thread-0@529" prio=5 tid=0xe nid=NA sleeping
java.lang.Thread.State: TIMED_WAITING
at java.lang.Thread.sleep(Thread.java:-1)
at com.alpha.data.util.SyncIncrDemo.incr2(SyncIncrDemo.java:33)
- locked <0x218> (a com.alpha.data.util.SyncIncrDemo)
at com.alpha.data.util.SyncIncrDemo.run(SyncIncrDemo.java:49)
at java.lang.Thread.run(Thread.java:748)

"Finalizer@533" daemon prio=8 tid=0x3 nid=NA waiting
java.lang.Thread.State: WAITING
at java.lang.Object.wait(Object.java:-1)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler@534" daemon prio=10 tid=0x2 nid=NA waiting
java.lang.Thread.State: WAITING
at java.lang.Object.wait(Object.java:-1)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"Attach Listener@531" daemon prio=5 tid=0x5 nid=NA runnable
java.lang.Thread.State: RUNNABLE

"Signal Dispatcher@532" daemon prio=9 tid=0x4 nid=NA runnable
java.lang.Thread.State: RUNNABLE

對於靜態方法呢?

public static synchronized void incr3(){
            i++;
    }

打印堆棧,可以看到是鎖的是類對象 locked <0x20f> (a java.lang.Class)
這也符合預期,因爲靜態類方法屬於類對象,而不是實例對象。

Full thread dump

"Thread-0@529" prio=5 tid=0xe nid=NA waiting for monitor entry
java.lang.Thread.State: BLOCKED
waiting for Thread-1@530 to release lock on <0x20f> (a java.lang.Class)
at com.alpha.data.util.SyncIncrDemo.incr(SyncIncrDemo.java:24)
at com.alpha.data.util.SyncIncrDemo.run(SyncIncrDemo.java:51)
at java.lang.Thread.run(Thread.java:748)

"main@1" prio=5 tid=0x1 nid=NA waiting
java.lang.Thread.State: WAITING
at java.lang.Object.wait(Object.java:-1)
at java.lang.Thread.join(Thread.java:1252)
at java.lang.Thread.join(Thread.java:1326)
at com.alpha.data.util.SyncIncrDemo.main(SyncIncrDemo.java:61)

"Thread-1@530" prio=5 tid=0xf nid=NA sleeping
java.lang.Thread.State: TIMED_WAITING
blocks Thread-0@529
at java.lang.Thread.sleep(Thread.java:-1)
at com.alpha.data.util.SyncIncrDemo.incr(SyncIncrDemo.java:24)
- locked <0x20f> (a java.lang.Class)
at com.alpha.data.util.SyncIncrDemo.run(SyncIncrDemo.java:51)
at java.lang.Thread.run(Thread.java:748)

"Finalizer@533" daemon prio=8 tid=0x3 nid=NA waiting
java.lang.Thread.State: WAITING
at java.lang.Object.wait(Object.java:-1)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler@534" daemon prio=10 tid=0x2 nid=NA waiting
java.lang.Thread.State: WAITING
at java.lang.Object.wait(Object.java:-1)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"Attach Listener@531" daemon prio=5 tid=0x5 nid=NA runnable
java.lang.Thread.State: RUNNABLE

"Signal Dispatcher@532" daemon prio=9 tid=0x4 nid=NA runnable
java.lang.Thread.State: RUNNABLE

所以,對於synchronized代碼塊,鎖對象爲顯示的鎖定對象;synchronized方法分爲靜態方法和非靜態方法,對於靜態方法,鎖對象爲該class對,否則屬於該class的實例對象。

public static synchronized void incr3(){
            i++;
    }
等同於

    public synchronized void incr3() throws InterruptedException {
        synchronized (SyncIncrDemo.class){
            Thread.sleep(100000);
        }
        i++;
    }
public synchronized void incr3(){
            i++;
    }
等同於

    public synchronized void incr3() throws InterruptedException {
        synchronized (this){
            Thread.sleep(100000);
        }
        i++;
    }

對於相同的競態場景,必須持相同的鎖對象。通俗的講,一個門只能有一把鎖。

爲什麼會有這樣的問題。這個問題對於新手來講有點饒,對於有些人來講是廢話。
源於看到一篇博文,大概是有一個Integer類型的共享變量,多線程併發對其操作,在同步塊裏對於操作。僞代碼

Integer count = 0;

public void add(){
    synchronized(count){
          count++;
     }
}

這樣子很明顯是達不到預期結果的。Integer在超出-128到127範圍以後,每一個對象都是一個新的實例對象。換句話說,這裏想要達到N多人同時想要通過一個通道,通道門上有多把鎖,很多人可以同時拿到鎖進而併發地通過。達不到預期目的。

可能有人會不以爲然,對代碼塊加同步鎖肯定不能用Integer這種對象啊,這隻能是新手犯的錯,用this呢?
前面說到,使用this鎖對象爲當前class的實例對象,在spring默認單例的情況下,肯定沒有問題,但如果某位同事在某天在某種需求場景下改爲了多例呢?
很明顯的,多例情況下,就是同一個門多把鎖了。

所以這裏是需要注意的點。
我覺得最安全最健壯的方法是不要在方法上加同步塊,一律使用類對象作爲鎖。

synchronized(A.class){          
     }

synchronized就一定安全嗎

從所周知,
synchronized能保證原子性,可見性,有序性,
volatile能保證可見性和有序性,防止指令重排。
那麼synchronized是不是就一定能保證線程安全呢?共享變量就可以不用volatile了呢?

我覺得答案是否定的。哪怕是在synchronized內部,如果有共享變量,必須強制使用volatile,這是一個好的編碼習慣。

synchronized 的有序性是持有相同鎖的兩個同步塊只能串行的進入,即被加鎖的內容要按照順序被多個線程執行,但是其內部的同步代碼還是會發生重排序,使塊與塊之間有序可見。
volatile的有序性是通過插入內存屏障來保證指令按照順序執行。不會存在後面的指令跑到前面的指令之前來執行。是保證編譯器優化的時候不會讓指令亂序。
synchronized 是不能保證指令重排的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章