ReentrantLock

  在jdk5.0以前AQS框架下的锁的性能是远远超过synchronized的,从jdk6.0开始对synchronized作出优化后两者在性能上差别已经不大了。ReentrantLock的有点在于:java

  • 灵活性。加锁解锁的过程是可控的,synchronized加锁解锁过程是编译完成后JVM来实现的
  • 可响应中断。synchronized下没法得到锁的线程是BLOCKED的,不能够相应中断。AQS下全部的锁包括重入锁没法得到锁是WAITING的,可相应中断,能够用来解决死锁问题
  • 超时得到锁。一样能够用来缓解死锁问题
  • 支持公平锁避免饥饿
  • 好基友Condition。虽然synchronized下一样有Object的wait notify方法,但Condition队列更灵活、更可控。
  • 使用CAS来实现原子操做
  • 使用LockSupport中的park unpark实现阻塞原语

1.1 基本使用

  一个老生常谈的多线程自增的例子。因为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.2 中断与死锁

  首先是一个死锁的代码,线程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();
    }
    
}

 

 1.3 超时获取锁与死锁

  这是避免死锁的第二种思路,若是一个线程得到锁失败后等待超过必定时间就会返回,而非一直等待,又看到了乐观锁的影子。

  以下所示,总体结构而言和最初的同样,这理应是一个死锁,可是通过一些等待后最终有了正确的返回结果。

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包裹起来,由于tryLock在未得到锁会返回false。
  • 每次进入if都表明锁获取成功,把里面的代码用try finally包裹起来,这是为了不在执行代码的时候抛出异常致使锁没有释放引起死锁,在finally里释放锁是最安全的行为
  • 若是有屡次得到锁的操做就在try里嵌套try finally
      while (true){
                if (lock1.tryLock()){
                    try {
                        if (lock2.tryLock()){
                            try {
                                //TO DO
                                return;
                            } finally {
                                lock2.unlock();
                            }
                        }
                    } finally {
                        lock1.unlock();
                    }
                }
            }

1.4 公平锁

  公平与否是借助底层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();
    }
}

  当使用公平锁的时候打印结果是左边,能够看到基本上两个线程是交替得到锁;使用非公平锁的结果在右边,明显看到锁是长期被一个线程霸占后又给了另外一个线程。于是公平锁最大的优势是避免饥饿产生,虽然须要付出必定的代价。

相关文章
相关标签/搜索