Java并发系列之Synchronized

  每一个刚接触多线程并发编程的同学,当被问到,如果多个线程同时访问一段代码,发生并发的时候,应该怎么处理?

  我相信闪现在脑海中的第一个解决方案就是用synchronized,用锁,让这段代码同一时间只能被一个线程执行。
我们也知道,synchronized关键字可以用在方法上,也可以用在代码块上,如果要使用synchronized,我们一般就会如下使用:

public synchronized void doSomething() {
    //do something here
}

或:

synchonized(LockObject) {
    //do something here
}

  那么实际上,synchronized关键字到底是怎么加锁的?锁又长什么样子的呢?关于锁,还有一些什么样的概念需要我们去认识,去学习,去理解的呢?

  以前在学习synchronized的时候,就有文章说, synchronized是一个很重的操作,开销很大,不要轻易使用,我们接受了这样的观点,但是为什么说是重的操作呢,为什么开销就大呢?

  到Java1.6之后,Java的开发人员又针对锁机制实现了一些优化,又有文章告诉我们现在经过优化后,使用synchronized并没有什么太大的问题了,那这又是因为什么原因呢?到底是做了什么优化?

  那今天我们就尝试着从锁机制实现的角度,来讲述一下synchronized在Java虚拟机上面的适应场景是怎么样的。

  由于Java在1.6之后,引入了一些优化的方案,所以我们讲述synchronized,也会基于Java1.6之后的版本。

锁对象

  首先,我们要知道锁其实就是一个对象,Java中每一个对象都能够作为锁。

  所以我们在使用synchronized的时候, 
  1. 对于同步代码块,就得指定锁对象。 
  2. 对于修饰方法的synchronized,默认的锁对象就是当前方法的对象。 
  3. 对于修饰静态方法的synchronized,其锁对象就是此方法所对应的类Class对象

  我们知道,所谓的对象,无非也就是内存上的一段地址,上面存放着对应的数据,那么我们就要想,作为锁,它跟其它的对象有什么不一样呢?怎么知道这个对象就是锁呢?怎么知道它跟哪个线程关联呢?它又怎么能够控制线程对于同步代码块的访问呢?

Markword

  可以了解到在虚拟机中,对象在内存中的存储分为三部分: 
  1. 对象头 
  2. 实例数据 
  3. 对齐填充

  其中,对象头填充的是该对象的一些运行时数据,虚拟机一般用2到3个字宽来存储对象头。 
  1. 数组对象,会用3个字宽来存储。 
  2. 非数据对象,则用2个字宽来存储。

  其结构简单如下:

长度 内容 说明
32/64bit Markword hashCode,GC分代年龄,锁信息
32/64bit Class Metadata Address 指向对象类型数据的指针
32/64bit Class Metadata Address 数组的长度(当对象为数组时)

 

 


  

  从上表中,我们可以看到,锁相关的信息,是存在称之为Markword中的内存域中。

  拿以下的代码作为例子,

synchonized(LockObject) {
    //do something here
}

  在对象LockObject的对象头中,当其被创建的时候,其Markword的结构如下:

bit fields   是否偏向锁 锁标志位
hash age 0 01

 

 

  从上面Markword的结构中,可以看出

  所有新创建的对象,都是可偏向的(锁标志位为01),但都是未偏向的(是否偏向锁标志位为0)。

文章暂时理解至此,https://blog.csdn.net/linmiansheng/article/details/80518130


  

上一篇:java基础 - synchronized实现原理


下一篇:JAVA锁的膨胀过程和优化