在jdk5.0以前AQS框架下的锁的性能是远远超过synchronized的,从jdk6.0开始对synchronized作出优化后两者在性能上差别已经不大了。ReentrantLock的有点在于:java
一个老生常谈的多线程自增的例子。因为i++的非原子性,在不采起任何措施的状况下最终的结果是小于等于200000的,为了使最终的结果为200000须要采起措施保障i++不可分割,在i++先后加锁便可。安全
虽然用synchronized可实现一样的结果,但用重入锁能够本身来加锁解锁,何况还省了一个大括号不是?多线程
public class myThread implements Runnable { public static ReentrantLock lock = new ReentrantLock(); public static int i=0; @Override public void run() { for (int j=0;j<100000;j++){ lock.lock(); i++; lock.unlock(); } } public static void main(String[] args) throws InterruptedException { myThread thread = new myThread(); Thread t1 = new Thread(thread); Thread t2 = new Thread(thread); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
首先是一个死锁的代码,线程1先得到锁1而后请求得到锁2,线程2先得到锁2而后请求得到锁1。框架
package ReLock; import java.util.concurrent.locks.ReentrantLock; public class deadLock implements Runnable { public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lock; public deadLock(int lock) { this.lock = lock; } @Override public void run() { if (lock == 1){ try { lock1.lockInterruptibly(); Thread.sleep(500); lock2.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); } }else { try { lock2.lockInterruptibly(); Thread.sleep(500); lock1.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { deadLock deadlock1 = new deadLock(1); Thread thread1 = new Thread(deadlock1); deadLock deadlock2 = new deadLock(2); Thread thread2 = new Thread(deadlock2); thread1.start(); thread2.start(); } }
执行以后没有反应且显示正在运行。用jps找到当前死锁进程的pidide
而后用jstack -l pid查看进程的状态,能够看到jstack分析出了死锁,且指出了死锁的缘由。性能
一样的可使用jConsole图形化界面的查看死锁。优化
总之咱们如今有了一个死锁的代码。注意到这里获取不是lock而是lockInterruptibly,意味着没有得到锁的线程能够响应中断。处于WAITING状态的线程响应中断方式是抛出异常,咱们catch到异常后就能够在异常处理逻辑中释放锁。ui
按照这个思路修改代码,在catch中加入释放锁的逻辑。注意响应中断并不会直接释放锁,要在catch逻辑里手动释放锁。而且为了代码的健壮性,先判断当前哪些锁被当先线程持有,释放当前线程持有的锁。在主线程里加入线程中断代码,这样在中断一个线程的时候能够看到另外一个线程完整的执行。this
package ReLock; import java.util.concurrent.locks.ReentrantLock; public class deadLock implements Runnable { public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lock; public deadLock(int lock) { this.lock = lock; } @Override public void run() { if (lock == 1){ try { lock1.lockInterruptibly(); Thread.sleep(500); lock2.lockInterruptibly(); System.out.println("1执行完毕"); } catch (InterruptedException e) { if (lock1.isHeldByCurrentThread()){ lock1.unlock(); } if (lock2.isHeldByCurrentThread()){ lock2.unlock(); } e.printStackTrace(); } }else { try { lock2.lockInterruptibly(); Thread.sleep(500); lock1.lockInterruptibly(); System.out.println("2执行完毕"); } catch (InterruptedException e) { if (lock1.isHeldByCurrentThread()){ lock1.unlock(); } if (lock2.isHeldByCurrentThread()){ lock2.unlock(); } e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { deadLock deadlock1 = new deadLock(1); Thread thread1 = new Thread(deadlock1); deadLock deadlock2 = new deadLock(2); Thread thread2 = new Thread(deadlock2); thread1.start(); thread2.start(); Thread.sleep(100); thread2.interrupt(); } }
但关于利用中断来解决死锁我又想到了新的思路:把lock.lockInterruptibly()换成lock.lock也是能够的,但前提是Thread.sleep要加进去。因此推广这个结论只要可以抛出InterruptedException异常的代码均可以用中断去打断。单独使用lock.lock不能够,由于单独使用lock.lock不会抛出中断异常。spa
好比下面这段代码,死锁的线程不会响应中断。
public class lockInter { private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { lock.lock(); System.out.println("解锁了"); } }); lock.lock(); thread.start(); thread.interrupt(); } }
这是避免死锁的第二种思路,若是一个线程得到锁失败后等待超过必定时间就会返回,而非一直等待,又看到了乐观锁的影子。
以下所示,总体结构而言和最初的同样,这理应是一个死锁,可是通过一些等待后最终有了正确的返回结果。
package ReLock; import java.util.concurrent.locks.ReentrantLock; public class tryAcquireKillDeadLock implements Runnable{ public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); private int num; public tryAcquireKillDeadLock(int num) { this.num = num; } @Override public void run() { if (num ==1){ while (true){ if (lock1.tryLock()){ try { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if (lock2.tryLock()){ try { System.out.println(Thread.currentThread().getName()+" MyJob Done"); return; } finally { lock2.unlock(); } } } finally { lock1.unlock(); } } } }else { while (true){ if (lock2.tryLock()){ try { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if (lock1.tryLock()){ try { System.out.println(Thread.currentThread().getName()+" MyJob Done"); return; } finally { lock1.unlock(); } } } finally { lock2.unlock(); } } } } } public static void main(String[] args) { tryAcquireKillDeadLock r1 = new tryAcquireKillDeadLock(1); tryAcquireKillDeadLock r2 = new tryAcquireKillDeadLock(2); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); } }
这里使用的释放锁的方法比较优雅,在finally里释放锁,虽然在finally前面有一个return语句,但finally里的释放锁的代码也会执行,而且按照从内向外的顺序,在执行完毕后再执行return。
public class testTry { public static int testT(){ try { try { int a =5; //int a = 5/0; return a; }finally { System.out.println("我执行了"); } } finally { System.out.println("我也执行了"); } } public static void main(String[] args) { testT(); } }
另外关注一下这里锁的使用与释放的代码模板,抛去sleep引入的try可使代码简洁一点。
while (true){ if (lock1.tryLock()){ try { if (lock2.tryLock()){ try { //TO DO return; } finally { lock2.unlock(); } } } finally { lock1.unlock(); } } }
公平与否是借助底层AQS实现的,书上说非公平的定义是每次释放锁的时候从等待队列里随机取出一个等待的线程给予锁,对次我存疑。
package ReLock; import java.util.concurrent.locks.ReentrantLock; public class FairReLock implements Runnable { private static ReentrantLock lock = new ReentrantLock(false); @Override public void run() { while (true){ try { lock.lock(); System.out.println(Thread.currentThread().getName()+" 得到锁"); } finally { lock.unlock(); } } } public static void main(String[] args) { FairReLock fairReLock = new FairReLock(); Thread t1 = new Thread(fairReLock); Thread t2 = new Thread(fairReLock); t1.start(); t2.start(); } }
当使用公平锁的时候打印结果是左边,能够看到基本上两个线程是交替得到锁;使用非公平锁的结果在右边,明显看到锁是长期被一个线程霸占后又给了另外一个线程。于是公平锁最大的优势是避免饥饿产生,虽然须要付出必定的代价。