有這樣一個(gè)場景,一個(gè)銀行賬戶有一個(gè)活期儲(chǔ)蓄余額和定期儲(chǔ)蓄余額。銀行類如下:
public class Account {
//活期存款
private int currentDeposit = 0;
//定期存款
private int fixedDeposit = 0;
public synchronized void addCurrentDeposit(int amount) {
this.currentDeposit += amount;
}
public synchronized void addFixedDeposit(int amount) {
this.currentDeposit += amount;
}
public void read() {
System.out.println("活期存款是:" + currentDeposit + " 定期存款是:" + fixedDeposit);
}
}
- 問題1:當(dāng)一個(gè)線程給活期增加余額時(shí)候(執(zhí)行addCurrentDeposit方法)作烟,另一個(gè)線程能不能給定期增加額度(執(zhí)行addFixedDeposit方法)?
- 問題2:當(dāng)一個(gè)線程給活期增加余額時(shí)候,能不能執(zhí)行read方法珍促?
- 問題3:假如要實(shí)現(xiàn)給活期存款時(shí)候也可以給定期存款,該怎么設(shè)計(jì)Account類剩愧?
問題1和問題2
設(shè)計(jì)這樣一個(gè)場景猪叙。三個(gè)線程分別操作同一個(gè)Account對象的3個(gè)方法addCurrentDeposit,addCurrentDeposit仁卷,read穴翩。執(zhí)行addCurrentDeposit需要20秒。然后通過實(shí)驗(yàn)結(jié)果驗(yàn)證锦积。
- 如果執(zhí)行addCurrentDeposit(需要20秒)過程中,addCurrentDeposit沒有執(zhí)行芒帕,同時(shí)read也沒有執(zhí)行,都是等addCurrentDeposit執(zhí)行完再執(zhí)行丰介,那么就可以得出:如果一個(gè)線程訪問一個(gè)對象的synchronized方法背蟆,其他線程不可以訪問該對象的所有其他方法。
- 如果執(zhí)行addCurrentDeposit(需要20秒)過程中基矮,addCurrentDeposit沒有執(zhí)行淆储,而read方法執(zhí)行了,則可以得出結(jié)論:如果一個(gè)線程訪問一個(gè)對象的synchronized方法家浇,其他線程不可以訪問該對象的其他synchronized方法本砰,其他非synchronized方法可以正常訪問
給出設(shè)計(jì)的實(shí)驗(yàn)代碼;
package synchronozedtest;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author: gethin
* @create: 2018-07-03 16:55
* @description:
**/
public class SynTest {
private static Account account = new Account();
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(3);
Thread t1 = new Thread(new AddCurrentDepositTask());
Thread t2 = new Thread(new AddFixedDepositTask());
Thread t3 = new Thread(new ReadTask());
executor.submit(t1);
//線程1提交后等待1秒钢悲,再提交線程2和線程3
Thread.sleep(1000);
executor.submit(t2);
executor.submit(t3);
executor.shutdown();
}
private static class AddCurrentDepositTask implements Runnable {
@Override
public void run() {
account.addCurrentDeposit(1);
}
}
private static class AddFixedDepositTask implements Runnable {
@Override
public void run() {
account.addFixedDeposit(2);
}
}
private static class ReadTask implements Runnable {
@Override
public void run() {
account.read();
}
}
private static class Account {
//活期存款
private int currentDeposit = 0;
//定期存款
private int fixedDeposit = 0;
public synchronized void addCurrentDeposit(int amount) {
long start=System.currentTimeMillis();
System.out.println("活期存錢");
this.currentDeposit += amount;
try {
//讓線程等待20秒点额,線程獲得鎖后必須等待100秒舔株,才能釋放鎖
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end=System.currentTimeMillis();
System.out.println("執(zhí)行活期存款用了"+(end-start)/1000+"秒");
}
public synchronized void addFixedDeposit(int amount) {
System.out.println("定期存錢");
this.fixedDeposit += amount;
}
public void read() {
System.out.println("活期存款是:" + currentDeposit + " 定期存款是:" + fixedDeposit);
}
}
}
運(yùn)行結(jié)果:
從上圖可以看出,執(zhí)行addCurrentDeposit過程中还棱,addCurrentDeposit沒有執(zhí)行载慈,而read方法執(zhí)行了。
所以可以得出結(jié)論:如果一個(gè)線程訪問一個(gè)對象的synchronized方法珍手,其他線程不可以訪問該對象的其他synchronized方法办铡,其他非synchronized方法則可以正常訪問
再往深處問一下,為什么是這個(gè)結(jié)論琳要,為什么一個(gè)線程訪問一個(gè)對象的synchronized方法寡具,其他線程也不能再訪問該對象的其他的synchronized?
這就要考究到synchronized的實(shí)現(xiàn)原理上來稚补,JVM 是通過進(jìn)入童叠、退出對象監(jiān)視器( Monitor )來實(shí)現(xiàn)對方法、同步塊的同步的课幕。
- 一個(gè)線程要進(jìn)去一個(gè)synchronized方法厦坛,必須獲得對象鎖,
- 如果第一個(gè)線程獲得對象鎖乍惊,進(jìn)入其中一個(gè)synchronized方法杜秸。
-
其他線程要進(jìn)入該對象其他synchronized方法,必須等第一個(gè)線程執(zhí)行完synchronized方法釋放掉對象鎖污桦,才能繼續(xù)執(zhí)行亩歹。
問題3
要在增加活期儲(chǔ)蓄同時(shí)增加定期儲(chǔ)蓄,可以重新設(shè)計(jì)Account,把int改成Integer對象凡橱,在addCurrentDeposit小作,addCurrentDeposit中分別同步Integer對象而不是同步Account對象。如下:
private static class Account {
//活期存款
private Integer currentDeposit = new Integer(0);
//定期存款
private Integer fixedDeposit = new Integer(0);
public void addCurrentDeposit(int amount) {
synchronized (currentDeposit) {
long start = System.currentTimeMillis();
System.out.println("活期存錢");
this.currentDeposit += amount;
try {
//讓線程等待20秒稼钩,線程獲得鎖后必須等待100秒顾稀,才能釋放鎖
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("執(zhí)行活期存款用了" + (end - start) / 1000 + "秒");
}
}
public void addFixedDeposit(int amount) {
synchronized (fixedDeposit) {
System.out.println("定期存錢");
this.fixedDeposit += amount;
}
}
public void read() {
System.out.println("活期存款是:" + currentDeposit + " 定期存款是:" + fixedDeposit);
}
}
結(jié)果如下:
從結(jié)果圖可以看出在執(zhí)行活期存儲(chǔ)的20秒內(nèi),定期存錢也執(zhí)行了坝撑,很好的解決了問題3静秆。此外,
synchronized關(guān)鍵字可用于標(biāo)記四種不同類型的塊:
- 實(shí)例方法
- 靜態(tài)方法
- 實(shí)例方法中的代碼塊
- 靜態(tài)方法中的代碼塊
這些使用方式巡李,區(qū)別如下:
同步普通方法抚笔,鎖的是當(dāng)前對象。
同步靜態(tài)方法侨拦,鎖的是當(dāng)前 Class 對象殊橙。
同步塊,鎖的是 {} 中的對象。