通常说的synchronized用来作多线程同步功能,其实synchronized只是提供多线程互斥,而对象的wait()和notify()方法才提供线程的同步功能。数据结构
通常说synchronized是加锁,或者说是加对象锁,其实对象锁只是synchronized在实现锁机制中的一种锁(重量锁,用这种方式互斥线程开销大因此叫重量锁,或者叫对象monitor),而synchronized的锁机制会根据线程竞争状况在运行会有偏向锁、轻量锁、对象锁,自旋锁(或自适应自旋锁)等,总之,synchronized能够认为是一个几种锁过程的封装。多线程
原理jvm
一般说的synchronized在方法或块上加锁,这里的锁就是对象锁(固然也能够在类上面),或者叫重量锁,在JVM中又叫对象监视器(Monitor),就是对象来监视线程的互斥。spa
先来回顾一下对象在堆里的逻辑结构:线程
对象头里的结构大体如此:对象
其中Tag的2bit用来显示锁类型。一般咱们说synchronized的对象锁,就是这里Tag=10时的monitor对象,这里的Monitor address就是这个monitor对象(就是重量锁)的地址。blog
当多个线程同时请求synchronized方法或块时,monitor会设置几个虚拟逻辑数据结构来管理这些多线程。下图是简化了的管理结构。队列
新请求的线程会首先被加入到线程排队队列中,线程阻塞,当某个拥有锁的线程unlock以后,则排队队列里的线程竞争上岗(synchronized是不公平竞争锁,下面还会讲到)。若是运行的线程调用对象的wait()后就释放锁并进入wait线程集合那边,当调用对象的notify()或notifyall()后,wait线程就到排队那边。这是大体的逻辑。资源
同时再看看线程的状态图同步
Blocked就是阻塞状态。
wait()和sleep()最大的不一样在于wait()会释放对象锁,而sleep()不会!wait、sleep、yield区别以下:
由于线程阻塞后进入排队队列和唤醒都须要CPU从用户态转为核心态,尤为频繁的阻塞和唤醒对CPU来讲是负荷很重的工做。同时统计发现,不少对象锁的锁定状态只会持续很短的一段时间,例如一个线程切换周期,这样的话在很短的时间内阻塞线程又很快唤醒线程显然不值得,因此引入了自旋锁概念。
所谓“自旋”,就monitor并不把线程阻塞放入排队队列,而是去执行一个无心义的循环,循环结束后看看是否锁已释放并直接进行竞争上岗步骤,若是竞争不到继续自旋循环,循环过程当中线程的状态一直处于running状态。明显自旋锁使得synchronized的对象锁方式在线程之间引入了不公平。可是这样能够保证大吞吐率和执行效率。
不过虽然自旋锁方式省去了阻塞线程的时间和空间(队列的维护等)开销,可是长时间自旋也是很低效的。因此自旋的次数通常控制在一个范围内,例如10,50等,在超出这个范围后,线程就进入排队队列。
自适应自旋锁,就是自旋的次数是经过JVM在运行时收集的统计信息,动态调整自旋锁的自旋次数上界。
讲到这里彷佛synchronized锁的过程更加丰满了。
不过synchronized在运行过程当中不是一会儿就到对象锁这个级别的,它根据线程竞争状况会通过几回升级变化。这里就出现了另外几种锁。
当多线程环境进入synchronized区域的线程没竞争时,JVM并不会立刻建立对象锁,而是用轻量锁或偏向锁。
不过须要明确的是,轻量锁和偏向锁,都不能代替重量锁,只不过是在没有多线程竞争时,不必用重量锁而无畏的消耗资源。可是一旦出现了多线程竞争时,synchronized区域的轻量锁或偏向锁都会当即升级为重量锁。
轻量锁或偏向锁使用的条件是进入synchronized区域时没有其余任何其余线程在使用。
这时线程t访问对象的synchronized区域时,对象头的标志位Tag状态为01,以及还有1位的偏向信息用于记录这个对象是否可用偏向锁。而后t在对象上申请轻量锁时,若偏向信息为0,代表当前对象还未加锁,或加过偏向锁(加过,注意是加过偏向锁的对象只能被一样的线程加锁,若是不一样的线程想要获取锁,须要先将偏向锁升级为轻量锁,稍后会讲到),在判断对当前对象确实没有被任何其余线程锁住后,便可以在该对象上加轻量锁。
加轻量锁的过程很简单:在当前线程的栈帧(stack frame)中生成一个锁记录(lock record),这个锁记录比前面说的那个对象锁(管理线程队列的monitor)简单多了,它只是对象头的一个拷贝。而后把对象头里的tag改为00,并把这个栈帧里的lock record地址放入对象头里。若操做成功,那就完成了轻量锁操做。若是不成功,说明有线程在竞争,则须要在当前对象上生成重量锁来进行多线程同步,而后将Tag状态改成10,并生成Monitor对象(重量锁对象),对象头里也会放入Monitor对象的地址。最后将当前线程t排队队列中。
轻量锁的解锁过程也很简单就是把栈帧里刚才的那个lock record拷贝到对象头里,若替换成功,则解锁完成,若替换不成功,表示在当前线程持有锁的这段时间内,其余线程也竞争过锁,而且发生了锁升级为重量锁,这时须要去Monitor的等待队列中唤醒一个线程去从新竞争锁。
偏向锁是比轻量锁还轻量的锁机制。当synchronized区域长期都由同一个线程加锁、解锁时,jvm就用偏向锁来作,它的加锁解锁比轻量锁操做起来指令更加简化。不过一旦有其余线程使用synchronized区域,即便没有线程间竞争,也会把偏向锁升级为轻量锁,固然若是发生线程竞争就再升级为对象锁。
锁的公平与不公平:公平锁是指线程得到锁的顺序按照fifo的原则,先排队的先得。非公平锁指每一个线程都先要竞争锁,无论排队前后,因此后到的线程有可能无需进入等待队列直接竞争到锁。非公平锁虽然可能致使某些线程饥饿,可是锁的吞吐率是公平锁好几倍,synchronized是一个典型的非公平锁方案,并且无法作成公平锁。
做者:hexter 连接:https://www.jianshu.com/p/5dbb07c8d5d5 來源:简书 著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。