ReentrantLock类的使用

一、使用ReentrantLock类

和 synchronized 关键字来实现线程之间的同步互斥一样,ReentrantLock 类也可以达到这样的效果

class MyService2 {

    private Lock lock = new ReentrantLock();

    public void methodA() {
        //当前线程获得对象锁
        lock.lock();

        try {
            System.out.println(Thread.currentThread().getName() + " begin "
                    + System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " end "
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //当前线程释放对象锁
        lock.unlock();
    }

    public void methodB() {
        lock.lock();

        try {
            System.out.println(Thread.currentThread().getName() + " begin "
                    + System.currentTimeMillis());
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + " end "
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        lock.unlock();
    }

}

class ThreadA extends Thread {

    private MyService2 service2;

    public ThreadA(MyService2 service2) {
        this.service2 = service2;
    }

    @Override
    public void run() {
        service2.methodA();
    }

}

class ThreadB extends Thread {

    private MyService2 service2;

    public ThreadB(MyService2 service2) {
        this.service2 = service2;
    }

    @Override
    public void run() {
        service2.methodB();
    }

}

public class Run2 {

    public static void main(String[] args) throws InterruptedException {
        MyService2 myService2 = new MyService2();
        ThreadA threadA = new ThreadA(myService2);
        ThreadB threadB = new ThreadB(myService2);

        threadA.start();
        Thread.sleep(2000);
        threadB.start();
    }

}

结果是:

Thread-0 begin 1541139543281
Thread-0 end 1541139545281
Thread-1 begin 1541139545282
Thread-1 end 1541139548282

执行多次后,发现 ReentrantLock 对象持有的是对象锁,在线程 Thread-0 执行 lock.lock() 这段代码之后,线程 Thread-0 就持有了 service2 这个对象锁,当执行完方法之后,执行 lock.unlock() 代码释放对象锁,然后线程 Thread-1 才能获得对象锁

如果我们将 methodA 中的 lock.unlock() 语句注释掉,那么结果是

Thread-0 begin 1541140069186
Thread-0 end 1541140071186

此时控制台没有停止,会一直等待下去,说明线程 Thread-0 确实是拿到了对象锁,如果不释放,那么线程 Thread-1 就永远拿不到

二、使用Condition实现等待和通知

关键字 synchronized 可以和 wait()、notify() 和 notifyAll() 方法相结合,来实现等待和通知模式,类 ReentrantLock 也可以实现相同的功能,但是需要借助 Condition 对象,同时,具有更好的灵活性

  • 一个Lock对象里面可以创建多个Condition实例
  • 线程对象可以有选择性的进行线程通知,在调度线程上更加灵活

在使用 await() 和signal() 之前,必须要先在当前线程调用 lock() 方法获得锁,使用完毕后在 finally 中调用 unlock() 释放锁,这和 wait()/notify()/notifyAll() 使用前必须先获得对象锁是一样的

class MyService6 {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void await() {
        try {
            //当前线程获得锁
            lock.lock();
            System.out.println(Thread.currentThread().getName()
                    + " await begin的时间为:" + System.currentTimeMillis());
            //使当前线程进入等待状态,相当于 Object 中的 wait(long time) 方法
            condition.await();
            System.out.println(Thread.currentThread().getName()
                    + " await end的时间为:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //当前线程释放锁
            lock.unlock();
        }
    }

    public void signal() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()
                    + " signal的时间为:" + System.currentTimeMillis());
            //唤醒进入等待状态的线程,相当于 Object 中的 notify() 方法
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

}

class ThreadA6 extends Thread {

    private MyService6 service6;

    public ThreadA6(MyService6 service6) {
        this.service6 = service6;
    }

    @Override
    public void run() {
        service6.await();
    }
}

public class Run6 {

    public static void main(String[] args) throws InterruptedException {
        MyService6 service6 = new MyService6();
        ThreadA6 threadA6 = new ThreadA6(service6);
        threadA6.start();
        Thread.sleep(2000);
        service6.signal();
    }

}

结果是:

Thread-0 await begin的时间为:1541142852855
main signal的时间为:1541142854854
Thread-0 await end的时间为:1541142854855

线程 Thread-0 调用 condition 对象的 await() 方法使得当前线程进入等待状态,可以看到,输出的第二行,是在线程 main 中执行的 signal() 方法,也说明了await() 方法是释放锁的,要不然在线程 main 中也不能执行 signal 方法

我们可以将 Object 类中的通知等待和 Condition 类中的通知等待进行对比

  • Object 类中的 wait() 方法相当于 Condition 类中的 await() 方法
  • Object 类中的 wait(long timeout) 方法相当于 Condition 类中的 await(long time, TimeUnit) 方法
  • Object 类中的 notify() 方法相当于 Condition 类中的 signal() 方法
  • Object 类中的 notifyAll() 方法相当于 Condition 类中的 signalAll() 方法

需要了解的是,使用 notify() 或 notifyAll() 方法进行通知的时候,被通知的线程却是由 JVM 随机选择的,即不能自己指定什么通知哪个等待的线程,而在 ReentrantLock 中使用 Condition 类可以实现自己选择通知哪个线程

如果我们在不同线程中对同一个 Condition 对象同时调用 await() 方法

class MyService7 {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " begina "
                    + System.currentTimeMillis());
            //使当前线程进入等待状态
            condition.await();
            System.out.println(Thread.currentThread().getName() + " enda "
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " beginb "
                    + System.currentTimeMillis());
            condition.await();
            System.out.println(Thread.currentThread().getName() + " endb "
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll() {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + " signalAll "
                + System.currentTimeMillis());
        condition.signalAll();
        lock.unlock();
    }

}

class ThreadA7 extends Thread {

    private MyService7 service7;

    public ThreadA7(MyService7 service7) {
        this.service7 = service7;
    }

    @Override
    public void run() {
        service7.awaitA();
    }

}

class ThreadB7 extends Thread {

    private MyService7 service7;

    public ThreadB7(MyService7 service7) {
        this.service7 = service7;
    }

    @Override
    public void run() {
        service7.awaitB();
    }

}

public class Run7 {

    public static void main(String[] args) throws InterruptedException {
        MyService7 service7 = new MyService7();
        ThreadA7 threadA7 = new ThreadA7(service7);
        threadA7.setName("AAA");
        threadA7.start();

        ThreadB7 threadB7 = new ThreadB7(service7);
        threadB7.setName("BBB");
        threadB7.start();

        Thread.sleep(2000);

        service7.signalAll();
    }

}

结果是:

AAA begina 1541147188535
BBB beginb 1541147188535
main signalAll 1541147190535
AAA enda 1541147190536
BBB endb 1541147190536

可以看到,用 lock 对象实例化了一个 Condition 对象,线程 AAA 和线程 BBB 同时调用了同一个 Condition 对象 condition,最终只能在线程 main 中调用 Condition 对象的 signalAll() 方法来唤醒 Condition 对象对应的两个线程

此时在一个 lock 对象中只有一个单一的 Condition 对象,所有的线程都只注册在它一个对象上,此时只能通知所有进入等待状态的线程,不能自己选择,会出现很大的效率问题
而这和 synchronized 是类似的,synchronized 使得所有线程都持有同一个对象锁,如果所有线程都调用 wait() 方法进入等待状态,那么其他线程也只能通过调用 notifyAll() 方法唤醒所有等待的线程,同样不能选择性的通知

使用多个Condition实现通知

如果用 lock 对象创建多个 Condition 对象,那么可以根据 Condition 来唤醒线程的部分

class Service8 {

    private Lock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " begin awaitA "
                    + System.currentTimeMillis());
            conditionA.await();
            System.out.println(Thread.currentThread().getName() + " end awaitA "
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " begin awaitB "
                    + System.currentTimeMillis());
            conditionB.await();
            System.out.println(Thread.currentThread().getName() + " end awaitB "
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalA() {
        try {
            lock.lock();
            System.out.println(ThreadB.currentThread().getName() + " signalA "
                    + System.currentTimeMillis());
            conditionA.signal();
        } finally {
            lock.unlock();
        }
    }

    public void signalB() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " signalB "
                    + System.currentTimeMillis());
            conditionB.signal();
        } finally {
            lock.unlock();
        }
    }
}

class ThreadA8 extends Thread {

    private Service8 service8;

    public ThreadA8(Service8 service8) {
        this.service8 = service8;
    }

    @Override
    public void run() {
        service8.awaitA();
    }

}

class ThreadB8 extends Thread {

    private Service8 service8;

    public ThreadB8(Service8 service8) {
        this.service8 = service8;
    }

    @Override
    public void run() {
        service8.awaitB();
    }
}

public class Run8 {

    public static void main(String[] args) throws InterruptedException {
        Service8 service8 = new Service8();
        ThreadA8 threadA8 = new ThreadA8(service8);
        threadA8.setName("AAA");
        threadA8.start();

        ThreadB8 threadB8 = new ThreadB8(service8);
        threadB8.setName("BBB");
        threadB8.start();

        Thread.sleep(2000);

        service8.signalA();	//语句1
        service8.signalB();
    }

}

结果是:

AAA begin awaitA 1541144978036
BBB begin awaitB 1541144978044
main signalA 1541144980044
main signalB 1541144980044
AAA end awaitA 1541144980045
BBB end awaitB 1541144980045

使用 lock 对象创建了两个 Condition 实例,相当于一个锁内部有两个 Condition,即有两路等待和通知,然后我们在线程 AAA 中调用其中一个 Condition 对象的 await() 方法,使该路进入等待状态,之后再在线程 BBB 中调用另一个 Condition 对象的 await() 方法,使另一路也进入等待状态。然后在线程 main 中调用各自 Condition 对象的 signal() 方法,使得每一路都被唤醒,也即线程 AAA 和线程 BBB 都被唤醒,然后各自输出 await() 方法之后的代码

可以看到,我们可以创建多个 Condition 实例来控制多个线程,方法是,我们需要在各自线程中调用 conditionXXX.await() 方法将线程和每个 Condition 实例进行绑定,此时,每个 Condition 对象就如同一个线程,可以随意控制

加入我把语句1注释掉,那么结果是

AAA begin awaitA 1541146681455
BBB begin awaitB 1541146681456
main signalB 1541146683457
BBB end awaitB 1541146683457

此时控制台还在运行,且永远等待下去,该结果更加证明了,如果一个 lock 对象实例出了多个 Condition 对象,我们可以将线程注册在指定的 Condition 对象中,同时选择性的对 Condion 对象进行通知和唤醒,因此通知唤醒的过程可以更加灵活

三、参考

《Java多线程编程核心技术》

猜你喜欢

转载自blog.csdn.net/babycan5/article/details/83692438