ReentrantLock锁
java5.0之前,我们对于共享对象访问的机制只有synchronized关键字和volatile变量,现在提供了ReentrantLock
该锁比synchronized更多的灵活处置,比如,synchronized没法去解决死锁,不能中断正在等待获取锁的线程.
ReentrantLock锁
1.可重入
单线程可以重复进入,但必须重复退出。
2.可中断
可以中断正在等待的线程
3可限时
超时不能获得锁,就返回false,不会永久等待构成死锁
4.可轮训
tryLock() ,只有在获取到锁时,才返回true.
可轮训和可限时可以规避死锁
5.公平锁
public ReentrantLock(boolean fair)
public static ReentrantLock fairLock = new ReentrantLock(true);
设置为true时,按照先来先得的原则,后来的被放进等待队列中.
那么怎么选择synchronized和ReentrantLock了
从上面可以看出,ReentrantLock有很多的特性,那么如果想选择可中断,可限时,可轮询等就可以选择ReentrantLock.
锁的书写方式:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
2. 可重入代码如下:
public class TestReentrantLock implements Runnable {
private ReentrantLock lock = new ReentrantLock();
private int a;
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 1000; i++) {
try {
// 加锁
lock.lock();
a++;
} catch (Exception e) {
// TODO: handle exception
} finally {
// 释放锁
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
TestReentrantLock tr = new TestReentrantLock();
Thread t1 = new Thread(tr);
Thread t2 = new Thread(tr);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(tr.a);
}
}
3.可限时
如果在规定的时间内范围内,没有获取到该锁,等返回false,等待.
/**
* 一段时间内,尝试获取锁
* @author Administrator
*
*/
public class TimeReentrantLock implements Runnable{
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
try {
//设置在5秒内获取锁
if(lock.tryLock(5, TimeUnit.SECONDS)){
System.out.println(Thread.currentThread().getName()+"获取锁成功");
Thread.sleep(6000);
}else{
System.out.println(Thread.currentThread().getName()+"获取锁失败");
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
public static void main(String[] args) {
TimeReentrantLock tr = new TimeReentrantLock();
Thread t1 = new Thread(tr,"t1");
Thread t2 = new Thread(tr,"t2");
t1.start();
t2.start();
}
}
结果:
t1获取锁成功
t2获取锁失败
Exception in thread "t2" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)
at java.util.concurrent.locks.ReentrantLock.unlock(Unknown Source)
at com.study.displaylock.TimeReentrantLock.run(TimeReentrantLock.java:25)
at java.lang.Thread.run(Unknown Source)
ReentrantLock源码分析
前面我们看到,ReentrantLock有两种锁,一种是公平锁,一种是非公平锁。
首先,来学习不公平锁
主要是下面三种技术来实现:
1.cas 2.等待队列 3.park
不公锁调用如下:
final void lock() {
if (compareAndSetState(0, 1))//使用cas尝试设置值,如果设置成功,则表明现在没有现成得到锁,那么获取成功
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//再次尝试
}
public final void acquire(int arg) {
if (!tryAcquire(arg) && //再次尝试获取锁,成功则不再做如下操作
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//否则添加到等待队列中
selfInterrupt();
}
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)) {//cas成功,则直接放入队列
pred.next = node;
return node;
}
}
enq(node);.//不成功,强制放入队列
return node;
}
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) &&//如果等待状态singal的就直接返回,singal表示这个线程可以unpack,如果是cancelled,则忽略这些线程
parkAndCheckInterrupt())//否则,pack这些线程
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
释放锁
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//unpark 释放这些线程
return true;
}
return false;
}
读写锁:
ReentrantLock不管是读取还是写入都是互斥的,这对于大量的并发读取操作来说,性能就不会太高,ReentrantReadWriteLock读写锁,可以允许多个线程来读,只有一个线程来写。
它是读读不互斥,读写互斥,写写互斥。
/**
* 读写锁
* 读写互斥 写写互斥 读读不互斥
*
*/
public class TestReentrantRWLock {
private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private ReadLock rl = rw.readLock();
private WriteLock wl = rw.writeLock();
private int i=5;
public void get(){
try {
rl.lock();
Thread.sleep(2000);
System.out.println("i的值是"+i);
rl.unlock();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void add(){
try {
wl.lock();
i++;
System.out.println("i++后的值是:"+i);
Thread.sleep(2000);
wl.unlock();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
final TestReentrantRWLock trl = new TestReentrantRWLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
trl.get();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
trl.get();
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
trl.add();
}
});
//t1.start();
t3.start();
t2.start();
}
}