当前位置: 代码迷 >> 综合 >> 【Java并发锁】Lock
  详细解决方案

【Java并发锁】Lock

热度:74   发布时间:2024-01-27 01:44:47.0

1 前言

本人jdk版本8。

在Lock接口出现之前,java程序靠的是synchronized关键字实现共享锁功能的,而Java SE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能。Concurrent包中已经提供的实现了Lock接口的锁包括:ReentrantLock(可冲入锁)和ReadWriteLock(读写锁)等。其提供的接口方法如下:

public interface Lock {// 获取锁,获取不到则将当前线程加入阻塞队列void lock();// 响应中断的获取锁void lockInterruptibly() throws InterruptedException;// 尝试一次获取锁的动作boolean tryLock();// 有时间限制的获取锁boolean tryLock(long time, TimeUnit unit) throws InterruptedException;// 释放当前线程持有的锁void unlock();
}

它提供了synchronized关键字类似的同步功能,只是在使用是需要显示的获取和释放锁,虽然他缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁的多种synchronized关键字锁不具备的同步特性。官方给出的使用方法如下:

   Lock lock = new ReentrantLock();lock.lock();try{// synchronized block}finally{lock.unLock();}

2 synchronized与Lock的区别

2.1 锁的获取与释放操作

  • synchronized,线程执行到加锁区域会自动获取锁,当加锁区域执行完或执行中抛出异常会自动释放锁,程序猿不用也不能控制锁的获取与释放。
  • Lock,程序猿要手动进行加锁与释放,如果忘记释放锁则可能造成死锁。

2.2 锁状态的可知性

  • synchronized,参与锁竞争的线程不知道锁是否已被获取,也不知道获取锁的线程是不是自己。
  • Lock,通过同步器的getState()能知道锁的获取情况,通过getExclusiveOwnerThread()也能知道独占锁的是哪个线程。

2.3 灵活性

  • synchronized,线程获取锁失败只能阻塞,等到拥有锁的线程释放锁才有可能被唤醒,不能响应中断。
  • Lock,提供了三类方法来对锁进行灵活的获取,获取失败也可以使线程不陷入阻塞,就算阻塞了也能通过中断阻塞线程来取消阻塞。具体见下:
操作 描述
tryLock() 尝试获取一次锁。通过锁的状态来判断是否能获取成功,true则获取到锁,false则返回。
lockInterruptibly() 可中断的获取锁,该方法必须显示的处理(如catch)可能抛出的InterruptedException。若获取锁失败该线程会被加入阻塞队列,并一直检测线程的中断状态,若线程被中断,则会将该线程从阻塞队列中移除并抛出InterruptedException,接下来该线程会从catich代码块继续向下执行。若线程已经获取了锁,则中断除了将线程标志置为true以外不会产生任何影响。(详情见下面代码测试)
tryLock(long time, TimeUnit unit) 在lockInterruptibly()的基础上增加了时间限制,若在指定时间内还没获取到锁则返回false并退出阻塞,从下一行代码接着执行。

3 lockInterruptibly()中断测试

因为InterruptedException不是RuntimeException,所以lockInterruptibly()方法必须显示的处理可能抛出来的InterruptedException。下面代码对lockInterruptibly()进行了catch包围。

1)线程获取锁失败阻塞时被中断

下面开启了两个线程,用同一个ReentrantLock来进行同步。线程1(也可能是线程2)首先执行,获取到锁,进入try区域,执行无限循环;线程2接着执行,因为线程1已经获取到锁,所以陷入阻塞。线程2阻塞后,在main线程中对线程2进行了中断。

public class Test1 {public static void main(String[] args) {MyThread mt1 = new MyThread("线程1");MyThread mt2 = new MyThread("线程2");mt1.start();mt2.start();while (true){if (MyThread.lock.hasQueuedThread(mt2)){System.out.println(mt2.getName()+"进入阻塞队列");mt2.interrupt();System.out.println("中断了"+mt2.getName());break;}}}
}class MyThread extends Thread{static ReentrantLock lock = new ReentrantLock();MyThread(String name){ super(name); }@Overridepublic void run(){try {lock.lockInterruptibly();} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName()+":是否在阻塞队列中——"+lock.hasQueuedThread(Thread.currentThread()));e.printStackTrace();}try {System.out.println(Thread.currentThread().getName()+"进入了lock后面的try区域");while (true){}}finally {if (lock.isHeldByCurrentThread()){lock.unlock();}}}
}

打印信息如下。当线程获取锁失败而加入阻塞队列后,若此时被其它线程中断,该方法会抛出InterruptedException,同时将此线程的节点从阻塞队列移除,该线程会接着执行catch下面的代码。注意一下上面代码的finally块,在unlock()之前我作了个判断,判断当前节点是否是这个锁的持有者,是才unlock,否则线程2被中断后继续执行到unlock会抛出IllegalMonitorStateException,因为释放锁必须要当前线程是锁的持有者。

线程1进入了lock后面的try区域
线程2进入阻塞队列
中断了线程2
线程2:是否在阻塞队列中——false
java.lang.InterruptedException at ....(此处省略异常打印信息 )
线程2进入了lock后面的try区域...(两个线程无限循环执行)

2)线程持有锁时被中断

启动一个线程,线程进行无限循环的判断,判断自己是否被中断,是就退出循环。

public class Test1 {public static void main(String[] args) throws InterruptedException {MyThread mt1 = new MyThread("线程1");mt1.start();Thread.sleep(2000);mt1.interrupt();}
}class MyThread extends Thread{static ReentrantLock lock = new ReentrantLock();MyThread(String name){ super(name); }@Overridepublic void run(){try {lock.lockInterruptibly();} catch (InterruptedException e) {e.printStackTrace();}try {while (true){if (Thread.currentThread().isInterrupted()){System.out.println("线程已被中断");break;}}System.out.println("被中断了我还在执行");}finally {if (lock.isHeldByCurrentThread()){lock.unlock();}}}
}

打印信息如下。显然,持有锁的线程被中断只会将中断标志置为true,而不会使线程释放锁,更不会中断线程的执行。

线程已被中断
被中断了我还在执行Process finished with exit code 0