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(); } }