Java- 技术专题 -Synchronized 和 lock 区别
在分布式开发中,锁是线程控制的重要途径。
Java 为此提供了两种锁机制,synchronized 和 lock。ReentrantLock 在内存上的语义与 synchronized 相同,但是它提供了额外的功能,可以作为一种高级工具。当需要一些可定时,可轮询,可中断的锁获取操作,或者希望使用公平锁,或者使用非块结构的编码时才应该考虑 ReetrantLock。
在业务并发简单清晰的情况下推荐 synchronized,在业务逻辑并发复杂,或对使用锁的扩展性要求较高时,推荐使用 ReentrantLock 这类锁。
一、synchronized 和 lock 用法区别
synchronized:在需要同步的对象中加入此控制,synchronized 可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
lock:需要显示指定起始位置和终止位置。一般使用 ReentrantLock 类做为锁,多个线程中必须要使用一个 ReentrantLock 类做为对象才能保证锁的生效。且在加锁和解锁处需要通过 lock() 和 unlock() 显示指出。所以要在 finally 块中写 unlock() 以防死锁。
二、synchronized 和 lock 性能区别
synchronized 是托管给 JVM 执行的,而 lock 是 Java 写的控制锁的代码。在 Java1.5 中,synchronized 是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。
相比之下使用 Java 提供的 Lock 对象,性能更高一些。但是到了 Java1.6,发生了变化。synchronized 在语义上很清晰,进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。
导致在 Java1.6 上 synchronized 的性能并不比 Lock 差。官方也表示,他们也更支持 synchronized,在未来的版本中还有优化余地。
说到这里,还是想提一下这两种机制的具体区别。synchronized 原始采用的是 CPU悲观锁机制,即线程获得的是排他锁。排他锁意味着其他线程只能依靠阻塞来等待线程释放锁。
而在 CPU 转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起 CPU 频繁的上下文切换导致效率很低。
而 Lock 用的是乐观锁机制。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是 CAS 操作(Compare and Swap)。进一步研究 ReentrantLock 的源码,会发现其中比较重要的获得锁的一个方法是 compareAndSetState。这里其实就是调用的 CPU 提供的特殊指令。
现代的 CPU 提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
三、synchronized 和 lock 用途区别
synchronized 原语和 ReentrantLock 在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用 ReentrantLock ,特别是遇到下面两种需求的时候。
1️⃣ 某个线程在等待一个锁的控制权的这段时间需要中断。
2️⃣ 分开处理一些 wait-notify,ReentrantLock 里面的 Condition 应用,能够控制 notify 哪个线程。
3️⃣ 公平锁功能,每个到来的线程都将排队等候。
先说第一种情况,ReentrantLock 的 lock 机制有 2 种,忽略中断锁和响应中断锁,这带来了很大的灵活性。比如:如果 A、B 两个线程去竞争锁,A 线程得到了锁,B 线程等待,但是 A 线程这个时候实在有太多事情要处理,就是一直不返回,B 线程可能就会等不及了,想中断自己,不再等待这个锁了,转而处理其他事情。这个时候 ReentrantLock 就提供了两种机制:可中断/可不中断:
①B 线程中断自己(或者别的线程中断它),但是 ReentrantLock 不去响应,继续让 B 线程等待,你再怎么中断,我全当耳边风(synchronized 原语就是如此);
②B 线程中断自己(或者别的线程中断它),ReentrantLock 处理了这个中断,并且不再等待这个锁的到来,完全放弃。
四、总结
1️⃣ lock 是一个接口,而 synchronized 是 Java 的一个关键字,synchronized 是内置的语言实现。
2️⃣ 异常是否释放锁:synchronized 在发生异常时候会自动释放占有的锁,因此不会出现死锁;而 lock 发生异常时候,不会主动释放占有的锁,必须手动 unlock 来释放锁,可能引起死锁的发生。(所以最好将同步代码块用 try catch 包起来,finally 中写入 unlock,避免死锁的发生。)
3️⃣ 是否响应中断
lock 等待锁过程中可以用 interrupt 来中断等待,而 synchronized 只能等待锁的释放,不能响应中断。
4️⃣ 是否知道获取锁:Lock 可以通过 trylock 来知道有没有获取锁,而 synchronized 不能。
5️⃣ Lock 可以提高多个线程进行读操作的效率。(可以通过 ReadWriteLock 实现读写分离)
6️⃣ 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于 synchronized。所以说,在具体使用时要根据适当情况选择。
7️⃣ synchronized 使用 Object 对象本身的 wait 、notify、notifyAll 调度机制,而 Lock 可以使用 Condition 进行线程之间的调度。
评论