一慈省、synchronized簡介
Java提供了強制性的鎖機制:synchronized憔维,可用來給對象和方法或者代碼塊加鎖,當(dāng)它鎖定一個方法或者一個代碼塊的時候牧嫉,同一時刻最多只有一個線程執(zhí)行這段代碼剂跟。當(dāng)兩個并發(fā)線程訪問同一個對象object中的這個加鎖同步代碼塊時,一個時間內(nèi)只能有一個線程得到執(zhí)行酣藻。另一個線程必須等待當(dāng)前線程執(zhí)行完這個代碼塊以后才能執(zhí)行該代碼塊曹洽。然而,當(dāng)一個線程訪問object的一個加鎖代碼塊時辽剧,另一個線程仍然可以訪問該object中的非加鎖代碼塊送淆。
包括兩種用法:synchronized方法和 synchronized 塊。
二怕轿、synchronized的使用
2.1 synchronized方法
public static synchronized void inc() {
....
}
synchronized方法控制多個線程對該方法內(nèi)的成員的并發(fā)訪問坊夫,我們將inc方法申明為synchronized,所以同一時間只有一個線程可以訪問inc方法。當(dāng)?shù)谝粋€玩家進(jìn)來后撤卢,會對inc方法加一把鎖不讓別的玩家訪問环凿,玩家二如果也想訪問inc方法只能排隊等候直到玩家一訪問結(jié)束釋放鎖后,效果如下圖放吩。 雖然它現(xiàn)在是線程安全的了智听,但是這種方法過于極端,它的性能非常差渡紫。因為有時候我們需要共享的只是方法內(nèi)的部分?jǐn)?shù)據(jù)到推,其它數(shù)據(jù)是可以自由訪問的,那么這個時候我們應(yīng)該在項目中使用synchronized塊惕澎。
2.2 synchronized代碼塊
synchronized代碼塊控制線程訪問的數(shù)據(jù)在synchronized(obj)或synchronized(this){}里面莉测,同一時間也只能有一個線程可以訪問,別的請求線程將被阻塞在 synchronized(obj){}外邊唧喉,這樣可以不影響別的線程訪問不需要共享的數(shù)據(jù)捣卤。比如:inc() 玩家等級小于30 忍抽,不滿足條件程序直接return不用讓線程也阻塞在synchronized代碼塊外邊。
public void inc(Object obj) {
if(obj == null)
{
return;
}
synchronized (obj) {
count++;
}
}
public void inc(int lvl) {
if(lvl < 30)//玩家等級小于30 返回
{
return;
}
synchronized (this) {
count++;
}
}
2.3 對synchronized(this)的理解
當(dāng)兩個并發(fā)線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時董朝,一個時間內(nèi)只能有一個線程得到執(zhí)行鸠项。另一個線程必須等待當(dāng)前線程執(zhí)行完這個代碼塊以后才能執(zhí)行該代碼塊。
然而子姜,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊祟绊。
尤其關(guān)鍵的是,當(dāng)一個線程訪問object的一個synchronized(this)同步代碼塊時哥捕,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞牧抽。
第三個例子同樣適用其它同步代碼塊,它就獲得了這個object的對象鎖遥赚。結(jié)果阎姥,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
-
以上規(guī)則對其它對象鎖同樣適用鸽捻。
以上是摘自百度百科對synchronized的理解呼巴,詳細(xì)使用參考這篇文章:http://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html
三、內(nèi)部鎖的重進(jìn)入
當(dāng)一個線程請求其它線程已經(jīng)占有的鎖時御蒲,請求線程將被阻塞衣赶。然而內(nèi)部鎖是可重進(jìn)入的,因此線程在試圖獲得它自己占有的鎖時厚满,請求會成功府瞄。重進(jìn)入意味著鎖的請求是基于“每線程”,而不是基于“每調(diào)用”的碘箍。重進(jìn)入的實現(xiàn)是通過每個鎖關(guān)聯(lián)一個請求計數(shù)和一個占有它的線程遵馆。當(dāng)計數(shù)為0時,認(rèn)為鎖時未被占有的丰榴。線程請求一個未被占有的鎖時货邓,JVM將記錄鎖的占有者,并且將請求計數(shù)置為1四濒。如果同一線程再次請求這個鎖换况,計數(shù)將遞增;每次占用線程退出同步塊盗蟆,計數(shù)器值將遞減戈二。直到計數(shù)器達(dá)到0時,鎖被釋放喳资【蹩裕【摘自JAVA并發(fā)編程實戰(zhàn)】
3.1代碼示例
package com.game.lll.syn;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class UnsafeCount {
public static int count = 0;
static LoggingWidget loggingWidget = new LoggingWidget();
public static void inc() {
loggingWidget.doSomething();
}
public static void main(String[] args) throws InterruptedException {
ExecutorService service=Executors.newFixedThreadPool(Integer.MAX_VALUE);
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
UnsafeCount.inc();
}
});
}
service.shutdown();
//避免出現(xiàn)main主線程先跑完而子線程還沒結(jié)束,在這里給予一個關(guān)閉時間
service.awaitTermination(3000,TimeUnit.SECONDS);
System.out.println("運行結(jié)果:UnsafeCount.count=" + UnsafeCount.count);
}
}
package com.game.lll.syn;
public class LoggingWidget extends Widget{
public synchronized void doSomething()
{
System.out.println("LoggingWidget"+UnsafeCount.count++);
super.doSomething();
}
}
package com.game.lll.syn;
public class Widget {
public synchronized void doSomething()
{
System.out.println("Widget"+UnsafeCount.count++);
}
}
控制臺輸出:
LoggingWidget0
Widget1
LoggingWidget2
Widget3
LoggingWidget4
Widget5
LoggingWidget6
Widget7
LoggingWidget8
Widget9
LoggingWidget10
Widget11
LoggingWidget12
Widget13
LoggingWidget14
Widget15
LoggingWidget16
Widget17
LoggingWidget18
Widget19
運行結(jié)果:UnsafeCount.count=20
3.2代碼分析
重進(jìn)入方便了鎖行為的封裝仆邓,因此簡化了面向?qū)ο蟛l(fā)代碼的開發(fā)鲜滩。上面代碼子類覆寫了父類的synchronized類型的方法伴鳖,并調(diào)用父類中的方法。如果沒有可重入的鎖绒北,這段代碼將會產(chǎn)生死鎖黎侈。因為Weight和loggingWeight中的soSomething方法都是synchronized類型的察署,都會在處理前試圖獲得weight的鎖闷游。倘若內(nèi)部鎖不是可重入的,super.doSomething的調(diào)用者就永遠(yuǎn)無法得到weight的鎖贴汪,因為鎖已經(jīng)被占有脐往,導(dǎo)致線程會永久的延遲,等待著一個永遠(yuǎn)無法獲得的鎖扳埂。
四业簿、鎖的三大特性
4.1 原子性
原子性是指在同一時刻只有一個線程對它進(jìn)行讀寫操作,避免多個線程在更改共享數(shù)據(jù)時出現(xiàn)數(shù)據(jù)的不準(zhǔn)確阳懂。
在Java中提供了原子操作的關(guān)鍵字synchronized梅尤。我在上一篇文章中寫過原子性Atomic(一)
4.2 可見性
可見性是指當(dāng)一個線程修改了線程共享變量的值,其它線程能夠立即得知這個值的修改岩调。在Java中巷燥,除了synchronized,volatile和final也是可見性的号枕。synchronized可以確保線程能預(yù)見另一個線程對某一個值或狀態(tài)的更改缰揪,就像下圖一樣。當(dāng)線程一執(zhí)行一個同步塊時葱淳,線程二也隨后進(jìn)入了同一個鎖的同步塊中钝腺,這時可以保證,在釋放鎖M之前線程一變量的值count對線程二是可見的赞厕。換句話說就是有一個玻璃透明的房間艳狐,雖然線程一進(jìn)入后將房間鎖住了皿桑,但是線程二在門口還是可以透過玻璃看見房間內(nèi)的一切事物。
package com.game.lll.syn;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class SafeCount implements Runnable{
public volatile static int count = 0;
public synchronized static void inc()
{
count++;
}
public static void main(String[] args) throws InterruptedException {
SafeCount t1 = new SafeCount();
Thread ta = new Thread(t1, "線程一");
Thread tb = new Thread(t1, "線程二");
ta.start();
tb.start();
System.out.println("UnsafeCount.count=" + SafeCount.count);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---執(zhí)行前--count:"+count);
inc();
System.out.println(Thread.currentThread().getName()+"---執(zhí)行后--count:"+count);
}
}
控制臺輸出:
UnsafeCount.count=0
線程二---執(zhí)行前--count:0
線程一---執(zhí)行前--count:0
線程二---執(zhí)行后--count:1
線程一---執(zhí)行后--count:2
鎖不僅僅是關(guān)于同步與互斥浆西,也是關(guān)于內(nèi)存可見的近零。為了保證所有線程看到共享的、可變變量的最新值漓摩,讀寫和寫入線程必須使用公共的鎖進(jìn)行同步入客。摘自--《Java并發(fā)編程實戰(zhàn)》
4.3 有序性
Java語言提供了volatile和synchronized兩個關(guān)鍵字來保證線程之間操作的有序性桌硫,volatile關(guān)鍵字本身就包含了禁止指令重排序的語義铆隘,而synchronized則是由“一個變量在同一時刻只允許一條線程對其進(jìn)行l(wèi)ock操作”這條規(guī)則來獲得的膀钠,這個規(guī)則決定了持有同一個鎖的兩個同步塊只能串行地進(jìn)入肿嘲。
1.程序次序規(guī)則(Pragram Order Rule):在一個線程內(nèi)睦刃,按照程序代碼順序涩拙,書寫在前面的操作先行發(fā)生于書寫在后面的操作。準(zhǔn)確地說應(yīng)該是控制流順序而不是程序代碼順序工育,因為要考慮分支如绸、循環(huán)結(jié)構(gòu)怔接。
2.管程鎖定規(guī)則(Monitor Lock Rule):一個unlock操作先行發(fā)生于后面對同一個鎖的lock操作。這里必須強調(diào)的是同一個鎖瓦侮,而”后面“是指時間上的先后順序肚吏。罚攀。
3.volatile變量規(guī)則(Volatile Variable Rule):對一個volatile變量的寫操作先行發(fā)生于后面對這個變量的讀取操作坞生,這里的”后面“同樣指時間上的先后順序是己。
4.線程啟動規(guī)則(Thread Start Rule):Thread對象的start()方法先行發(fā)生于此線程的每一個動作任柜。
5.線程終于規(guī)則(Thread Termination Rule):線程中的所有操作都先行發(fā)生于對此線程的終止檢測摔认,我們可以通過Thread.join()方法結(jié)束宅粥,Thread.isAlive()的返回值等作段檢測到線程已經(jīng)終止執(zhí)行秽梅。
6.線程中斷規(guī)則(Thread Interruption Rule):對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生企垦,可以通過Thread.interrupted()方法檢測是否有中斷發(fā)生郑现。
7.對象終結(jié)規(guī)則(Finalizer Rule):一個對象初始化完成(構(gòu)造方法執(zhí)行完成)先行發(fā)生于它的finalize()方法的開始接箫。
8.傳遞性(Transitivity):如果操作A先行發(fā)生于操作B辛友,操作B先行發(fā)生于操作C瞎领,那就可以得出操作A先行發(fā)生于操作C的結(jié)論九默。
詳細(xì)請參考這篇文章:深入理解Java虛擬機筆記---原子性驼修、可見性墨礁、有序性
作者:小毛驢恩静,一個Java游戲服務(wù)器開發(fā)者 原文地址:https://liulongling.github.io/