synchronized

1.解决的问题

解决多线程数据共享及同步

2.使用方式

2.1修饰实例方法

作用于当前实例,进入同步代码需要获取当前实例的锁

synchronized void addA(){
    a+=1;
}

等价于

void addA() {
    synchronized (this) {
        a += 1;
    }
}

2.2 修饰静态方法

作用于当前类,进入同步代码需要获取当前类的锁

static synchronized void addA(){
        a+=1;
}

等价于

void addA() {
    synchronized (当前类.class) {
        a += 1;
    }
}

2.3 修饰代码块

作用于锁对象,进入同步代码需要获取当前锁对象的锁

void addA(){
      synchronized (object){
          a+=1;  
      }
}

3.验证

3.1 修饰实例方法/代码块 this

public class AccountingStaticSync implements Runnable {

    static int a;

    private String threadName;

    synchronized void addA() {
        a += 1;
    }

    /**
     * synchronized 修饰实例方法等同于 synchronized (this)
     */

//    void addA(){
//        synchronized (this) {
//            a += 1;
//        }
//    }


    public void run() {
        for (int index = 0; index < 100000; index++) {
            addA();
            Thread.yield();
        }

    }

    public AccountingStaticSync(String threadName) {
        this.threadName = threadName;
    }

    public static void main(String[] args) throws Exception {
        int threadCount = 15;
        List<Thread> threads = new LinkedList<Thread>();
        for (int index = 0; index < threadCount; index++) {
            threads.add(new Thread(new AccountingStaticSync(String.valueOf(index))));
        }
        for (int index = 0; index < threadCount; index++) {
            threads.get(index).start();
        }
        for (int index = 0; index < threadCount; index++) {
            threads.get(index).join();
        }
        System.out.println("a=" + a);

    }
}
执行结果.png

当多个线程以该方式修改共享数据时,导致数据不一致的发生

3.2 修饰静态方法/共享对象

public class AccountingStaticSync implements Runnable {

    static AccountingStaticSync sync = new AccountingStaticSync("sync");

    static int a;

    private String threadName;

    static synchronized void addA() {
        a += 1;
    }

    /**
     * synchronized 修饰静态方法等同于 synchronized (this.class) 或 synchronized (sync object)
     */

//    void addA(){
//        synchronized (AccountingStaticSync.class) {
//            a += 1;
//        }
//    }
//
//    void addA(){
//        synchronized (sync) {
//            a += 1;
//        }
//    }


    public void run() {
        for (int index = 0; index < 100000; index++) {
            addA();
            Thread.yield();
        }

    }

    public AccountingStaticSync(String threadName) {
        this.threadName = threadName;
    }

    public static void main(String[] args) throws Exception {
        int threadCount = 15;
        List<Thread> threads = new LinkedList<Thread>();
        for (int index = 0; index < threadCount; index++) {
            threads.add(new Thread(new AccountingStaticSync(String.valueOf(index))));
        }
        for (int index = 0; index < threadCount; index++) {
            threads.get(index).start();
        }
        for (int index = 0; index < threadCount; index++) {
            threads.get(index).join();
        }
        System.out.println("a=" + a);

    }
}
执行结果.png

修饰静态方法/this.class/sync object 均是阻塞在同一个对象上,因此可以安全地执行

4.底层原理

4.1 同步对象

当synchronized修饰实例方法/对象时,是基于对象头以及monitorenter 和 monitorexit 指令来实现同步的

4.1.1 对象头

对象实例.png

实例变量:存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐

填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐,这点了解即可

4.1.2MarkWord

虚拟机位数 头对象结构 说明
32/64bit Mark Word 存储对象的hashCode、锁信息或分代年龄或GC标志等信息
32/64bit Class Metadata Address 类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例

其中Mark Word在默认情况下存储着对象的HashCode、分代年龄、锁标记位等以下是32位JVM的Mark Word默认存储结构

锁状态 25bit 4bit 1bit是否偏向锁 2bit锁标志位
无锁状态 对象hashcode 对象分代年龄 0 01

由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间,如32位JVM下,
除了上述列出的Mark Word默认存储结构外,还有如下可能变化的结构:


Mark Word.png

4.1.3 重量锁---synchronized

重量锁中的指针指向一个 monitor 对象,该 monitor伴随锁对象生成和销毁
monitor 的数据结构如下

_ownerObjectMonitor() {
    _header       = NULL;
    _count        = 0; //记录锁个数,重入时,_count + 1, 释放时 _count - 1
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;  //记录持有锁的线程,因此是可重入的
    _WaitSet      = NULL; // 处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }
  • ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象)
  • _owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合
  • 当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1
  • 若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)
  • 无论执行代码的过程中成功或异常,均会释放锁

4.2同步方法

静态同步方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的

  • 方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中
  • JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法
  • 当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法
  • 最后再方法完成(无论是正常完成还是非正常完成)时释放monitor
  • 无论执行代码的过程中成功或异常,均会释放锁
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容