Java中具有通过Synchronized实现的内置锁,和ReentrantLock实现的显示锁,这两种锁各有各的好处,算是互有补充,今天就来做一个总结。
内置锁获得锁和释放锁是隐式的,进入synchronized修饰的代码就获得锁,走出相应的代码就释放锁。
与Synchronized配套使用的通信方法通常有wait(),notify()。
wait()方法会立即释放当前锁,并进入等待状态,等待到相应的notify并重新获得锁过后才能继续执行;notify()不会立刻立刻释放锁,必须要等notify()所在线程执行完synchronized块中的所有代码才会释放。用如下代码来进行验证:
运行结果
可见读线程开始运行,开始wait过后,写线程才获得锁;写线程走出同步块而不是notify过后,读线程才wait结束,亦即获得锁。所以notify不会释放锁,wait会释放锁。值得一提的是,notifyall()会通知等待队列中的所有线程。
编码模式比较简单,单一,不必显示的获得锁,释放锁,能降低因粗心忘记释放锁的错误。使用模式如下:
Synchronized在JDK1.5及之前性能(主要指吞吐率)比较差,扩展性也不如ReentrantLock。但是JDK1.6以后,修改了管理内置锁的算法,使得Synchronized和标准的ReentrantLock性能差别不大。
ReentrantLock是显示锁,需要显示进行 lock 以及 unlock 操作。
与ReentrantLock搭配的通行方式是Condition,如下:
Condition是被绑定到Lock上的,必须使用lock.newCondition()才能创建一个Condition。从上面的代码可以看出,Synchronized能实现的通信方式,Condition都可以实现,功能类似的代码写在同一行中。而Condition的优秀之处在于它可以为多个线程间建立不同的Condition,比如对象的读/写Condition,队列的空/满Condition,在JDK源码中的ArrayBlockingQueue中就使用了这个特性:
相比于Synchronized要复杂一些,而且一定要记得在finally中释放锁而不是其他地方,这样才能保证即使出了异常也能释放锁。
虽然Synchronized和标准的ReentrantLock性能差别不大,但是ReentrantLock还提供了一种非互斥的读写锁, 也就是不强制每次最多只有一个线程能持有锁,它会避免“读/写”冲突,“写/写”冲突,但是不会排除“读/读”冲突, 因为“读/读”并不影响数据的完整性,所以可以多个读线程同时持有锁,这样在读写比较高的情况下,性能会有很大的提升。
下面用两种锁分别实现的线程安全的linkedlist:
读写锁测试代码:
同步锁测试代码:
结果:
可见读写锁的确是优于纯碎的互斥锁
内置锁最大优点是简洁易用,显示锁最大优点是功能丰富,所以能用内置锁就用内置锁,在内置锁功能不能满足之时在考虑显示锁。
关于两种锁,目前接触到的就是这么多,总结不到位之处,欢迎拍砖。