synchronized

来源:互联网 发布:mac地址定位软件 编辑:程序博客网 时间:2024/05/16 05:33

上一篇中在提到StringBuffer类中操作字符串的方法都是用synchronized修饰的,所以是线程安全的,那么在这篇就介绍一个synchronized是什么,为什么它修饰了就是线程安全的。

造成线程安全的诱因

1.存在共享数据
2.存在多条线程共同操作共享数据

解决方案

当存在多条线程操作共享数据时,只要保证在同一时刻有且只有一条线程能够操作该共享数据,其他线程之一必须等到该线程操作完之后才能操作共享数据,能够实现这种机制的就是互斥锁 ,即一旦给共享数据加了互斥锁,那么同一时刻只能有一条线程在操作,其他线程处于等待状态,等当前线程处理完毕释放锁之后,才能够由等待的线程中的其中之一获取锁,操作共享数据。

注:锁不是配在代码或者代码块上,而是对象上。

要想了解synchronized是怎么工作的,还要了解Java对象头和Monitor。
在JVM中对象在堆中的布局其实分为3部分:对象头、实例变量和填充数据,其中实例变量是存放类的属性数据信息,填充数据是为了满足JVM对对象起始地址的要求(要求必须是8字节的整数倍)。而对象头则是和锁息息相关了。

对象头

对象头也有两部分组成:Mark Word和Class metadata address:

表格 1:MarkWord存储结构

重量级锁的指针指向Monitor对象(管程/监视器锁)的起始地址,每个对象都存在一个Monitor与之关联,而Monitor是由ObjectMonitor实现的,其主要数据结构如下:看到Mark word中有不同的锁分类,而synchronized就是属于重量级锁,其锁标志位是10。

表格 2:ObjectMonitor.hpp

synchronized可以用于修饰代码块、静态方法和实例变量。

synchronized修饰同步块的底层原理

反编译一个文件就可以发现,synchronized修饰的代码块中有一个monitorenter和两个monitorexit指令,这两条指令分布表示了开始同步和同步结束(正常结束和异常结束)。

 monitorenter//进入同步方法   …..   monitorexit//退出同步方法   ……   goto   monitorexit //退出同步方法(异常结束时)

自己反编译一下看看,我不贴完整代码了。

synchronized修饰方法底层原理

和同步代码块不同,同步方法并不是monitorenter和monitorexit实现的,而是通过一个叫ACC_SYNCHRONZED访问标志来区分的,一旦代码中有这个标志,就说明这个方法是同步方法,否则是非同步方法。

Publicsynchronized void syncTask(){descriptor:()Vflags:ACC_PUBLIC,ACC_SYNCHRONIZED //方法标识ACC_PUBLIC标识public修饰Code:……}

补充:
锁有四种:无锁状态,偏向锁,轻量级锁和重量级锁。正像我排序的这样,锁的膨胀也是逐步进行的,不能越级膨胀,即不能从偏向锁直接膨胀到重量级锁,而是要先膨胀为轻量级锁,再膨胀为重量级锁。
偏向锁

适用于无所竞争的情况下,即偏向锁总是偏向于第一个获取该锁的线程(会通过CAS记录下线程的ID并记录在Mark Word中),只要没有锁竞争,偏向锁的线程永远不需要同步,但是如果有其他线程来竞争锁,偏向锁就不满足了,要膨胀为轻量级锁。(下面的图看得懂就看,看不懂就算了,这是偏向锁及其膨胀的示意图)