继上篇博客多线程的并发,我们提到了一种同步互斥的解决方案——synchronized,这篇博客,我们来说说其他解决方案。
lock
还是老规矩,在开始文字描述前,我们先来看看我们的学习思路:
我们介绍图中的前两个:
1、我们是如何使用lock的?
2、lock和synchronized的对比,为什么有了synchronized又有了lock呢?
先看第一个:我们是如何使用lock的。lock是在Java5以后专门提供了锁对象,利用锁可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制,主要集中在java.util.concurrent.locks 包下面。
ReenTrantLock
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。
以下是一个简单的Reentrantlock实现的Demo。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Printer {
private Lock lock = new ReentrantLock();// 锁对象
public void printLetters(char c) {
lock.lock();// 得到锁
try {
for(int i = 0; i<5; i++) {
System.out.print(c);
}
System.out.println();
}finally {
lock.unlock();// 释放锁
}
}
}
值得注意的是:
ReentrantLock需要我们手动去关闭,我们一般是在结合try……catch语句块来使用,在finally中,手动释放锁。
ReentrantLock获取锁定的方式
1. lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁;
2. tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
3. tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
4. lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断。
实现lock接口的类还哟:ReentrantReadWriteLock,其内部类包含ReadLock与WriteLock;其功能更强大。
Lock与Synchronized区别
1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。
总结
synchronized:在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock: 提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。