Lock和synchronized
JDK1.5以后,在锁机制方面引入了新的锁-Lock,在网上的说法都比较笼统,结合网上的信息和我的理解这里做个总结。
java现有的锁机制有两种实现方式,J.DK1.4前是通过synchronized实现,JDK1.5后加入java.util.concurrent.locks包下的各种lock(以下简称Lock)
先说说代码层的区别
synchronized:在代码里,synchronized类似“面向对象”,修饰类、方法、对象。
Lock:不作为修饰,类似“面向过程”,在方法中需要锁的时候lock,在结束的时候unlock(一般都在finally块里)。
例如代码:
Java代码
1 public void method1() {
2 synchronized(this){//旧锁,无需人工释放
3 System.out.println(1);
4 }
5 }
6
7 public void method2() {
8 Lock lock = new ReentrantLock();
9 lock.lock();//上锁
10 try{
11 System.out.println(2);
12 }finally{
13 lock.unlock();//解锁
14 }
15 }
其次说说性能
相关的性能测试网上已经有很多,这里也直接拿来主义,给出结论:
在并发高是,luck性能优势很明显,在低并发时,synchronized也能取得优势。具体的临界范围比较难定论,下面会讨论。
现在来分析它们具体的区别
锁都是原子性 的,也可以理解为锁是否在使用的标记,并且比较和设置这个标记的操作是原子性的,不同硬件平台上的jdk实现锁的相关方法都是native的(比如 park/unpark),所以不同平台上锁的精确度的等级由这些native的方法决定。所以网上经常可以看见的结论是“Lock比 synchronized有更精确的原子操作”说的也是native方法(不得不感慨C才是硬件王道)。
改进:
还有个改进我也想说下,其实很重要的。线程分阻塞(wait)和非阻塞状态,阻塞状态由操作系统(linux、windows等)完成,当前 一个被“锁”的线程执行完毕后,有可能在后续的线程队列里还没分配出一个获取锁而被“唤醒”的非阻塞线程,即所有线程还都是阻塞状态时,就被系统调度(进 入内核的线程是阻塞的),这样会导致内核在用户态和内核态之间来回接换,严重影响锁的性能。在jdk1.6以前主要靠自旋锁来解决,原理是在前一个线程结 束后,争用线程可以做一个空循环,继续占有CPU,等待取锁的机会。当然这样做显然也是浪费时间,只是在两种浪费中选取浪费少的…… jdk1.6后引入了偏向锁,当线程第一次获得了监视对象,之后让监视对象“偏向”这个线程,之后的多次调用则可以避免CAS操作,等于是置了一临时变量来记录位置(类似索引比较)。详细的就涉及到汇编指令了,我也就没太深究,偏向锁性能优于自旋锁,但是还是没有达到HotSpot认为的最佳时间(一个线 程上下文切换的时间)。
总结:
综合来看对于所有的高并发情况,采用Lock加锁是最优选择,但是由于历史遗留等问题,synchronized也还是不能完全被淘汰,同时,在低并发情况下,synchronized的性能还是比Lock好的。