java基础内容

synchronized

2018-10-26  本文已影响0人  嗷大喵儿

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

4.2同步方法

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

上一篇下一篇

猜你喜欢

热点阅读