我對(duì)于鎖的初次接觸和簡(jiǎn)單理解
定義一個(gè)商店Shop類
public class Shop {
public void watch() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ":" + "開(kāi)始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了舟山!");
Thread.sleep(2000);
}
public void buy() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ":" + "開(kāi)始買商品掩浙!");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "買完了");
Thread.sleep(2000);
}
}
這個(gè)類中有兩個(gè)方法赖临,每個(gè)方法都可以輸出兩次當(dāng)前線程名和對(duì)應(yīng)語(yǔ)句星立。為了方便區(qū)分爽茴,在這兩個(gè)輸出語(yǔ)句之間加上2秒的間隔。
public class About_LockTest {
public static void main(String[] args) {
//實(shí)例化一個(gè)Shop對(duì)象 sp
Shop sp = new Shop();
//匿名內(nèi)部類使得t0和t1都執(zhí)行sp中的watch方法
Thread t0 = new Thread(() -> {
try {
sp.watch();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t1 = new Thread(() -> {
try {
sp.watch();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t0.start();
t1.start();
}
}
運(yùn)行該程序绰垂,發(fā)現(xiàn)t0和t1同時(shí)輸出相應(yīng)語(yǔ)句室奏,這表明兩個(gè)線程在同時(shí)運(yùn)行。如果劲装,對(duì)watch方法加上鎖胧沫,那么再運(yùn)行該程序會(huì)有什么結(jié)果呢?
public synchronized void watch() throws InterruptedException {//在public后面加上synchronized
System.out.println(Thread.currentThread().getName() + ":" + "開(kāi)始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
Thread.sleep(2000);
}
此時(shí)占业,通過(guò)輸出線程名可以發(fā)現(xiàn)绒怨,只有一個(gè)線程在運(yùn)行,而另一個(gè)線程沒(méi)有運(yùn)行纺酸,也就是處于阻塞狀態(tài)窖逗。直接在方法名前面加上synchronized關(guān)鍵詞,其實(shí)等價(jià)于以下語(yǔ)句
public void watch() throws InterruptedException {
synchronized (this) {//將整個(gè)方法體包裹在鎖內(nèi)餐蔬,其中用this碎紊,本類對(duì)象作為鑰匙
System.out.println(Thread.currentThread().getName() + ":" + "開(kāi)始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
Thread.sleep(2000);
}
}
? 線程在執(zhí)行這個(gè)方法的時(shí)候樊诺,被鎖住的部分必須要鑰匙才能進(jìn)入仗考,每個(gè)鑰匙只能同時(shí)被一個(gè)線程持有。在這里鑰匙是本類對(duì)象词爬,而在上面都是利用sp對(duì)象調(diào)用方法秃嗜,所以鑰匙就是sp。這個(gè)持有sp的狀態(tài)顿膨,會(huì)一直持續(xù)到線程在走出synchronized包裹的范圍前锅锨,此時(shí)沒(méi)拿到鑰匙sp的線程只能在鎖外排隊(duì),處于阻塞狀態(tài)恋沃。等到一個(gè)線程走出synchro-nized的作用域之后必搞,就會(huì)釋放手中的鑰匙sp,此時(shí)鑰匙處于自由狀態(tài)囊咏,而另一個(gè)線程會(huì)得到這把鑰匙恕洲,于是可以進(jìn)入這個(gè)鎖住的區(qū)域塔橡,可以執(zhí)行里面的代碼。
? 為了驗(yàn)證以上觀點(diǎn)霜第,我對(duì)Thread t1進(jìn)行改寫:
1葛家、鎖只作用于自己的作用域
Thread t1 = new Thread(() -> {
try {
sp.buy();//添加一個(gè)buy方法在watch方法前面
sp.watch;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
運(yùn)行程序后,發(fā)現(xiàn)t0在執(zhí)行watch方法的同時(shí)泌类,t1也在執(zhí)行buy方法癞谒,當(dāng)t0執(zhí)行完watch且t1執(zhí)行完buy之后,t1拿到t0的鎖末誓,繼續(xù)執(zhí)行watch方法扯俱。所以說(shuō),此處這把鎖只是鎖住了watch方法喇澡,對(duì)于buy方法而言迅栅,并無(wú)任何影響
2、同一把鑰匙只能同時(shí)被同一個(gè)線程持有(忽略釋放之后持有之前的自由狀態(tài))
將t0的watch方法刪去晴玖,只留有buy方法:
Thread t1 = new Thread(() -> {
try {
sp.buy();//此處t1只有buy方法了
} catch (InterruptedException e) {
e.printStackTrace();
}
});
將Shop類中的buy也加上synchronized鎖读存,這把鎖的鑰匙也是this本類對(duì)象,在目前認(rèn)仍是sp
public synchronized void buy() throws InterruptedException {//buy方法加鎖
System.out.println(Thread.currentThread().getName() + ":" + "開(kāi)始買商品呕屎!");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "買完了");
Thread.sleep(2000);
}
運(yùn)行程序后让簿,發(fā)現(xiàn)t0執(zhí)行watch方法時(shí),線程t1沒(méi)有任何動(dòng)作秀睛,而等到t0結(jié)束watch方法后尔当,才開(kāi)始執(zhí)行buy。由于之前已經(jīng)證明鎖只能作用于自己的作用域蹂安,所以排除了watch的鎖影響了buy(相反亦然)椭迎,這就說(shuō)明此處鑰匙只能同時(shí)被一個(gè)線程持有,只要等鑰匙釋放并拿到鑰匙后田盈,另一個(gè)線程才會(huì)執(zhí)行畜号。
3、只要持有鑰匙就可以打開(kāi)對(duì)應(yīng)鎖
(在2的基礎(chǔ)上)在Shop類中實(shí)例化一個(gè)Object對(duì)象obj允瞧,這里將watch方法鎖的鑰匙改為obj:
Object obj = new Object();//新生成一個(gè)obj
public void watch() throws InterruptedException {
synchronized (obj) {//將obj作為鑰匙
System.out.println(Thread.currentThread().getName() + ":" + "開(kāi)始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了简软!");
Thread.sleep(2000);
}
}
運(yùn)行程序發(fā)現(xiàn),t0和t1分別同時(shí)運(yùn)行watch和buy方法述暂。由1和2可以知道痹升,不會(huì)是二者同時(shí)持有鑰匙,并且因?yàn)椴煌i之間相互影響而導(dǎo)致線程同時(shí)運(yùn)行畦韭。這里是因?yàn)槭勇瑆atch方法使用了obj作為鑰匙,t0持有的是obj廊驼;buy使用了本類對(duì)象sp作為鑰匙据过。這兩個(gè)鑰匙并不相同,所以t0和t1都能直接拿到各自的鑰匙(沒(méi)有其他線程競(jìng)爭(zhēng))妒挎,打開(kāi)對(duì)應(yīng)的鎖執(zhí)行方法绳锅。
4、調(diào)用不同對(duì)象的多線程的同步
新創(chuàng)建一個(gè)Shop對(duì)象shp酝掩,讓t0只調(diào)用shp的buy方法鳞芙,t1只調(diào)用原來(lái)sp的方法,將3中兩個(gè)鎖的鑰匙都改為this
public static void main(String[] args) {
Shop sp = new Shop();
Shop shp=new Shop();//再實(shí)例化一個(gè)shp
Thread t0 = new Thread(() -> {
try {
shp.buy();//調(diào)用shp中的buy方法
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t1 = new Thread(() -> {
try {
sp.buy();//調(diào)用sp中的buy方法
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t0.start();
t1.start();
}
/**兩個(gè)方法都以this作為鑰匙*/
public void watch() throws InterruptedException {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + ":" + "開(kāi)始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了期虾!");
Thread.sleep(2000);
}
}
public synchronized void buy() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ":" + "開(kāi)始買商品原朝!");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "買完了");
Thread.sleep(2000);
}
執(zhí)行程序后發(fā)現(xiàn)t0和t1同時(shí)各自執(zhí)行buy方法,因?yàn)樗麄兊蔫€匙shp镶苞、sp都直接被持有喳坠,無(wú)需等待。如果在這種情況下同步t0和t1茂蚓,可以在類中創(chuàng)建一個(gè)靜態(tài)對(duì)象作為鑰匙:
static Object obj = new Object();//靜態(tài)對(duì)象
//都以靜態(tài)對(duì)象為鑰匙
public void watch() throws InterruptedException {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + ":" + "開(kāi)始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了壕鹉!");
Thread.sleep(2000);
}
}
public void buy() throws InterruptedException {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + ":" + "開(kāi)始買商品!");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "買完了");
Thread.sleep(2000);
}
}
靜態(tài)對(duì)象是類所有對(duì)象共享的聋涨,所以不管有多少個(gè)對(duì)象晾浴,在這個(gè)類都只有一個(gè)靜態(tài)對(duì)象可用,這個(gè)鑰匙就唯一了牍白,所以執(zhí)行程序后一個(gè)線程先執(zhí)行buy脊凰,之后另一個(gè)線程才執(zhí)行buy。
總結(jié)
鎖的作用范圍是有限的(可自定義)茂腥,不同鎖對(duì)應(yīng)的鑰匙可以相同狸涌,相應(yīng)的這把鑰匙完全可以打開(kāi)這些鎖。同一把鑰匙同時(shí)只能被同一個(gè)線程持有础芍,并且只有這個(gè)持有鑰匙的線程可以執(zhí)行鎖內(nèi)代碼杈抢,沒(méi)有鑰匙的線程就處于阻塞狀態(tài),等到持有鑰匙的線程走出鎖后仑性,會(huì)釋放鑰匙惶楼,這時(shí)就有機(jī)會(huì)得到鑰匙執(zhí)行代碼,沒(méi)有得到就繼續(xù)堵塞诊杆,等待下一次機(jī)會(huì)歼捐。