java并发包学习系列:重入锁与Condition条件

重入锁

  这里介绍一下synchronized、wait、notify方法的替代品(或者说是增强版)-重入锁。重入锁是可以完全替代以上的内容的。并且重入锁的性能是远高于synchronized的,但是jdk6.0开始,jdk对synchronized做了大量的优化,使得两者性能差距不大。

  重入锁使用java.util.concurrent.locks.ReentrantLock类来实现。它的几个重要方法如下:

lock():获得锁,如果锁已经被占用,则等待。
lockInterruptibly():获得锁,但优先响应中断。
tryLock():尝试获得锁,如果成功,返回true,失败返回false。该方法不等待,立即返回。
tryLock(long time, TimeUnit unit):在给定时间内尝试获得锁。
unLock():释放锁。

注意:把解锁操作lock.unlock()放到finally子句非常重要。这样保证即使在临界区的代码抛出了异常,锁也必须释放,否则,其他线程将永远阻塞.

重入锁简单案例: lock与unLock

/** * Created by niehongtao on 16/7/8. * 3.1重入锁 */
public class ReenterLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
            }
        }
    }


    public static void main(String[] args) throws InterruptedException {
        ReenterLock rl = new ReenterLock();
        Thread t1 = new Thread(rl);
        Thread t2 = new Thread(rl);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

  上述代码使用重入锁保护临界区资源i,确保了多线程对i操作的安全性。从这段代码可以看到,与synchronized相比,重入锁有着显示的操作过程。开发人员必须手动指定何时加锁,何时释放锁。也正因为这样,重入锁对逻辑控制的灵活性要远远好于synchronized,但值得注意的是,在提出临界区时,必须记得释放锁,否则其他线程就没有机会再访问临界区了。

  对于重入锁,同一个线程可以多次获得锁,但是释放锁的时候,也必须释放相同次数。否则会产生异常。

重入锁的中断响应:lockInterruptibly

/** * Created by niehongtao on 16/7/8. * 中断响应 */
public class IntLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    /** * 控制加锁顺序,方便构造死锁 * * @param lock */
    public IntLock(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            if (lock == 1) {
                lock1.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
                lock2.lockInterruptibly();
            } else {
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
                lock1.lockInterruptibly();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock1.isHeldByCurrentThread()) {
                lock1.unlock();
            }
            if (lock2.isHeldByCurrentThread()) {
                lock2.unlock();
            }
            System.out.println(Thread.currentThread().getId() + ":线程退出");
        }

    }

    public static void main(String[] args) throws InterruptedException {
        IntLock il1 = new IntLock(1);
        IntLock il2 = new IntLock(2);
        Thread t1 = new Thread(il1);
        Thread t2 = new Thread(il2);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        // 中断其中一个线程
        t2.interrupt();
    }
}

锁申请等待限时:lock.tryLock的两个方法

/** * Created by niehongtao on 16/7/8. * 锁申请等待延时 */
public class TimeLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();



    @Override
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                Thread.sleep(6000);
            } else {
                System.out.println("get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

    }


    public static void main(String[] args) {
        TimeLock tl = new TimeLock();
        Thread t1 = new Thread(tl);
        Thread t2 = new Thread(tl);
        t1.start();
        t2.start();
    }
}

公平锁:ReentrantLock的构造函数可以增加一个参数

Condition条件

  如果大家理解了obj.wait和obj.notify方法的话,那么就很容易理解Condition对象了。它和wait和notify方法的作用是大致相同的。但是wait和notify方法是和synchronized关键字合作使用的,而Condition是与重入锁相关联的。通过Condition的newCondition()方法可以生成一个与当前重入锁绑定的Condition实例。利用Condition对象,我们就可以让线程在合适的时间等待,或者在某一个特定的时间得到通知,继续执行。

Condition提供的基本方法如下:

 void   await() 
          Causes the current thread to wait until it is signalled or interrupted.
 boolean    await(long time, TimeUnit unit) 
          Causes the current thread to wait until it is signalled or interrupted, or the specified waiting time elapses.
 long   awaitNanos(long nanosTimeout) 
          Causes the current thread to wait until it is signalled or interrupted, or the specified waiting time elapses.
 void   awaitUninterruptibly() 
          Causes the current thread to wait until it is signalled.
 boolean    awaitUntil(Date deadline) 
          Causes the current thread to wait until it is signalled or interrupted, or the specified deadline elapses.
 void   signal() 
          Wakes up one waiting thread.
 void   signalAll() 
          Wakes up all waiting threads.

以上方法的具体含义如下:

await() 方法会是当前线程等待,同时释放当前锁,当其他线程中使用signal() 或signalAll() 方法时,线程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和obj.wait方法很相似。

awaitUninterruptibly() 方法与await() 方法基本相同,只不过它不会在等待过程中响应中断。

signal() 方法用于唤醒一个在等待中的线程,这和obj.notify方法很类似。

简单示例

/** * Created by niehongtao on 16/7/8. * Condition用法 */
public class ReenterLockcondition implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();



    @Override
    public void run() {
        lock.lock();
        try {
            condition.await();
            System.out.println("thread is going on");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        ReenterLockcondition rlc = new ReenterLockcondition();
        Thread t1 = new Thread(rlc);
        t1.start();
        Thread.sleep(2000);
        // 通知线程继续执行
        lock.lock();
        condition.signal();
        lock.unlock();
    }
}

  代码中,听过lock生成一个与之绑定的Condition对象。代码15行要求线程在Condition对象上进行等待。代码32行,由主线程发起通知,告知等待在Condition上的线程可以继续执行了。

  和obj.wait和notify方法一样,当线程使用Condition.await时,要求线程持有相关的重入锁,在Condition.await调用后,这个线程会释放这把锁。同理,在Condition.signal方法调用时,也要求线程先获得相关的锁。在signal方法调用后,系统会从当前Condition对象的等待队列中,唤醒一个线程。一旦线程被唤醒,它会重新尝试获得与之绑定的重入锁,一旦成功获取,就可以继续执行了。因此,在signal方法调用之后,一般需要释放相关的锁,谦让给被唤醒的线程,让他可以继续执行。比如,在本例中,第33行就释放了重入锁,如果省略第24行,那么,虽然已经唤醒了线程t1,但是由于它无法重新获得锁,因而也就无法真正的继续执行。

  在jdk内部,重入锁和Condition对象被广泛的使用,后面讲到的线程安全的容器,他们的内容时候都有重入锁和Condition对象的影子。

    原文作者:java锁
    原文地址: https://blog.csdn.net/a910626/article/details/51900941
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞