赞
踩
如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:耶瞳空间
从Java 5开始,引入了一个高级的处理并发的java.util.concurrent
包,它提供了大量更高级的并发功能,能大大简化多线程程序的编写。我们知道Java语言直接提供了synchronized关键字用于加锁,但这种锁一是很重,二是获取时必须一直等待,没有额外的尝试机制。而java.util.concurrent.locks
包提供的ReentrantLock用于替代synchronized加锁。
Lock是Java并发编程中很重要的一个接口,它要比synchronized关键字更能直译"锁"的概念,Lock需要手动加锁和手动解锁,一般通过lock.lock()方法来进行加锁,通过lock.unlock()方法进行解锁。一般会在finally块中写unlock( )以防死锁。而ReentrantLock实现了Lock接口。
ReentrantLock是一个可重入的互斥锁,又被称为“独占锁”。可重入表示ReentrantLock锁可以被同一个线程多次获取而不会出现死锁。ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。在“公平锁”的机制下,线程依次排队获取锁;而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。
ReentrantLock和synchronized比较
基本使用示例:
public class Counter {
private final Lock lock = new ReentrantLock();
private int count;
public void add(int n) {
lock.lock();
try {
count += n;
} finally {
lock.unlock();
}
}
}
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
示例代码如下:
public class Demo { static Lock lock = new ReentrantLock(); public static void main(String[] args) { method1(); } public static void method1() { lock.lock(); try { System.out.println("execute method1"); method2(); } finally { lock.unlock(); } } public static void method2() { lock.lock(); try { System.out.println("execute method2"); } finally { lock.unlock(); } } }
public class Demo { static Lock lock = new ReentrantLock(); public static void main(String[] args) { Thread t1 = new Thread(() -> { try { // 如果没有竞争,那么此方法就会获取lock对象锁 // 如果有竞争,就会进入阻塞队列,可以被其他线程用interrupt方法打断 System.out.println("尝试获得锁"); lock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); // 没有获得锁,在等待时被打断了 return; } try { System.out.println("获取到锁了"); } finally { lock.unlock(); } }, "t1"); lock.lock(); t1.start(); try { Thread.sleep(1); System.out.println("打断t1"); t1.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
当tryLock方法里没有传入参数时,默认立刻尝试获得锁:
public class Demo { static Lock lock = new ReentrantLock(); public static void main(String[] args) { Thread t1 = new Thread(() -> { System.out.println("t1线程启动。。。。。。"); if (!lock.tryLock()) { System.out.println("t1线程获取锁失败,返回"); return; } try { System.out.println("t1线程获得了锁"); } finally { lock.unlock(); } }, "t1"); lock.lock(); System.out.println("主线程获得了锁"); t1.start(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("主线程释放锁"); lock.unlock(); } } }
但tryLock还有重载方法tryLock(long time, TimeUnit unit)
,该方法会等待时间内一直尝试获得锁:
public class Demo { static Lock lock = new ReentrantLock(); public static void main(String[] args) { Thread t1 = new Thread(() -> { System.out.println("t1线程启动。。。。。。"); try { if (!lock.tryLock(2, TimeUnit.SECONDS)) { System.out.println("t1线程获取锁失败,返回"); return; } } catch (InterruptedException e) { System.out.println("等待被打断"); e.printStackTrace(); return; } try { System.out.println("t1线程获得了锁"); } finally { lock.unlock(); } }, "t1"); lock.lock(); System.out.println("主线程获得了锁"); t1.start(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("主线程释放锁"); lock.unlock(); } } }
公平锁与非公平锁:
ReentrantLock默认是不公平,但可以根据需要自行设置是否公平。ReentrantLock构造方法源码如下:
/**
* 创建一个ReentrantLock实例
* 该方法等同于调用ReentrantLock(false)
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* 根据传入的公平策略创建ReentrantLock实例
* @param fair true为公平策略,false为非公平策略
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock公平锁相对于非公平锁来说,多线程并发情况下的系统吞吐量偏低,因为需要排队等待。所以ReentrantLock公平锁适应于多线程并发不是很高、倾向于先来先到的应用场景。
ReentrantLock中的条件变量功能,类似于普通synchronized的wait、notify,我们可以使用ReentranLlock锁,配合Condition对象上的await()和signal()或signalAll()方法,来实现线程间协作。与synchronized的wait和notify不同之处在于,ReentrantLock中的条件变量可以有多个,可以实现更精细的控制线程。
在介绍方法的使用之前,先来了解一下Condition是什么。可以把Condition看作是Object监视器的替代品。众所周知,Object有wait()和notify()方法,用于线程间的通信。并且这两个方法只能在synchronized同步块内才可以调用,所有线程的等待和唤醒都需要关联到监视器对象的WaitSet集合。Condition同样可以实现上面的线程通信。不同点在于,synchronized锁对象关联的监视器对象仅有一个,所以等待队列也只有一个。而一个ReentrantLock可以有多个Condition,这样可以根据不同的业务需求,在使用同一个lock锁对象的基础上使用多个等待队列,让不同性质的线程加入到不同的等待队列当中。
AQS当中Condition的实现类是ConditionObject,它是AQS的内部类,所以无法直接实例化。可以配合ReentrantLock来使用。ReentrantLock中有newCondition()的方法,来实例化一个ConditionObject对象,因此可以调用多次newCondition()方法来得到多个等待队列。
使用流程:
示例代码如下:
@Slf4j public class Demo { private static ReentrantLock lock = new ReentrantLock(); // 等烟休息室 static Condition cigaretteRoom = lock.newCondition(); // 等外卖休息室 static Condition eattingRoom = lock.newCondition(); static boolean hasCigarette = false; static boolean hasTakeout = false; public static void main(String[] args) throws InterruptedException { // 小南 new Thread(() -> { lock.lock(); try { log.debug("[{}]", hasCigarette); while (!hasCigarette) { log.debug("没烟,先歇会!"); try { cigaretteRoom.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有烟没?[{}]", hasCigarette); if (hasCigarette) { log.debug("可以开始干活了"); } } finally { lock.unlock(); } }, "小南").start(); // 小女等外卖 new Thread(() -> { lock.lock(); try { log.debug("外卖送到没?[{}]", hasTakeout); while (!hasTakeout) { log.debug("没外卖,先歇会!"); try { eattingRoom.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("外卖送到没?[{}]", hasTakeout); if (hasTakeout) { log.debug("可以开始干活了"); } else { log.debug("没干成活..."); } } finally { lock.unlock(); } }, "小女").start(); // 送烟的来了 Thread.sleep(1000); new Thread(() -> { lock.lock(); try { hasCigarette = true; cigaretteRoom.signal(); } finally { lock.unlock(); } }, "送烟的").start(); // 送外卖的来了 Thread.sleep(1000); new Thread(() -> { lock.lock(); try { hasTakeout = true; eattingRoom.signal(); } finally { lock.unlock(); } }, "送外卖的").start(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。