什么是鎖,什么鎖住了什么
在 Android 中保證線程安全 有一種方式是使用 Synchronized 關(guān)鍵字葛躏。使用的是 Object 自帶的內(nèi)在鎖( Intrinsic Lock 本質(zhì)的 固有的 鎖)魁蒜。
Synchronized 的表現(xiàn)形式
同步代碼塊
public void getTicket() {
synchronized (objectA) {
System.out.println("code block ");
}
}
同步方法
public synchronized void getTicket() {
//...
}
Synchronzied 干了啥
synchronized 不管是同步代碼塊眼耀,還是同步方法较雕,內(nèi)部都是做了一樣事结执,就是告訴別人這個東西是他的了次氨,他動的時候別人都不能動蔽介,這樣才能保證這個東西的安全。他是誰煮寡,他就是線程屉佳,別人就是其他線程。這個東西是啥洲押,就是需要保證原子性的代碼武花。怎么告訴別人的,就是在用這個東西的時候加個鎖杈帐,鎖的是啥体箕,鎖住的是對象专钉,別的線程一看到這個東西鎖住了,就沒法用了累铅,就等著跃须。簡言之,就是 <b>線程鎖住了對象</b>娃兽。
舉幾個例子
在 MultiPassenger 中新建兩個 線程菇民,分別去調(diào) ticket 的 getTicket() 和 refundTicket()。
public class MultiPassenger {
public static void main(String args[]) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
ticket.getTicket();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
ticket.refundTicket();
}
}).start();
}
}
- getTicket() refundTicketd() 都不加 Synchronized
public class Ticket {
public void getTicket() {
try {
System.out.println(System.currentTimeMillis() + " get ticket");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void refundTicket() {
try {
System.out.println(System.currentTimeMillis() + " refund ticket");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出
1487387908597 get ticket
1487387908598 refund ticket
時間差只有 1ms投储,可以看出 兩個線程執(zhí)行方法時都沒有互相等待第练。
- getTicket() 方法前加 Synchronized,refundTicketd() 不加
public synchronized void getTicket() {
try {
System.out.println(System.currentTimeMillis() + " get ticket");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void refundTicket() {
try {
System.out.println(System.currentTimeMillis() + " refund ticket");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
輸出
1487388144889 get ticket
1487388144892 refund ticket
時間差也是只有 3ms玛荞,可以看出 兩個線程執(zhí)行方法時也都沒有互相等待娇掏。
- getTicket() 和 refundTicketd() 都加 Synchronized
public synchronized void getTicket() {
try {
System.out.println(System.currentTimeMillis() + " get ticket");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void refundTicket() {
try {
System.out.println(System.currentTimeMillis() + " refund ticket");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
輸出
1487388226934 get ticket
1487388227935 refund ticket
時間差大約 1s,可以看出 退票的線程等了取票的線程 1s勋眯。前文說 加鎖鎖住的是對象婴梧,在這里鎖住的就是當(dāng)前方法所在的對象。
- 改一下入口方法 客蹋,加上鎖塞蹭,鎖住 ticket,然后去掉 refundTicket() 前的 Synchronizd讶坯。
public class MultiPassenger {
public static void main(String args[]) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
ticket.getTicket();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (ticket){ //這里鎖住了 ticket
ticket.refundTicket();
}
}
}).start();
}
}
public class Ticket {
public synchronized void getTicket() {
try {
System.out.println(System.currentTimeMillis() + " get ticket");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void refundTicket() {
try {
System.out.println(System.currentTimeMillis() + " refund ticket");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運行下番电,輸出
1487389351027 refund ticket
1487389352029 get ticket
時間間隔還是 1s,說明synchronized 加在 方法上鎖住的就是當(dāng)前方法所在的對象實例闽巩。
- 如果是靜態(tài)方法上面加 synchronized 呢钧舌,鎖住的是啥担汤。其實是當(dāng)前類 this.getClass()涎跨,類也是個對象。
public class Ticket {
public synchronized static void getTicket() { //靜態(tài)類加鎖
try {
System.out.println(System.currentTimeMillis() + " get ticket");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void refundTicket() {
try {
synchronized (getClass()) { //鎖住 getClass()
System.out.println(System.currentTimeMillis() + " refund ticket");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運行下輸出
1487389688340 get ticket
1487389689342 refund ticket
時間差了 1s崭歧,可以看出靜態(tài)方法前加 Synchronized 和 Synchronized(this.getClass()) 鎖住的是同一個東西隅很。
- 再舉個死鎖的例子玩玩
如果 Thread1 鎖住了 objectA,Thread2 鎖住了 objectB率碾,這時 Thread1 還需要去鎖 objectB叔营,就需要等 Thread2 執(zhí)行完成釋放objectB,但是這時Thread2 還需要 objectA 所宰,就會發(fā)生死鎖绒尊,誰都過不去。
代碼如下
public class Ticket {
private Object objectA = new Object();
private Object objectB = new Object();
public void getTicket() {
try {
synchronized (objectA) {
Thread.sleep(100);
System.out.println(Thread.currentThread()+ " getTicket lock a");
synchronized (objectB) {
System.out.println(Thread.currentThread()+ " getTicket lock b");
System.out.println(System.currentTimeMillis() + " get ticket");
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread()+ "getTicket over");
}
}
public void refundTicket() {
try {
synchronized (objectB) {
System.out.println(Thread.currentThread()+ " refundTicket lock b");
Thread.sleep(100);
synchronized (objectA) {
System.out.println(Thread.currentThread()+ " refundTicket lock a");
System.out.println(System.currentTimeMillis() + " refund ticket");
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread()+ " refundTicket over");
}
}
}
輸出
Thread[Thread-1,5,main] refundTicket lock b
Thread[Thread-0,5,main] getTicket lock a
只輸出了兩行 log仔粥,沒有終止婴谱,只能手動終止程序蟹但。
總結(jié)
- synchronized 是線程鎖住了對象。
- synchronized 加在 非靜態(tài)方法上谭羔,鎖住的是 當(dāng)前對象华糖。
- 加在靜態(tài)方法前,鎖住的是當(dāng)前類(this.getclass() 類也是對象)瘟裸。
- synchronized(objectA) 加在代碼塊上客叉,鎖住的就是 objectA。
- 要注意 synchronized 可能引發(fā)死鎖
致謝
如有錯誤话告,望指出兼搏,十分感謝。