Java Synchronized的使用细节

有这样一个场景,一个银行账户有一个活期储蓄余额和定期储蓄余额。银行类如下:

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);
        }
    }
}

运行结果:


image

从上图可以看出,执行addCurrentDeposit过程中,addCurrentDeposit没有执行,而read方法执行了

所以可以得出结论:如果一个线程访问一个对象的synchronized方法,其他线程不可以访问该对象的其他synchronized方法,其他非synchronized方法则可以正常访问

再往深处问一下,为什么是这个结论,为什么一个线程访问一个对象的synchronized方法,其他线程也不能再访问该对象的其他的synchronized?

这就要考究到synchronized的实现原理上来,JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。

  • 一个线程要进去一个synchronized方法,必须获得对象锁,
  • 如果第一个线程获得对象锁,进入其中一个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);
        }
    }

结果如下:


image

从结果图可以看出在执行活期存储的20秒内,定期存钱也执行了,很好的解决了问题3。此外,
synchronized关键字可用于标记四种不同类型的块:

  1. 实例方法
  2. 静态方法
  3. 实例方法中的代码块
  4. 静态方法中的代码块

这些使用方式,区别如下:

  • 同步普通方法,锁的是当前对象。

  • 同步静态方法,锁的是当前 Class 对象。

  • 同步块,锁的是 {} 中的对象。

你可能感兴趣的:(Java Synchronized的使用细节)