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 对象。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容

  • 前言 本人主要是结合《Java多线程编程核心技术》这本书的第二章内容,对synchronized关键字的知识进行梳...
    AR7_阅读 894评论 0 4
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,374评论 8 265
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 2,808评论 3 53
  • 静谧的午后里我遇见你阳光重若千钧压弯了你的脊背连须发也像倦极了的云低声喘息——当我问起你的往日曾经闪烁的眸子沉默着...
    Biobot阅读 136评论 0 0
  • 悸动的心 人会在一日之间改变,你信吗? 生命会在一瞬间变得光辉灿烂,你信吗? 岁月会突然充满了...
    秋萧萧之平儿阅读 373评论 4 2