Synchronized关键字

为什么要加锁:

多线程访问临界资源时(共享资源)可能发生线程安全问题:比如忘数据库插入一条数据,若此数据不存在则插入,此时多个线程多检测到

了此条数据不存在,那么将会出现数据库被插入多条相同数据的情况,即线程安全问题出现。

java提供了两种方式来实现同步互斥访问:sychronized关键字和Lock

 

java的synchrozied关键字;

我们把synchrozied关键字称作同步,主要用来给方法、代码块加锁,被加锁的代码段,同一时间内多线程同时访问同一对象的加锁方法/代码块时,

只能有一个线程执行能执行方法/代码块中的代码,其余线程必须等待当前线程执行完以后才执行该方法/代码块。

synchrozied作用域:

1.对于同步方法,锁是当前类的实例对象

2.对于同步方法块,锁是synchronized括号中内置的对象

3.对于静态同步方法,锁是前类的class对象,即一个java.lang.class的实例

synchronized特点:

1.是java语言的关键字,是java语言内置的特性

2.当线程执行完同步代码块或同步方法以后,线程会自动释放锁

3.当同步方法/同步代码块中发生异常,jvm会让线程释放锁

4.如果一个获得锁的线程因为磁盘IO或者其它原因长时间占用锁,其他线程将被阻塞,一直等待下去。(缺点)

synchronized底层原理:

1.同步代码块:同步代码块是通过monitorenter和monitorexit指令来实现的,monitorenter插入在代码块的开始位置,monitoreixt插入在代码块的结束位置,

JVM保证一个monitorenter对应一个monitoreixt,每一个对象都与一个监视器monitor相关联,当线程执行到monitorenter指令时,会尝试获取对象所对应的

monitor的所有权,即尝试获得锁。

2.同步方法:是依靠方法表中的ACC_SYNCHRONIZED标志来实现的,如果是同步方法,将此标志设为1,使调用该方法的对象或该方法所属的类

在JVM的内部对象Kclass作为对象锁。

为什么说synchronized是重量级锁?

synchronized会使争用不到锁的线程进入阻塞状态,而阻塞或唤醒一个线程需要操作系统在用户态和核心态之间切换,会消耗大量的资源,所以所synchronized

是一把重量锁。

JDK1.6之后,对synchronized做了大量优化。

了解优化之前,先来了解一下对象头和monitor

synchronized的锁保存在对象头中,对象头主要由标记字段(Mark word)和类型指针组成,类型指针指向类的元数据,JVM通过类型指针判断当前对象是哪个类的实例,而 标记字段用于记录对象自身的运行时数据,如对象的hash码,GC分代年龄,是否为偏向锁,锁标志位等等

Mark word会虽随程序运行而复用自己的存储空间,锁为偏量锁时:Mark word 保存了当前拥有锁的线程ID,对象GC分代年龄等,轻量锁时,Mark word指向当前拥有锁的栈帧,重量级锁时,Mark word为指向重量级锁的指针。

monitor:Monitor是线程私有的数据结构,每个线程都有一个可用的monitor record 列表,每个java对象都携带一把锁,我们把它称为内部所或monitor,对象头中的的标记字段(Mark word)中的Lock word指向monitor的起始地址,monitor中有一个owner存放拥有该锁的线程的唯一标识,owner为空时,说明当前没有任何栈帧拥有此monitor record说明当前没有线程拥有此锁。

锁的几种状态:无锁状态、偏向锁、轻量级锁、重量级锁

轻量级锁并不是用来代替重量级锁的,而是用于无多线程竞争多同一资源的情况下对的一种锁优化形式。

自旋锁:当要获取的锁被其他线程占用时,当前线程做无意义的自旋等待锁释放,虽然避免了线程切换带来的开销,但同时也带来了性能上的浪费。

自应性自旋锁:自旋次数由上一次在同一把锁上的自旋时间和锁拥有者的状态来决定,上一次自旋成功了此次自旋次数就会增加,反之则减少。

锁消除:某些情况下,JVM监测到某部分数据操作不可能存在共享资源竞争,如在方法中定义线程安全的Vector变量,因为方法中的变量属于局部变量,

是线程私有的,所有不会存在共享资源竞争,JVM会将锁消除。

锁粗化:为防止连续加锁释放锁造成不必要的性能消耗,将多个加锁、解锁操作连在一起,扩展成一个范围更大的锁。

轻量级锁:引入轻量级锁的目的主要是在无多线程竞争的条件下,减少重量级锁使用使得操作系统互斥量产生的性能消耗,并不是代替重量级锁。

获取轻量级锁的步骤:

1.判断当前对象是否属于无锁状态,若是,在当前栈帧中建立一个名为锁记录(Lock record)的空间,用于存放锁对象的Mark word,JVM用CAS尝试将锁对象头的Mark word更新为指向栈帧的指针,更新成功说明竞争到锁,将锁标志位设为0(轻量级锁),然后执行同步操作。

2.若过当前对象的锁已被持有,则判断当前对象的Mark word是否指向当前栈帧,若是说明已经拿到锁,执行同步方法或同步代码块,若不是说明当前所对象已经被其他线程抢占了,这是轻量级锁膨胀成重量级锁。

3.如果1中更新失败,说明没有抢占到锁,同样将轻量级锁膨胀成中量级锁。

释放锁:

轻量级锁的释放也是通过CAS操作来实现的。

1.取出刚才栈帧中保存在锁记录中的数据,将数据替换到当前所对象的Mark Lock中,如果替换成功,则释放成功,否则说明有其它线程在尝试获得锁,

并且锁已经膨胀为重量级锁,这时候释放锁的同时还要唤醒被挂起的线程。

偏向锁:引入偏向锁的目的是在无多线程竞争的情况下,尽量减少不必要的轻量锁的执行路径,减少不必要的CAS操作。

获取锁的过程:

1.通过当前对象的Mark word中的锁标记位判断当前对象的锁是否为偏向锁,若是,判断与当前对象头中Mark word中的线程ID是否为当前线程ID,是的话说明当前线程已经获取锁,执行同步代码块。

2.如果Mark word中的线程ID不是当前线程的ID,那么当前线程通过CAS操作尝试获得锁,如果获取成功,将对象头中的线程ID指向自己,执行同步代码块。

如果竞争失败,说明当前存在多线程竞争的情况,偏向锁将会升级为轻量级锁。

 

 

 

 

 

 

 

Lock相对于synchronized加入了更多的机制

 

posted @ 2018-03-10 15:34  大熊好好写代码  阅读(560)  评论(0编辑  收藏  举报