java当中锁有很多种。先把各种锁的含义说清楚。
重量级锁:凡是通过操作系统mutex lock 来实现的锁都是重量级锁。因为java 当中的锁 实现要通过操作系统,需要切换线程状态,效率很低。
轻量级锁:线程之前存在交流竞争关系,并没有严重的竞争关系。
偏向锁:只有单个线程需要获取锁。
可重入锁:表示当一个线程已经获取到了对象的锁,这个线程下去执行这个对象的另外sync方法,能马上获取到锁。
读写锁:分成一个读锁和写锁,两个不冲突,读锁在多线程环境下可以并发执行。
可中断锁:A 获取锁太长时间,导致B等待太久,我们可以中断A线程获得的锁。让B获得锁,synchornized 就不是可中断锁。
公平锁:一个对象的锁尽量是按照先来先获得锁的顺序获得锁。ReentrantReadWriteLock 可以设置成公平锁。synchornized就无法保证公平。
下面就着重解释一下synchornized
这个关键词在以前很早的版本就是重量级锁,但后面1.6版本之后就优化了。有可能是偏向锁和轻量级锁。synchornized锁的信息是存放在对象头信息里面mask word 字段。2位表示锁标志。在jvm 中是通过monitor 对象来实现的。主要指令是monitor enter 和monitor exit。 enter 一次,monitor对象计数+1,exit -1。为0则释放锁。
在解释一下lock
lock 在java.util.concurrent 包下面,这是一个并发包,主要理论是CAS和AQS。compareAndSwap. 顾名思义就是比较后再交换。例如我拿A 去和它比较,如果它真的等于A 那我就把它设置B,如果不等于A 就什么也不操作。这个理论底层也是c++实现的,主要是cmpxchg这个指令。 在比较后操作失败,会进入短暂的自旋,说白了就是重试。但有次数和时间限制。在比如concurrentHashMap 中,还有Volatile 这些字段的使用,在cas volatile 操作主要都封装在unsafe 类中。AQS 在java叫AbstractQueuedSynchronizer,抽象队列同步器。内部其实是一个Node 对象的双向列表。Node 里面核心的属性有thread,prev,next。
拿ReentrantLock源码示例,ReentrantLock内部有一个抽象对象Sync继承自AQS,然后因为ReentrantLock支持公平锁和非公平锁,所以有FairSync和NonFairSync。公平和非公平其实都是针对于加锁而言的。
加锁流程:首先调用tryAcquire内部使用CAS 去尝试修改AQS 中state 字段,如果修改成功说明获得锁成功,程序可以正常执行。当然这个锁是支持可重入的,state可以+1+1。如果没有获得锁,则需要加入队列中,一开始这个链表是空的,会生成一个thread 为空的header 节点,然后将没有获得锁的节点放到尾节点,然后调用LockSupport.park(this)方法进行阻塞线程,操作系统其实采用信号量的机制。
解锁流程:首先tryRelease使用CAS去修改state字段。如果修改成功,则调用LockSupport.unpark方法让header节点的next节点进入执行。AQS 中的列表也会进行变化,如果队列中的第二个节点获得了锁,会将第一个节点的关联断开,然后将获得锁的节点设置成头节点,thread 为空,prev 指向aqs。