JAVA锁漫谈,最好的锁是无锁

关于synchronizedjava

众所周知,JAVA中最简单的加锁方法是用关键字synchronized,咱们可使用这个关键字将一个方法变成线程安全的,也能够将一个代码块变成线程安全的,这样子咱们不须要再担忧多线程同时执行到这段代码会引起的并发问题。同时配合方法wait,notify和notifyall能够很好的实现多线程之间的协做,好比某个线程由于须要等待一些资源,因而调用wait方法将本身设置为waiting状态,其余线程释放或生产这个线程须要的资源的时候须要通知这个线程(notify)将其唤醒,或者通知全部等待当前资源的线程(notifyall)。
安全

然而当功能完成以后咱们彷佛并不知足于此,因而咱们开始考虑这么作的代价是什么,是否能够作的更好。多线程

先说说这么作(使用synchronized)的代价是什么,当多个线程请求临界资源的时候只能有一个线程获得知足,那么其余的线程会作什么呢,他们会被阻塞,直到被通知(notify/notifyall)又有资源的时候才被唤醒进行再一次的锁争用,然后往复的是又只有一个线程能被获得知足,其余的线程继续进入阻塞状态,而这个时候可能会有不断的增长争用线程。性能损耗的关键点在于线程的阻塞操做是由操做系统来完成的,在Linux系统下是由pthread_mutex_lock函数来完成。线程被阻塞以后便进入了内核调度态,这个过程发生了操做系统将保存用户态的上下文进入内核态,这也就是常说的上下文切换,上下文切换代价大,在于操做系统须要将当前线程执行上下文内容(包括堆栈、寄存器等存储的内容)的保存以便以后线程切换回来时候再进行现场恢复。并发

上面能够看出使用synchronized的代价是什么了吧,当竞争激烈的时候会引发频繁的操做系统上下文切换,从而影响系统的性能。下面再来说讲自旋锁。框架


自旋锁的原理函数

自旋锁是对线程阻塞的一种优化,他的原理简单的说就是当线程争用锁失败的时候不当即进入阻塞状态,而是再等一会,由于对于执行时间短的代码这一会可能就会释放锁,而线程就不须要进行一次阻塞与唤醒。等待操做就是让线程多执行几个空指令,至于等待多久这跟具体的处理器实现有关,也有可能处理器根本不支持自旋锁,具体实现的时候咱们能够设置一个临界值,当超过了这个临界值以后咱们就不自旋了,就乖乖进入阻塞状态吧。这种优化对于执行时间短的代码是颇有效的。synchronized使用自旋锁的时机是线程进入等待队列即阻塞的前一步。性能


关于偏向锁优化

偏向锁是java6提供的一种功能,主要是对无竞争条件下的对加锁代码执行的优化,获得优化的地方是省去了对等待队列的更新操做。在竞争条件下,获取锁失败的线程会被放入等待队列,这个队列的更新操做是经过CAS指令来完成的。对于那么一段本部应该被加锁的代码被加了锁,咱们认为每次执行这段被加了锁的代码的时候更新等待队列的操做并非必要的,而CAS操做会延迟本地代码的执行,所以偏向锁是用于优化这个问题的。操作系统


关于Lock线程

Lock是JAVA5增长的内容,在JCU(java.util.concurrent.locks)包下面,做者是并发大师Doug Lea。JCU包提供了不少封装的锁,包括经常使用的ReentrantLock和ReadWriteLock。这些所其实都是依赖java.util.concurrent.AbstractQueuedSynchronizer这个类来实现的,这个类有个简写的名字叫AQS,对这就是著名的AQS。

关于Lock,先说说线程获取Lock锁的时候会引发哪些事件呢。首先AQS是依赖一个被volatile修饰的int变量来标识当前锁的状态的,为0的时候表明当前锁不被任何线程拥有,当线程拿到这个锁的时候会经过CAS操做修改state的状态,那么对于争用失败的线程AQS会怎么办呢,AQS内部维护了一个等待队列,这个队列是纯JAVA实现的,其实现也是很是巧妙的,多线程在经过CAS来获取本身在队列中的位置,同时队列中的线程状态也是阻塞状态,遇到阻塞就头疼了,上面已经介绍过阻塞会带来的性能问题。在源码中咱们能够看到的是AQS经过LockSupport(LockSupport底层依赖Unsafe)将线程阻塞,关于LockSupport我有一篇文章介绍的,其功能是用来代替wait和notity/notifyall的,更好的地方是LockSupport对park方法和unpark方法的调用没有前后的限制,而notify/notifyall必须在wait调用以后调用。尽管如此,这一切并无阻止线程进入阻塞状态,我有点失望。


无锁时代

讲到无锁,必然是Disruptor并发框架,Disruptor底层依赖一个RingBuffer来进行线程之间的数据交换,无锁在于在并发条件下,多线程对RingBuffer的读和写不会涉及到锁,然而由于RingBuffer满或者RingBuffer中没有可消费内容引起的线程等待,那就要另当别论了。简单几句介绍下无锁原理,RingBuffer维护者可读和可写的指针,也叫游标,它指向生产者或消费者须要写或读的位置,而对于指针的更新是由CAS来完成的,这个过程当中咱们不须要加锁/解锁的过程。


后记:

JAVA锁方面的知识主要是要搞清楚不一样的锁的优势与缺点,深刻到操做系统层的实现机制与不一样场景中对应用的性能影响。本文简单的撸了一下JAVA锁从synchronized到无锁的发展以及一些锁的简单原理,主要是抛砖引玉吧,由于介绍的比较简单,对于文中提到的知识不知道的同窗能够深刻了解,我相信你会颇有收获。有些实现的原理介绍可能就一句话,可是实际实现起来是蛮复杂的,须要考虑到的东西是咱们没有写过所不能考虑到的。到这里,若是你的项目中用到了多线程并发,你是否会考虑使用无锁模型来优化你项目中多线程之间的通讯呢。


本文由做者原创,转载需著名来源,做者热爱JAVA技术,但愿多多交流。QQ:378979705

相关文章
相关标签/搜索