以上就是给各位分享使用ReentrantLock,其中也会对使用傀儡消耗降低多少进行解释,同时本文还将给你拓展14、Reentrantlock和ReentrantWriteLock、Read锁、3.锁
以上就是给各位分享使用 ReentrantLock,其中也会对使用傀儡消耗降低多少进行解释,同时本文还将给你拓展14、Reentrantlock 和 ReentrantWriteLock、Read 锁、3.锁-ReentrantLock、AQS系列(1)----ReentrantLock中非公平锁的lock和unlock、Java Lock示例 - ReentrantLock等相关知识,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!
本文目录一览:- 使用 ReentrantLock(使用傀儡消耗降低多少)
- 14、Reentrantlock 和 ReentrantWriteLock、Read 锁
- 3.锁-ReentrantLock
- AQS系列(1)----ReentrantLock中非公平锁的lock和unlock
- Java Lock示例 - ReentrantLock
使用 ReentrantLock(使用傀儡消耗降低多少)
大家知道ArrayList并不是一个线程安全的List,当然我们可以用 Vector 来作线程安全的 List 。但下面只是一个例子而已。调用了lock()方法后,执行完后,一定不要忘记进行解锁调用 unlock()方法。看代码:
package com.yao.thread;
import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ArrayListReentrantLock {
private int next;
private Object[] list;
private Lock lock = new ReentrantLock();
public ArrayListReentrantLock(int capacity) {
list = new Object[capacity];
}
public ArrayListReentrantLock() {
this(16);
}
public void add(Object obj) {
try {
lock.lock();
if (next == list.length) {
list = Arrays.copyOf(list, list.length * 2);
}
list[next++] = obj;
} finally {
lock.unlock();
}
}
public Object get(int index) {
try {
lock.lock();
return list[index];
} finally {
lock.unlock();
}
}
public int size(){
try {
lock.lock();
return next;
} finally {
lock.unlock();
}
}
}
14、Reentrantlock 和 ReentrantWriteLock、Read 锁
1、介绍
为什么有了 Synchronized 锁,还需要 ReentranLock(重入锁)锁?
-
1、因为 synchronized 锁导致线程一旦拿不到锁就会一直阻塞,不能通知用户,拿不到锁的情况该如何处理。而 ReentrantLock 锁就可以 tryLock,如果没有拿到锁,就返回用户 false,线程也就不必要继续等待,可以继续往下处理其他事情。其实就三点:支持请求等待、支持请求中断、提供 condition 机制。
-
2、另外构建 Reentranlock 对象时可以传递一个 Boolean 参数,表示是公平还是非公平锁,非公平锁效率高,但是可能有的线程一直得不到锁。
-
3、除此之外,还有读写锁 ReentrantReadWriteLock
2、代码
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import static java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
/**
* [@Author](https://my.oschina.net/arthor) liufu
* @CreateTime 2018/3/22 15:40
*/
public class ReentrantLockTest {
public void reentrantLockTest(){
//公平锁
ReentrantLock fairLock = new ReentrantLock(true);
//非公平锁
ReentrantLock notFairLock = new ReentrantLock(false);
boolean isLock = fairLock.tryLock();
try {
//doSomeThing
}finally {
fairLock.unlock();
}
}
public void reentrantReadWriteLockTest(){
//公平锁
ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true);
//非公平锁
ReentrantReadWriteLock notFairLock = new ReentrantReadWriteLock(true);
ReadLock readLock = fairLock.readLock();
WriteLock writeLock = fairLock.writeLock();
readLock.lock();
// readLock.tryLock();
try {
//doSomeThing
}finally {
readLock.unlock();
}
writeLock.lock();
// writeLock.tryLock();
try {
//do samething
}finally {
writeLock.unlock();
}
}
}
3.锁-ReentrantLock
ReentrantLock
Lock
提供的接口都很简单 加锁还是解锁
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
//Condition 第四章节
Condition newCondition();
直接看ReentrantLock吧
属性
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
初始化
public ReentrantLock() {
sync = new NonfairSync();
}
Sync
继承了AbstractQueuedSynchronizer的一个类
abstract static class Sync extends AbstractQueuedSynchronizer {}
AQS系列(1)----ReentrantLock中非公平锁的lock和unlock
其实一直想专门写aqs(AbstractQueuedSynchronizer),但是发现这个类功能有点广泛,设计理念更是比较庞大。
可能以我的能力应该是先写jdk中应用到这个aqs的类,然后再重新回过头来整理aqs才是比较合理的思路。
而其中最常用而且直接的类应该就是ReentrantLock(重入锁)了。
要看懂这个需要基本了解aqs的一些概念:同步队列以及节点状态位。
这里我们只分析两个核心方法NonfairSync的lock和unlock,其中lock比unlock会简单很多。
先看看NonfairSync的类层次
可以看到非公平锁NonfairSync是间接继承了aqs
可以看到ReentrantLock单纯地实现了Lock接口,里面又有Sync,NonfairSync,FairSync作为内部类,可以理解为一个包装类了。
Lock
通过看ReentrantLock的入口方法的注释来预热一下:
/**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*/
public void lock() {
sync.lock();
}
- 如果当前锁没有被其他线程持有则马上可以获取锁并立刻返回,然后将计数设置为1;
- 如果当前的线程已经持有了锁,那计数+1,且方法立刻返回;
- 如果当前的锁被其他线程持有,然后这个线程则会被线程调度所禁用,并且维持休眠直到能够获取锁,当这个线程能够获取锁的时候,计数器设为1。(这个情况该方法会阻塞)
直接点进去看NonfairSync 都lock方法。
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
和注释描述的一样,这个方法在if的地方直接尝试暴力CAS来获取锁状态,成功的话当前线程置为owner然后就结束了。
然后看失败后的else,
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
注意这个方法是aqs的方法了,也就是说这个应该是一个通用方法(因为锁可以通过继承aqs来实现)!
先看tryAcquire(arg),注意这个方法是一个模板方法,是交由子类实现的
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//当前锁未被占据,代表有机会抢锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//当前线程已经持有了锁
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
我们在ReentrantLock找到了实现。
这里面的思想就是非公平获取的思想:如果当前无锁则直接去暴力CAS抢锁(不管那些在aqs同步队列里面等了大半天的线程节点),或者看看当前线程是不是持有了锁,那这里就可以单线程操作将计数器加一就好了(因此叫做重入锁)。
如果是抢到了锁,或者当前线程已经持有了锁了,那就结束了完事了。
如果还是没拿到锁,至此当前的线程已经两次cas抢锁失败了,是时候要用极端办法了。
回看acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire返回false的话下一步就是看addWaiter(Node.EXCLUSIVE)方法了。
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
先以该线程创建一个独占锁节点作为线程节点,然后队列非空的话尝试cas加入等待队列的队尾;
先不看return 因为不管这里这么样return都是返回当前线程节点的。
先看cas加入队尾失败的情况(cas失败或者队列为空)进入enq方法
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node''s predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
这里的逻辑比较简单,就是先看队列是不是空,空的话建立一个空的node作为head和tail。然后就是不停地自旋cas尝试让当前线程节点加入同步队列的尾端。
意思已经很明确了,这个线程必须一定要进同步队列!注意这里是return t,node的前驱节点,但是调用它的addWaiter方法并没有取他的返回值!我之前就是看这里被enq返回值误导了很久。
继续看acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
进去了acquireQueued方法,获取队列,
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
这里的入参是当前的线程节点node,而且这个方法是不可中断的方法。
这个方法就是再给刚刚入队的方法一个交代,看看他到底应该怎么往下走。
这里同样是一个自旋操作,但是一般情况下这个方法不会像前一个入队方法一样无节制地自旋,无论如何都要入队那种。
如果他是头结点,那么他就要去再试试能不能抢到锁,不行的话就要看是不是应该park休息一下。
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node''s predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don''t park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
这个方法我一开始看真的很抽象。这里我们要先思考这个方法应该怎么实现,这个方法传参是当前线程节点及其前驱,方法名叫做如果取锁失败是否应该休息,那什么时候应该休息呢?你前面如果有很多排队取锁的线程,而且他们个个很生猛的时候,是不是就不要去凑热闹了?
这个方法正是这个意思,先看前一个节点是不是signal状态,是的话就返回true,这时候就不自旋了,可以休息了。
如果前驱的状态>0那就是cancelled了的,坑爹货,那要一直循环看他前面还有没有坑货,直到找到一个不是很坑的货(或者是head节点),重塑节点连接关系。
如果前驱的状态<0且不是singal,那就让他成为signal(这里涉及到aqs知识,请看aqs状态解释),返回false,出去继续自旋。
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
上述方法返回true会进入该方法,意思是休眠而且返回当前节点的中断状态以及清空中断状态。
现在有两个小问题:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
这个方法中为什么这么关注中断状态?
什么时候会能进入到cancelAcquire呢?
我说说我的理解
对于第一个问题:
acquireQueued 方法体明确说了是uninterruptible的,但是线程如果在执行过程中被其他线程提示中断了怎么办呢?那总不能丢失掉中断状态吧,那只能将中断状态保存起来,返回给上层,如果被中断了,然后再在上面的acquire方法调用selfInterrupt,将中断位保存住。核心就是要保证被中断的话中断信息不丢失。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
第二个问题:
如果但看非公平锁的实现是只有在tryAcquire方法中抛异常才能进入这个cancelAcquire,但是这个异常又是锁重入次数超限才能发生的,是只有这种情况了嘛,那也太少几率发生了吧。事实上应该是由于tryAcquire是一个模板方法,是可以给予其他框架和个人来实现的一个方法,方法体里面可以允许自由地抛异常,那么在这种时候,就可以进入cancelAcquire来做一些清理工作了。
这个cancelAcquire方法比较难,看了好几遍都没看懂,以后有机会再来补充了。
这里我们总结一下一个线程调用lock的流程:
- 先会有两次尝试cas取锁的机会
- 都失败的话有一次cas入aqs同步队列尾的机会
- 再失败的话自旋进入同步队列尾端
- 成功入队列之后看情况下一步怎么走:万一能成为队列头 则继续cas尝试获取锁,否则找机会休息一波再战。
unlock
/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() {
sync.release(1);
}
注意的是,如果该线程没持有该锁,则会抛异常。
实现是在aqs里面的:
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease方法是在ReentrantLock里面的
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
回看release,如果是这个线程进入了这个锁不止一次,那就是会返回false;
如果不是的话,那就是进入unparkSuccessor方法来对头部方法解锁;
/**
* Wakes up node''s successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
这个方法的核心思想就是找一个节点唤醒。
一开始会尝试头结点指向如果是坑爹货,那就放弃他了,直接从尾端开始找,找到第一个就唤醒它。
唤醒之后做什么呢?
那就是从acquireQueued方法中醒来,继续自旋看看自己是不是头节点了从而找机会抢锁出队列了。
这里有个疑问没解决:
为什么这个unparkSuccessor方法这么大胆直接将头的下一个节点置空?然后从尾端开始往前找第一个waitstatus成立的节点唤醒呢?为什么不是找最前一个呢?
个人的猜测是在ReentrantLock中,不会发现head指向的下一个节点的是null或者cancelled的情况,for循环是不会进入的。
要解答这个问题,需要明白的是头节点的连接什么时候能够得到重置,还有就是线程节点的waitStatus的值的变化。
解锁的方法比较简单,也没什么可以解决的,如果谁能解答最后这个疑问,麻烦能留言告诉我一下,谢谢!
Java Lock示例 - ReentrantLock
引言
在多线程环境下,通常我们使用 synchronized 关键字来保证线程安全。
大多数情况下,用 synchronized 关键字就足够了,但它也有一些缺点, 所以在 Java Concurrency 包中引入了 Lock API 。从Java 1.5版开始在 java.util.concurrent.locks 包中提供了处理并发的 Concurrency API 的 Lock 锁接口和一些实现类来改进 Object 锁定机制。
Java Lock API中的一些重要接口和类
Java Lock API中的一些重要接口和类是:
- 锁(Lock):这是Lock API的基本接口。它提供了 synchronized 关键字的所有功能,以及为锁定创建不同条件的其他方法,为线程等待锁定提供超时功能。一些重要的方法是 lock() 获取锁,unlock() 释放锁,tryLock() 等待锁定一段时间,newCondition() 创建条件等。
- 条件(Condition):条件对象类似于对象等待通知( Object wait-notify)模型,具有创建不同等待集的附加功能。Condition 对象始终由 Lock 对象创建。一些重要的方法是 await(),类似于Object.wait() 和 signal(),signalAll(),类似于 Object.notify() 和 Object.notifyAll() 方法。
- 读写锁(ReadWriteLock):它包含一对关联的锁,一个用于只读操作,另一个用于写入。只要没有写入线程,读锁可以由多个读取线程同时保持。写锁是独占的。
- 重入锁(ReentrantLock):这是最广泛使用的 Lock 接口实现类。此类以与 synchronized 关键字类似的方式实现 Lock 接口。除了 Lock 接口实现之外,ReentrantLock 还包含一些实用程序方法来获取持有锁的线程,等待获取锁线程等。
synchronized 块
synchronized 块本质上是可重入的,即如果一个线程锁定了监视对象,并且另一个同步块需要锁定在同一个监视对象上,则线程可以进入该代码块。我认为这就是类名是ReentrantLock的原因。让我们通过一个简单的例子来理解这个特性。
public class Test{
public synchronized foo(){
//do something
bar();
}
public synchronized bar(){
//do some more
}
}
如果一个线程进入 foo(),它就会锁定Test对象,所以当它尝试执行 bar() 方法时,允许该线程执行 bar() 方法,因为它已经在 Test 对象上持有锁,即与 synchronized(this) 效果是一样的。
Java Lock 示例 - Java 中的 ReentrantLock
现在让我们看一个简单的例子,我们将使用 Java Lock API 替换 synchronized 关键字。
假设我们有一个 Resource 类,其中包含一些操作,我们希望它是线程安全的,以及一些不需要线程安全的方法。
package com.journaldev.threads.lock;
public class Resource {
public void doSomething(){
//do some operation, DB read, write etc
}
public void doLogging(){
//logging, no need for thread safety
}
}
现在假设我们有一个 Runnable 类,我们将使用 Resource 方法。
package com.journaldev.threads.lock;
public class SynchronizedLockExample implements Runnable{
private Resource resource;
public SynchronizedLockExample(Resource r){
this.resource = r;
}
@Override
public void run() {
synchronized (resource) {
resource.doSomething();
}
resource.doLogging();
}
}
请注意,我使用 synchronized 块来获取 Resource 对象上的锁。我们可以在类中创建一个虚拟对象,并将其用于锁定的目的。
现在让我们看看我们如何使用 Java Lock API 并重写上面的程序而不使用 synchronized 关键字。我们将在Java 中使用 ReentrantLock。
package com.journaldev.threads.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConcurrencyLockExample implements Runnable{
private Resource resource;
private Lock lock;
public ConcurrencyLockExample(Resource r){
this.resource = r;
this.lock = new ReentrantLock();
}
@Override
public void run() {
try {
if(lock.tryLock(10, TimeUnit.SECONDS)){
resource.doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//release lock
lock.unlock();
}
resource.doLogging();
}
}
正如你所看到的,我正在使用 tryLock() 方法来确保我的线程只等待一定的时间,如果它没有获得对象的锁定,它只是记录和退出。另一个要注意的重点是使用 try-finally 块来确保即使 doSomething() 方法调用抛出任何异常也会释放锁定。
Java Lock 与 synchronized 比较
基于以上细节和程序,我们可以很容易地得出 Java Lock 和同步之间的以下差异。
- Java Lock API 为锁定提供了更多的可见性和选项,不像在线程可能最终无限期地等待锁定的同步,我们可以使用tryLock() 来确保线程仅等待特定时间。
- 用同步关键字的代码更清晰,更易于维护,而使用 Lock,我们不得不尝试使用 try-finally 块来确保即使在 lock() 和 unlock() 方法调用之间抛出异常也会释放 Lock。
- 同步块或方法只能覆盖一种方法,而我们可以在一种方法中获取锁,并使用 Lock API 在另一种方法中释放它。
- synchronized 关键字不提供公平性,而我们可以在创建 ReentrantLock 对象时将公平性设置为 true,以便最长等待的线程首先获得锁定。
- 我们可以为 Lock 创建不同的等待条件(Condition),不同的线程可以针对不同的条件来 await() 。
这就是 Java Lock 示例,Java 中的 ReentrantLock 以及使用 synchronized 关键字的比较分析。
作 者:
关于Pankaj
如果你走得这么远,那就意味着你喜欢你正在读的东西。为什么不直接在Google Plus,Facebook或Twitter上与我联系。我很想直接听到你对我的文章的想法和意见。
最近我开始创建视频教程,所以请在Youtube上查看我的视频。
今天关于使用 ReentrantLock和使用傀儡消耗降低多少的讲解已经结束,谢谢您的阅读,如果想了解更多关于14、Reentrantlock 和 ReentrantWriteLock、Read 锁、3.锁-ReentrantLock、AQS系列(1)----ReentrantLock中非公平锁的lock和unlock、Java Lock示例 - ReentrantLock的相关知识,请在本站搜索。
本文标签: