一、Lock接口
1.简介
Lock lock = new ReentrantLock();
lock.lock();
try {
//执行同步逻辑
} finally {
lock.unlock();
}
2.Lock接口提供的synchronized关键字不具备的主要特性
3.Lock的API
二、ReentrantLock
1.可重入:
public class ReentrantLockTest {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
for (int i = 1; i <= 3; i++) {
lock.lock();
}
for(int i=1;i<=3;i++){
try {
} finally {
lock.unlock();
}
}
}
}
2.上面的代码通过lock()
方法先获取锁三次,然后通过unlock()
方法释放锁3次,程序可以正常退出。从上面的例子可以看出,ReentrantLock是可以重入的锁,当一个线程获取锁时,还可以接着重复获取多次。在加上ReentrantLock的的独占性,我们可以得出以下ReentrantLock和synchronized的相同点。
-
1.ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。但是实现上两者不同:synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活。一般并发场景使用synchronized的就够了;ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。
-
2.ReentrantLock和synchronized都是可重入的。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。
ps:虽然ReentrantLock叫可重入锁,但是其实jdk中所有的锁都支持可重入
2.公平锁
true
创建公平锁,如果传入的是
false
或没传参数则创建的是非公平锁
// 创建的是公平锁
private ReentrantLock lock = new ReentrantLock(true);
// 创建的是非公平锁
private ReentrantLock lock = new ReentrantLock(false);
// 默认创建非公平锁
private ReentrantLock lock = new ReentrantLock();
3.可响应中断
当使用synchronized实现锁时,阻塞在锁上的线程除非获得锁否则将一直等待下去,也就是说这种无限等待获取锁的行为无法被中断。而ReentrantLock给我们提供了一个可以响应中断的获取锁的方法lockInterruptibly()
。该方法可以用来解决死锁问题。
public class ReentrantLockTest {
static Lock lock1 = new ReentrantLock();
static Lock lock2 = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new ThreadDemo(lock1, lock2));//该线程先获取锁1,再获取锁2
Thread thread1 = new Thread(new ThreadDemo(lock2, lock1));//该线程先获取锁2,再获取锁1
thread.start();
thread1.start();
thread.interrupt();//是第一个线程中断
}
static class ThreadDemo implements Runnable {
Lock firstLock;
Lock secondLock;
public ThreadDemo(Lock firstLock, Lock secondLock) {
this.firstLock = firstLock;
this.secondLock = secondLock;
}
@Override
public void run() {
try {
firstLock.lockInterruptibly();
TimeUnit.MILLISECONDS.sleep(10);//更好的触发死锁
secondLock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
firstLock.unlock();
secondLock.unlock();
System.out.println(Thread.currentThread().getName()+"正常结束!");
}
}
}
}
如果不使用lockInterruptibly()那么当线程1中断时,finally中断的代码就不会被执行锁就无法释放,线程二需要线程1释放锁才能执行,所以导致堵塞,而lockInterruptibly()可以监控到中断从而释放锁避免死锁情况发生
4.超时等待
ReentrantLock还给我们提供了获取锁限时等待的方法tryLock()
,可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。我们可以使用该方法配合失败重试机制来更好的解决死锁问题。
//,此时如果在1秒内没有获得锁那么会返回false
boolean lockSucceed = reentrantLock.tryLock(1, TimeUnit.SECONDS);
三、读写锁(ReadWriteLock)
1.特性
2.提供的方法
3.样例实现
static Map<String, String> map = new HashMap<>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
public static void main(String[] args) {
map.put("test","0000");
for (int i = 0; i < 10; i++) {
new Thread(new ReadThread()).start();
if(i == 5){
new Thread(new WriteThread()).start();
}
}
}
static class ReadThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep((int)Math.random()*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
getData("test");
}
}
static class WriteThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep((int)Math.random()*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setData("test","1234");
}
}
public static String getData(String key) {
r.lock();
try {
System.out.println(map.get(key));
return map.get(key);
} finally {
r.unlock();
}
}
public static String setData(String key, String value) {
w.lock();
try {
System.out.println("写入成功");
return map.put(key, value);
} finally {
w.unlock();
}
}
执行结果:
上述示例中,Cache组合一个非线程安全的HashMap作为缓存的实现,同时使用读写锁的