有这样一个场景,一个银行账户有一个活期储蓄余额和定期储蓄余额。银行类如下:
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:当一个线程给活期增加余额时候(执行addCurrentDeposit方法),另一个线程能不能给定期增加额度(执行addFixedDeposit方法)?
- 问题2:当一个线程给活期增加余额时候,能不能执行read方法?
- 问题3:假如要实现给活期存款时候也可以给定期存款,该怎么设计Account类?
问题1和问题2
设计这样一个场景。三个线程分别操作同一个Account对象的3个方法addCurrentDeposit,addCurrentDeposit,read。执行addCurrentDeposit需要20秒。然后通过实验结果验证。
- 如果执行addCurrentDeposit(需要20秒)过程中,addCurrentDeposit没有执行,同时read也没有执行,都是等addCurrentDeposit执行完再执行,那么就可以得出:如果一个线程访问一个对象的synchronized方法,其他线程不可以访问该对象的所有其他方法。
- 如果执行addCurrentDeposit(需要20秒)过程中,addCurrentDeposit没有执行,而read方法执行了,则可以得出结论:如果一个线程访问一个对象的synchronized方法,其他线程不可以访问该对象的其他synchronized方法,其他非synchronized方法可以正常访问
给出设计的实验代码;
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("执行活期存款用了"+(end-start)/1000+"秒");
}
public synchronized void addFixedDeposit(int amount) {
System.out.println("定期存钱");
this.fixedDeposit += amount;
}
public void read() {
System.out.println("活期存款是:" + currentDeposit + " 定期存款是:" + fixedDeposit);
}
}
}
运行结果:
从上图可以看出,执行addCurrentDeposit过程中,addCurrentDeposit没有执行,而read方法执行了。
所以可以得出结论:如果一个线程访问一个对象的synchronized方法,其他线程不可以访问该对象的其他synchronized方法,其他非synchronized方法则可以正常访问
再往深处问一下,为什么是这个结论,为什么一个线程访问一个对象的synchronized方法,其他线程也不能再访问该对象的其他的synchronized?
这就要考究到synchronized的实现原理上来,JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。
- 一个线程要进去一个synchronized方法,必须获得对象锁,
- 如果第一个线程获得对象锁,进入其中一个synchronized方法。
-
其他线程要进入该对象其他synchronized方法,必须等第一个线程执行完synchronized方法释放掉对象锁,才能继续执行。
问题3
要在增加活期储蓄同时增加定期储蓄,可以重新设计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("执行活期存款用了" + (end - start) / 1000 + "秒");
}
}
public void addFixedDeposit(int amount) {
synchronized (fixedDeposit) {
System.out.println("定期存钱");
this.fixedDeposit += amount;
}
}
public void read() {
System.out.println("活期存款是:" + currentDeposit + " 定期存款是:" + fixedDeposit);
}
}
结果如下:
从结果图可以看出在执行活期存储的20秒内,定期存钱也执行了,很好的解决了问题3。此外,
synchronized关键字可用于标记四种不同类型的块:
- 实例方法
- 静态方法
- 实例方法中的代码块
- 静态方法中的代码块
这些使用方式,区别如下:
同步普通方法,锁的是当前对象。
同步静态方法,锁的是当前 Class 对象。
同步块,锁的是 {} 中的对象。