版权声明 :
本文为博主原创文章,如需转载,请注明出处(https://blog.csdn.net/F1004145107/article/details/85163337)
本文大纲
1.ReentrantLock
怎么用
2.ReentrantLock与
synchronized
3.ReentrantLock
的进阶使用
1.ReentrantLock(boolean fair){}
构造器,默认为false非公平锁
FairSync
公平锁 , NonfairSync
非公平锁
公平锁
线程A正在占用锁,线程B正在等待A释放锁,此时线程C来了,C会直接被放到等待线程队列的尾部
非公平锁
如上场景,线程C来了之后会立刻进行争抢锁的操作,抢不到锁才会被加入到等待线程队列的尾部
2.void lock()
上锁,需要先获取当前ReentranLock对象
使用该方法后必须使用unlock
进行释放锁,且最好在finally
块中执行
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
try {
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
注意,这里的ReentranLock
对象一定要是属于类的,不能是局部变量,否则锁是没有意义的
3.boolean tryLock()
/ boolean tryLock(long timeout, TimeUnit unit)
尝试重新去获取锁,无论是否成功都会返回boolean值
支持设置重试时间,当时间到了之后还没有获取到锁,就会返回false
4.void lockInterruptibly()
与lock()
一样,都是上锁,但是该方法支持中断,调用Thread.interrupt()
方法即可中断该线程
5.boolean isHeldByCurrentThread()
查询当前线程是否持有锁
6. Condition newCondition()
返回Condition对象,该对象方法与Object
中的wait,notify等方法功能类似,会在下面有详细的介绍
相同
二者都是可重入锁
public static ReentrantLock lock = new ReentrantLock();
//ReentrantLock
public void lockMethod1() {
try {
lock.lock();
lockMethod2();
} finally {
lock.unlock();
}
}
public void lockMethod2() {
try {
lock.lock();
} finally {
lock.unlock();
}
}
//synchronized
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
如果已经获得了method1的锁,那么就会自动获取method2的锁
synchronized
在优化之后和ReentranLock
一样都是自旋锁,避免了线程进入阻塞状态
区别
synchronized是关键字,是在JVM层面上实现的,如果出现了异常情况,JVM会自动释放锁资源,而不会造成死锁
ReentranLock
是java代码实现的,出现异常之后并不会自动释放锁资源,所以必须要在finally
代码块中进行锁资源的释放
ReentranLock
我们可以选择锁的争夺机制,可以选择定时等待的功能,也可以选择在合适的时候进行中断
在资源竞争不激烈的状况下synchronized
会更适合我们,它可以提供更为简单的操作,以及更好的可读性,
在资源竞争激烈的情况下,ReentranLock
更有优势一些,因为它可以为我们提供更多更强大的功能,以及更细
粒度的操作,方便我们在复杂的环境下进行锁的操作
常用API
// 当前线程进入等待状态,可以被中断
void await()
// 当前线程进入等待状态,等到达指定时间后重新抢夺锁,可以被中断
boolean await(long time, TimeUnit unit)
// 当前线程进入等待状态,等到达指定时间后重新抢夺锁,如果在时间未到之前被收到signal()
// 或者signalAll(),则返回指定时间 - 已等待的时间,可以被中断
long awaitNanos(long nanosTimeout)
// 当前线程进入等待状态,不可以被中断
void awaitUninterruptibly()
// 当前线程进入等待状态,与awaitNanos相似,指定一个等待日期,返回是否到了指定日期
boolean awaitUntil(Date deadline)
// 唤醒因为调用await相关方法而进入等待队列中的线程,会唤醒第一个
void signal()
// 唤醒因为调用await相关方法而进入等待队列中的线程,会唤醒全部
void signalAll()
Condition
中的方法与Object
中的方法很相似,那么为什么我们要使用Condition
呢
我们在使用的其实是Condition
的一个实现类ConditionObject
,ConditionObject
是AQS中的一个内部类,所以机制和AQS一样,内部都维护了一个队列,
在调用await()
等系列方法的时候其实就是将当前线程加入到了当前ConditionObject
中的等待队列中,说到这可能有同学已经知道它的其它用法了,那就是多个ConditionObject
来实现线程之间的通讯
我们来看一个例子,有俩个线程,我们要求线程在启动后打印num
字段的值,并且要求线程A一直打印1,线程B一直打印0
/**
* @author wise
*/
public class ConditionTest {
private static ReentrantLock lock = new ReentrantLock();
static Condition firstCondition = lock.newCondition();
static Condition secondCondition = lock.newCondition();
private static int num = 0;
public static void main(String[] args) {
ConditionTest test = new ConditionTest();
Thread threadA = new Thread(new ThreadA(), "线程A");
Thread threadB = new Thread(new ThreadB(), "线程B");
threadA.start();
threadB.start();
}
static class ThreadA extends Thread {
@Override
public void run() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "正在运行");
while (num == 0) {
num++;
secondCondition.signal();
System.out.println(Thread.currentThread().getName() + "-num : " + num);
firstCondition.await();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
static class ThreadB extends Thread {
@Override
public void run() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "正在运行");
while (num == 1) {
firstCondition.signal();
num = 0;
System.out.println(Thread.currentThread().getName() + "-num : " + num);
secondCondition.await();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
打印结果如下 :
线程A-num : 1
线程B-num : 0
线程A-num : 1
线程B-num : 0
线程A-num : 1
线程B-num : 0
线程A-num : 1
线程B-num : 0
线程A-num : 1
线程B-num : 0
线程A-num : 1
网上还有一个进阶的例子 :
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[5];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock(); //获取锁
try {
// 如果“缓冲已满”,则等待;直到“缓冲”不是满的,才将x添加到缓冲中。
while (count == items.length)
notFull.await();
// 将x添加到缓冲中
items[putptr] = x;
// 将“put统计数putptr+1”;如果“缓冲已满”,则设putptr为0。
if (++putptr == items.length) putptr = 0;
// 将“缓冲”数量+1
++count;
// 唤醒take线程,因为take线程通过notEmpty.await()等待
notEmpty.signal();
// 打印写入的数据
System.out.println(Thread.currentThread().getName() + " put "+ (Integer)x);
} finally {
lock.unlock(); // 释放锁
}
}
public Object take() throws InterruptedException {
lock.lock(); //获取锁
try {
// 如果“缓冲为空”,则等待;直到“缓冲”不为空,才将x从缓冲中取出。
while (count == 0)
notEmpty.await();
// 将x从缓冲中取出
Object x = items[takeptr];
// 将“take统计数takeptr+1”;如果“缓冲为空”,则设takeptr为0。
if (++takeptr == items.length) takeptr = 0;
// 将“缓冲”数量-1
--count;
// 唤醒put线程,因为put线程通过notFull.await()等待
notFull.signal();
// 打印取出的数据
System.out.println(Thread.currentThread().getName() + " take "+ (Integer)x);
return x;
} finally {
lock.unlock(); // 释放锁
}
}
}
public class ConditionTest2 {
private static BoundedBuffer bb = new BoundedBuffer();
public static void main(String[] args) {
// 启动10个“写线程”,向BoundedBuffer中不断的写数据(写入0-9);
// 启动10个“读线程”,从BoundedBuffer中不断的读数据。
for (int i=0; i<10; i++) {
new PutThread("p"+i, i).start();
new TakeThread("t"+i).start();
}
}
static class PutThread extends Thread {
private int num;
public PutThread(String name, int num) {
super(name);
this.num = num;
}
public void run() {
try {
Thread.sleep(1); // 线程休眠1ms
bb.put(num); // 向BoundedBuffer中写入数据
} catch (InterruptedException e) {
}
}
}
static class TakeThread extends Thread {
public TakeThread(String name) {
super(name);
}
public void run() {
try {
Thread.sleep(10); // 线程休眠1ms
Integer num = (Integer)bb.take(); // 从BoundedBuffer中取出数据
} catch (InterruptedException e) {
}
}
}
}
上面的例子是一个缓存空间中对Condition
的运用,通过Condition
来保证缓存空间内一定是先存后取
可能有同学会有疑问,为什么我什么也没做,并没有将pX系列线程与notFull绑定,怎么我调用signal()
方法时唤醒的就是pX系列线程呢,其实上面已经有讲了,因为在调用await()
系列方法时其实就是将当前线程放入到了当前Condition
对象中的等待队列中,我们来看一下源码
//要解决上述疑问其实我们只需要看第二个方法addConditionWaiter()方法即可
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//将当前等待的线程加入到等待队列中
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//创建当前线程节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//如果尾部节点为null,那么当前线程节点就是头部节点
if (t == null)
firstWaiter = node;
else //否则尾部节点的下一个节点就是当前线程节点
t.nextWaiter = node;
lastWaiter = node;
return node;
}
剩下的源码如果大家有兴趣可以自行去翻阅
本文是主要关于怎么使用ReentrantLock
,以及我们为什么要用ReentrantLock
,而不是synchronized
,其实如果不是已经确定会出现大量的线程竞争,那么最好还是用synchronized
,因为通俗易懂,而且对操作没有很大的要求,以及最后叙述了关于ReentrantLock
的进阶应用,希望能对你有所帮助
本文在讲述Condition的时候涉及到了一部分AQS的实现,AQS就是(AbstractQueuedSynchronizer
),可以说关于整个Lock
最核心的部分就是这里了,本文并没有打算讲的特别深,而且这部分我自己也没有研究的特别深入,如果以后有机会的话我会出相关的博客