版权声明 :
本文为博主原创文章,如需转载,请注明出处(https://blog.csdn.net/F1004145107/article/details/85163337)
本文大纲
1.ReentrantLock
怎么用
2.ReentrantLock与
synchronized
3.ReentrantLock
的进阶使用
常用API
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等方法功能类似,会在下面有详细的介绍
ReentranLock与synchronized
-
相同
-
二者都是可重入锁
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
更有优势一些,因为它可以为我们提供更多更强大的功能,以及更细 粒度的操作,方便我们在复杂的环境下进行锁的操作
-
Condition(进阶应用)
-
常用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
最核心的部分就是这里了,本文并没有打算讲的特别深,而且这部分我自己也没有研究的特别深入,如果以后有机会的话我会出相关的博客