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 是不能保證指令重排的。