synchroinzed
假如有两个线程ThreadA/ThreadB
- 只有ThreadA去访问(大部分情况是属于这种) ->引入了偏向锁
- ThreadA的ThreadId、每次线程A进来判断ThreadId是否一样,在判断偏向锁标记是否为1
- ThreadA和ThreadB交替访问->轻量级锁->自旋
- 多个线程同时访问 ->阻塞。(挂起,等待获得锁)
在我们程序中我们怎么知道当前上面3种场景是哪种情况呢,只能通过升级锁的方式来达到,升级锁的顺序为:
偏向锁 -> 轻量级锁 -> 重量级锁
偏向锁
偏向锁是指当一段代码一直被一个线程所放访问时,对应上面的锁的第一种情况,即不存在多个线程竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗,即提升性能。
当一个线程访问同步代码块并获取锁时,会在mark word里存储锁的偏向的线程ID,偏向标记为01,当同一个线程访问这个代码块时,会先根据自身的线程ID判断是否跟mark word里存储的线程ID相等,如果相等则不通过CAS操作来进行原子操作加锁
偏向锁在JDK6以后的版本JVM是默认开启的,可以通过JVM参数关闭偏向锁: -XX:-UseBiasedLocking=false,关闭之后程序默认进入轻量级锁状态
轻量级锁
轻量级锁是由偏向锁升级的第二锁,轻量级锁是当锁是偏向锁时,被另外的线程访问,此时偏向锁就会升级为轻量级锁,其他线程可以通过自身的自旋的形式尝试获取锁,线程不会阻塞(虽然占用CPU,但是阻塞的线程更加消耗CPU)从而提高性能。
轻量级锁的获取主要有两种情况,第一种就是我们主动关闭偏向锁的功能时,第二种则是多个线程同事竞争获取偏向锁,导致偏向锁升级为轻量级锁的时候。
轻量级锁是当一个线程对一个锁进行cas操作更新时,如果更新失败,则代表这个锁已经被占用了,其他线程已经获得了偏向锁,这种情况说明当前所存在竞争,需要撤销已获得偏向锁的线程,并且把它升级为轻量级锁。
- 线程在自己的栈桢中创建锁记录LockRecord。
- 将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中。
- 将锁记录中的Owner指针指向锁对象。
- 将锁对象的对象头的MarkWord替换为指向锁记录的指针。
自旋锁
轻量级锁在加锁过程中,用到了自旋锁所谓自旋,就是指当有另外一个线程来竞争锁时,这个线程会在原地循环等待,而不是把该线程给阻塞,直到那个获得锁的线程释放锁之后,这个线程就可以马上获得锁的。注意,锁在原地循环的时候,是会消耗cpu的,就相当于在执行一个啥也没有的for循环。所以,轻量级锁适用于那些同步代码块执行的很快的场景,这样,线程原地等待很短的时间就能够获得锁了。自旋锁的使用,其实也是有一定的概率背景,在大部分同步代码块执行的时间都是很短的。所以通过看似无异议的循环反而能提升锁的性能。但是自旋必须要有一定的条件控制,否则如果一个线程执行同步代码块的时间很长,那么这个线程不断的循环反而会消耗CPU资源。默认情况下自旋的次数是10次,可以通过preBlockSpin来修改在JDK1.6之后,引入了自适应自旋锁,自适应意味着自旋的次数不是固定不变的,而是根据前一次在同一个锁上自旋的时间以及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。
for(;;){
//do something
}
重量级锁
重量级锁是指当一个线程获得锁之后,正在执行线程中的代码,未执行完未释放锁,而其他线程等待获取该锁的时候都会处于阻塞状态。
重量级锁是通过对象内部的监视器(monitor)实现的,而其中monitor的本质是依赖于底层操作系统的。任意线程对锁的访问,首先要获得监视器,如果获取失败,线程进入同步队列,线程状态变为BLOCKED。当占有锁的线程释放了锁。则该释放锁的操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。