java多线程-Lock(八)
多线程使用synchronized来保持线程之间同步互斥,jdk1.5中加入了Lock对象也能实现同步效果
ReentrantLock(rɪ’entrənt)类的使用
ReentrantReadWriteLock类的使用
ReentrantLock
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。
lock,unlock的使用
代码段:使用ReentrantLock
package cn.thread.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyService {
private Lock lock = new ReentrantLock();
public void serviceA() {
lock.lock();//serviceA ,serviceB 中的lock是同一把锁哦,谁先获取谁就有控制权。
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "----Ai=" + i);
}
lock.unlock();
}
public void serviceB() {
lock.lock();
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "----Bi=" + i);
}
lock.unlock();
}
}
public class ReentrantDome1 {
public static void main(String orgs[]) {
final MyService start = new MyService();
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceB();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
}
}
lock.lock();lock.unlock();包起来的代码,就和synchronized是一样的效果。
同一个lock对象锁是同一把;代码中serviceA ,serviceB 中的lock是同一把锁哦,谁先获取谁就有控制权。
lock.unlock最好还是放在finally中,这样才能安全的解锁,不然lock住的线程会一直阻塞。
await(),signal() 线程通信
package cn.thread.lock.reentrant;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyService2 {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void serviceA() {
try {
lock.lock();
condition.await();//线程挂起,进入等待中,和wait一样。
System.out.println(Thread.currentThread().getName() + "----Ai=");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void serviceB() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "----Bi=");
condition.signal();//唤醒被await的线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ReentrantDome2 {
public static void main(String orgs[]) {
final MyService2 start = new MyService2();
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceB();
}
}).start();
}
}
结果说明:
A线程先运行,获取到了锁,但是进入await之后,交出锁权限,线程挂起;B线程获取到锁,B线程唤醒A线程,B线程运行完毕,A线程接着运行。
lock的await与,signal,signalAll与对象的wait,notify,notifyAll一毛一样。
多个Condition.await
lock 可以拥有多个Condition,每个Condition可以独自拥有自己的await,signal。
package cn.thread.lock.reentrant;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyService3 {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Condition condition2 = lock.newCondition();
public void serviceA() {
try {
lock.lock();
condition.await();//线程挂起,进入等待中,和wait一样。
System.out.println(Thread.currentThread().getName() + "----Ai=");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void serviceB() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "----Bi=");
condition.signal();//唤醒被await的线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void serviceC() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "----Ci=");
condition2.signal();//condition2的等候,只有condition2.signal才能唤醒哦
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void serviceD() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "----Di=");
condition2.signal();//唤醒被condition2.await的线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ReentrantDome3 {
public static void main(String orgs[]) {
final MyService3 start = new MyService3();
//线程一
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
//线程二
new Thread(new Runnable() {
public void run() {
start.serviceD();
}
}).start();
}
}
说明:
- 线程2中调用了serviceD方法,唤醒的condition2中的await;但是线程1中的await是condition,所有线程2无法唤醒线程1中的等待。
线程1继续挂起。 - 可以看到线程1与线程2还是同一把锁,lock还是同一个,只不过他们condition2不一样而已。如果线程1没有await,那么线程1,2还是存在竞争锁资源的。(他们两个一开始就在竞争锁资源,只不过线程1先获取到了,但是遇到await后线程挂起,交出了锁权限)
可中断锁
lock与synchronized还有一个很大的区别,就是lock是可中断的。我们来看看这个是怎么个中断的
public class ReentrantDome4 {
private Lock lock = new ReentrantLock();
public void serviceA() {
try {
//lock.lock();
//可中断,响应中断。这个和lock是一样的,只不过在lock()的基础上加了一个判断,用来响应线程的中断。
lock.lockInterruptibly();
//1.加入时间等待
Thread.sleep(500);
System.out.println("lock了。。。。。" + Thread.currentThread().getId());
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("中断了.....");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ReentrantDome4 dome4 = new ReentrantDome4();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadA=" + Thread.currentThread().getId());
dome4.serviceA();
}
});
t1.start();
//2加入时间等待
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadB=" + Thread.currentThread().getId());
dome4.serviceA();
}
});
t2.start();
t2.interrupt(); //如果把这个去了,相对来说lock.lockInterruptibly()就和lock.lock()功能一样了。
}
}
解说:线程t1开始执行,t1获取到了锁,这个时候t2.start()开始创建线程,t2.interrupt开始执行线程状态改为停止状态。
t2开始尝试获取锁,发现线程被标记为停止了,就抛异常了,不在继续往下执行。
让interrupt无效的写法
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadB=" + Thread.currentThread().getId());
dome4.serviceA();
}
});
t2.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt();
t2.开始执行,然后t2.interrupt修改状态是无效的,为什么,因为t2已经执行完毕了,t2.interrupt修改状态已经无效了。
看下lock.lockInterruptibly的源码?
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) //检查线程是否被停止了,如果停止则抛异常,不在往下之心
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
lockInterruptibly()在与interrupt()配套时lockInterruptibly才有它的作用,不然lockInterruptibly和lock就没有区别了。
公平锁与非公平锁
Lock锁分为公平锁和非公平锁,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得FIFO。而非公平锁就是一种获取锁的抢占机制,随机获得锁。
private Lock lock = new ReentrantLock();默认是非公平锁
private Lock lock = new ReentrantLock(true);公平锁。
其他方法
getHoldCount():查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
getQueueLength():返回正在等待获取此锁定的线程估计数。比如有10个线程,一个线程先获取了lock锁权限,其他9个线程就需要等待lock释放锁,这个时候返回9.
getWaitQueueLange(Condition condition):查询await等待队列数。
hasQueuedThread(Thread thread):查询thread线程是否正在等待获取此锁定。
hasQueuedThread():查询是否有线程正在等待获取此锁定。
hasWaiters(Condition condition):是否有线程正在等待与此锁定有关的condition条件。
lockInterruptibly():如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常。
tryLock(): 仅在调用时锁定未被锁另一个线程保持的情况下,才获取该锁定
tryLock(long timeout,TimeUnit unit)
ReentrantReadWriteLock的使用
ReentrantReadWriteLock字面意思可以看出是一个读写锁。
ReentrantReadWriteLock有两种锁,一种是读锁,称为共享锁,一个种是写锁为互斥锁。
读写锁的特性:
- 可重入:允许读锁可重入,写锁可重入,但是写锁可以获得读锁,读锁不能获得写锁。 注意:在锁重入中,读锁不能获取写锁
- 锁降级:允许写锁降低为读锁,就是在锁重入中,写锁重入到了读锁中.称为锁降级.
- 中断锁的获取:在读锁和写锁的获取过程中支持中断
- 支持Condition:写锁提供Condition实现
- 监控:提供确定锁是否被持有等辅助方法
读读共享
package cn.thread.lock.reentrant;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyService4 {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private int i = 0;
public void serviceA() {
try {
lock.readLock().lock();
i++;
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "----Ai=" + "i=" + i);
} catch (Exception e) {
} finally {
lock.readLock().unlock();
}
}
public void serviceB() {
try {
lock.readLock().lock();
i++;
System.out.println(Thread.currentThread().getName() + "----Bi=" + "i=" + i);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
public class ReentrantWirteReadDome1 {
public static void main(String orgs[]) {
final MyService4 start = new MyService4();
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceB();
}
}).start();
}
}
输出:
Thread-1—-Bi=i=2
Thread-0—-Ai=i=2
线程1将i加1后,开始休眠100毫秒,线程2这个时候将i加1后变为2;接着两个同时输出i=2;
使用线程1和线程2都使用的是读锁,所以不会加锁.
写写,读写,写读互斥
public void serviceB() {
try {
lock.writeLock().lock();
i++;
System.out.println(Thread.currentThread().getName() + "----Bi=" + "i=" + i);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
输出结果:
Thread-0—-Ai=i=1
Thread-1—-Bi=i=2
将serviceB改为写锁时,serviceA与serviceB为互斥锁(它们都是同一把锁哦)
线程A先执行serviceA方法,将i++后变为1,睡眠100毫秒,线程B执行serviceB,但是由于读写锁互斥,所以B线程被阻塞,A线程睡眠100ms后接着执行;A线程执行完毕后,释放锁,B线程获取到锁,B开始执行i++变为2.
锁可重入
public class ReentrantReadWriteLockDemo2 {
public static void main(String[] args) {
final ReentrantTest test = new ReentrantTest();
new Thread(new Runnable() {
public void run() {
test.serviceA();
}
}).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
test.serviceB();
}
}).start();
}
public static class ReentrantTest {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private volatile int i = 0;
public void serviceA() {
try {
System.out.println("serviceA开始获取读锁=====");
lock.readLock().lock();
System.out.println("serviceA得到读锁=====");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "----Ai=" + "i=" + i);
serviceA1();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
public void serviceA1() {
try {
System.out.println("write1开始获取重入写锁=====");
lock.writeLock().lock();
System.out.println("write2得到写锁=======");
i++;
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public void serviceB() {
try {
System.out.println("read2serviceb获取读锁");
lock.readLock().lock();
System.out.println("read2serviceb得到读锁");
System.out.println(Thread.currentThread().getName() + "----Bi=" + "i=" + i);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println();
lock.readLock().unlock();
}
}
}
}
结果:
serviceA开始获取读锁=====
serviceA得到读锁=====
read2serviceb获取读锁
read2serviceb得到读锁
Thread-1----Bi=i=0
Thread-0----Ai=i=0
write1开始获取重入写锁=====
解说:serviceA,serviceB都是读锁,所以serviceA获取锁还在执行的过程中,sercieB也同样可以获取锁.他们两个一起执行了.
这里虽然没有死锁,但是基本和锁死差不多.线程1先进入执行了serviceA(读锁)然后serviceA调用了serviceA1(写锁)程序开始不往下执行了.因为读锁中是不能重入写锁的(原因看源码可知)
如果是serviceA调用serviceB是可以的,他们读是读锁;serviceA1调用serviceB是可以的,这就是锁降级.
高性能缓存设计
你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
这个时候就可以使用我们的读写锁来实现了。
class MyService4 {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private int i = 0;
public void serviceA() {
try {
lock.readLock().lock();
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + "----Ai=" + "i=" + i);
} catch (Exception e) {
} finally {
lock.readLock().unlock();
}
}
public void serviceB() {
try {
lock.writeLock().lock();
i++;
System.out.println(Thread.currentThread().getName() + "----Bi=" + "i=" + i);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
}
public class ReentrantWirteReadDome1 {
public static void main(String orgs[]) {
final MyService4 start = new MyService4();
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceB();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceB();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceB();
}
}).start();
}
}
输出结果:
Thread-1----Ai=i=0
Thread-0----Ai=i=0
Thread-2----Bi=i=1
Thread-3----Ai=i=1
Thread-4----Ai=i=1
Thread-5----Bi=i=2
Thread-6----Bi=i=3
解说:可以看到线程0,1,3,4都是执行的A方法,他们都是读取值,所以0,1,3,4之间没有互斥。
- 0,1两个线程都是读,所以没有互斥,他们同时执行了。
- 线程2是写锁,由于读写是互斥的,所以他必须等待0,1线程释放锁才能开始写。
- 线程0,1释放锁后,线程2获取到了锁,3,4必须阻塞,因为写读也是互斥锁。所以3,4必须等待2写完后释放锁。
- 3,4都是读锁,所以他们之间没有互斥效果,一起执行了。线程5是写锁,由于3,4获取到了锁,所以5必须等待3,4执行完毕。
- 5,6都是写锁,他们必须顺序执行。因为写写互斥。
这里就是一个很好的读写分离。如果只有一个线程写,其他用户都是读, 那么多个线程一起读没有必要加锁,这样就提高的读的性能。但是读写,写读,读读都是互斥的,所以在读的时候不能写,在写的时候不能读,这样就能很好的保持多线程一致性。
ReentrantReadWriteLock源码分析
不管是ReentrantReadWriteLock还是 ReentrantLock 都是实现了lock接口,然后内部使用sync,底层使用AQS来实现加锁逻辑的.只是ReentrantReadWriteLock有两把锁,一把都锁,一把写锁.
那就来看看ReentrantReadWriteLock是怎么实现读写分类的.
1.需要先来看下这几个变量
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 2的16次方
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // 2的16次方-1 = 65535
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; } //无符号右移16位,得到高16位的值
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } //
(转载)
比如,现在当前,申请读锁的线程数为13个,写锁1个,那state怎么表示?
ReentrantReadWriteLock是用一个32位的int类型来表示读写状态的,怎么表示呢?就是高16位表示读锁线程数,低16位表示写锁状态.
读锁13的二进制为 1101,写锁1的二进制就是1, 那state的二进制表示为00000000 00001101 00000000 00000001,十进制数为851969, 接下在具体获取锁时,需要根据这个851968这个值得出上文中的 13 与 1。
高位运算(读锁状态): 要算成13,只需要将state 无符号向右移位16位置,得出00000000 00001101,就出13(sharedCount这个方法).
低位运算(写锁状态): 根据851969要算成低16位置,只需要用该00000000 00001101 00000000 00000001 & 111111111111111,就可以得出00000001.
2.读锁
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState(); //获取一下状态
//exclusiveCount(c) ,查看是否有写锁并且当前线程不是自己,则返回-1,进入等待队列.
//从这里可以看出来,只有当前对象有写锁的情况下,才会进入阻塞队列,如果都是读锁,则不会进入阻塞队列.
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c); //获取读锁的状态
//readerShouldBlock查看是否需要加锁
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
//这里基本就是一个自旋,不断尝试获取锁;如果没有写的的情况下,读锁不会线程阻塞,线程挂起
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
//如果需要阻塞,说明除了当前线程持有写锁外,还有其他线程已经排队在申请写锁,故,
//即使申请读锁的线程已经持有写锁(写锁内部再次申请读锁,俗称锁降级)还是会失败,
//因为有其他线程也在申请写锁,此时,只能结束本次申请读锁的请求,转而去排队,否则,将造成死锁。
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId()) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove(); //从readHolds中移除当前线程的持有数,然后返回-1,结束尝试获取锁步骤(结束tryAcquireShared 方法)然后去排队获取。
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
3.写锁
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState(); //获取状态
int w = exclusiveCount(c); //获取写锁个数
if (c != 0) { //当前已经存在读锁或者写锁了.
// (Note: if c != 0 and w == 0 then shared count != 0)
//没有写锁或者当前线程不是自己,返回false,进入等待队列;
//假如w==0,则说明没有写锁,那么c!=0就表示当前有写锁,返回false进入阻塞状态.
//假如w!=0,表示当前有写锁,但是当前线程不是自己,说明写锁是别人,则false进入阻塞状态.
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//w!=0,并且写锁就是自己,则状态加1,表示锁重入;(只有在锁重入的情况下才会进入到这步哦)如果是第一次获取到锁,那么c=0才对.
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//获取锁,设置写锁状态.
setState(c + acquires);
return true;
}
//c状态=0,表示没有写锁,没有读锁,开始使用cas机制竞争锁,如果竞争到,则将当前线程设置为自己,如果竞争失败,则返回false,进入等待队列.
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
写锁的逻辑很简单:当前没有写锁,读锁,则尝试获取锁.如果有写锁或者读锁了,则进入阻塞队列.
根据源码解说一下上面提出的:读锁不能重入写锁的原因.(锁可重入实例中)
1.线程1调用了serviceA(读锁),sercieA调用了serviceA1(写锁);
2.sercieA获取到锁后,c!=0;
3.进入第一个if,这个时候没有写锁,所以w=0直接返回false.写锁进入等待队列,当前线程1被挂起.
4.线程1进入了等待中,需要其他锁解锁时唤醒自己,但是其他线程根本就不能获取到锁了;因为其他读锁需要获取锁时发现有写锁状态,自己进入阻塞中;其他写锁获取锁时发现有读锁,自己进入阻塞中.就这样线程就一直处在等待中.
这里有没有感觉到死锁,最大的问题是这个死锁还不能被jstack分析出来.因为他并没有死锁,只是在等待解锁中.
什么时候选择用 ReentrantLock 代替 synchronized
既然如此,我们什么时候才应该使用 ReentrantLock 呢?答案非常简单 —— 在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。
结束语
Lock 框架是同步的兼容替代品,它提供了 synchronized 没有提供的许多特性,它的实现在争用下提供了更好的性能。但是,这些明显存在的好处,还不足以成为用 ReentrantLock 代替 synchronized 的理由。相反,应当根据您是否 需要 ReentrantLock 的能力来作出选择。大多数情况下,您不应当选择它 —— synchronized 工作得很好,可以在所有 JVM 上工作,更多的开发人员了解它,而且不太容易出错。只有在真正需要 Lock 的时候才用它。在这些情况下,您会很高兴拥有这款工具。
在《深入理解java虚拟机》书中也提到过,synchronized在jdk1.6之后有了很大的性能提升,几乎和reentrantLock性能差不多了,而且在jdk开发团队也说过,尽量使用原生语法synchronized,他们会在后续中继续优化。而且jdk1.6中加入了偏向锁,轻量级锁,自旋锁,自适应自旋锁,等优化。所以在没必要纠结的情况下尽量使用synchronized。
参考文献:
https://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html
《java多线程编程核心技术》
https://blog.csdn.net/prestigeding/article/details/53286756