您的位置:首页 > 编程语言 > Java开发

Java并发编程札记-(四)JUC锁-02Lock与ReentrantLock

2017-12-09 23:50 274 查看
今天学习Lock与ReentrantLock。

Java中的锁有两种,synchronized与Lock。因为使用synchronized并不需要显示地加锁与解锁,所以往往称synchronized为隐式锁,而使用Lock时则相反,所以一般称Lock为显示锁。想了解synchronized更多请移步Java并发编程札记-(一)基础-06synchronized详解

synchronized修饰方法或语句块,所有锁的获取和释放都必须出现在一个块结构中。当需要灵活地获取或释放锁时,synchronized显然是不符合要求的。Lock接口的实现允许锁在不同的范围内获取和释放,并支持以任何顺序获取和释放多个锁。

一句话,Lock实现比synchronized更灵活。但凡事有利就有弊,不使用块结构锁就失去了使用synchronized修饰方法或语句时会出现的锁自动释放功能,在大多数情况下,Lock实现需要手动释放锁。

除了更灵活之外,Lock还有以下优点:

Lock 实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。

Lock 类还可以提供与隐式监视器锁完全不同的行为和语义,如保证排序、非重入用法或死锁检测。如果某个实现提供了这样特殊的语义,则该实现必须对这些语义加以记录。

ReentrantLock是一个可重入的互斥锁。顾名思义,“互斥锁”表示在某一时间点只能被同一线程所拥有。“可重入”表示锁可被某一线程多次获取。

当锁没有被某一线程占有时,调用lock()方法的线程将成功获取锁。可以使用isHeldByCurrentThread()和 getHoldCount()方法来判断当前线程是否拥有该锁。

ReentrantLock既可以是公平锁又可以是非公平锁。当此类的构造方法ReentrantLock(boolean fair) 接收true作为参数时,ReentrantLock就是公平锁,线程依次排队获取公平锁,即锁将被等待最长时间的线程占有。与默认情况(使用非公平锁)相比,使用公平锁的程序在多线程环境下效率比较低。而且公平锁不能保证线程调度的公平性,tryLock方法可在锁未被其他线程占用的情况下获得该锁。

方法列表

//构造方法摘要
ReentrantLock()
//创建一个 ReentrantLock 的实例。
ReentrantLock(boolean fair)
//创建一个具有给定公平策略的 ReentrantLock。
//方法摘要
int getHoldCount()
//查询当前线程保持此锁的次数。
protected  Thread   getOwner()
//返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。
protected  Collection<Thread>   getQueuedThreads()
//返回一个 collection,它包含可能正等待获取此锁的线程。
int    getQueueLength()
//返回正等待获取此锁的线程估计数。
protected  Collection<Thread>   getWaitingThreads(Condition condition)
//返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
int    getWaitQueueLength(Condition condition)
//返回等待与此锁相关的给定条件的线程估计数。
boolean    hasQueuedThread(Thread thread)
//查询给定线程是否正在等待获取此锁。
boolean    hasQueuedThreads()
//查询是否有些线程正在等待获取此锁。
boolean    hasWaiters(Condition condition)
//查询是否有些线程正在等待与此锁有关的给定条件。
boolean    isFair()
//如果此锁的公平设置为 true,则返回 true。
boolean    isHeldByCurrentThread()
//查询当前线程是否保持此锁。
boolean    isLocked()
//查询此锁是否由任意线程保持。
void   lock()
//获取锁。
void   lockInterruptibly()
//如果当前线程未被中断,则获取锁。
Condition  newCondition()
//返回用来与此 Lock 实例一起使用的 Condition 实例。
String toString()
//返回标识此锁及其锁定状态的字符串。
boolean    tryLock()
//仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
boolean    tryLock(long timeout, TimeUnit unit)
//如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
void   unlock()
//试图释放此锁。


使用最典型的代码如下

class X {
private final ReentrantLock lock = new ReentrantLock();
// ...

public void m() {
lock.lock();  // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}


修改下Java并发编程札记-(一)基础-06synchronized详解中火车票订票系统实例,使用ReentrantLock实现线程安全。

例6:火车票订票系统-ReentrantLock实现线程安全版

import java.util.concurrent.locks.ReentrantLock;

public class SellTickets {

public static void main(String[] args) {
TicketsWindow tw1 = new TicketsWindow();
Thread t1 = new Thread(tw1, "一号窗口");
Thread t2 = new Thread(tw1, "二号窗口");
t1.start();
t2.start();
}
}

class TicketsWindow implements Runnable {
private int tickets = 1;
private final ReentrantLock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
lock.lock();
try {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "还剩余票:" + tickets + "张");
--tickets;
System.out.println(Thread.currentThread().getName() + "卖出一张火车票,还剩" + tickets + "张");
} else {
System.out.println(Thread.currentThread().getName() + "余票不足,暂停出售!");
try {
Thread.sleep(1000 * 60);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
}


在某些方法的参数中出现了Condition,在后面我们会详细介绍Condition。

总结

与synchronized 相比ReentrantLock的使用更灵活。Lock接口的实现允许锁在不同的范围内获取和释放,并支持以任何顺序获取和释放多个锁。

ReentrantLock具有与使用 synchronized 相同的一些基本行为和语义,但功能更强大。包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。

ReentrantLock具有synchronized所没有的许多特性,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁。

ReentrantLock可伸缩性强,应当在高度争用的情况下使用它。

本文就讲到这里,想了解Java并发编程更多内容请参考:

Java并发编程札记-目录

参考:

JDK 5.0 中更灵活、更具可伸缩性的锁定机制

END.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: