第4章 Lock的使用

使用 ReentrantLock 类

使用 Condition 实现等待/通知

Condition 类是 JDK5 中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能。也就是在一个 Lock 对象里面可以创建多个 Condition 实例,线程对象可以注册在指定的 Condition 中,从而可以有选择地进行线程通知,在调度上更加灵活。
在使用 notify() 方法进行线程通知时,被通知的线程是由 JVM 随机选择的。但使用 ReentrantLock 结合 Condition 类可以实现前面介绍过的选择性通知,这是一个非常重要的功能。
而 synchronized 就相当于整个 Lock 对象中只有一个单一的 Condition 对象,所有线程都会注册在它一个对象身上,线程开始 notifyAll() 时,需要通知所有的 WAITING 线程,没有选择权。

public class MyService {
    private Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();
    public void await(){
        try {
            lock.lock();
            System.out.println("await 时间为:" + System.currentTimeMillis());
            condition.await();
            System.out.println("await end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void single(){
        try {
            lock.lock();
            System.out.println("single 的时间为:" + System.currentTimeMillis());
            condition.signal();
        } finally {
            lock.unlock();
        }
    }
}

public class ThreadA extends Thread {
    private MyService service;

    public ThreadA(MyService service) {
        super();
        this.service = service;
    }

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

public class Run {
    public static void main(String[] args) throws InterruptedException{
        MyService myService = new MyService();
        ThreadA threadA = new ThreadA(myService);
        threadA.start();
        Thread.sleep(3000);
        myService.single();
    }
}

Object 类中的 wait() 方法相当于 Condition 类中的 await() 方法;Object 类中的 notify() 方法相当于 Condition 类中的 single() 方法;Object 类中的 notifyAll() 方法相当于 Condition 类中的 singleAll() 方法。

使用多个 Condition 实现通知部分线程

前面的章节使用一个 Condition 对象来实现等待/通知模式,其实 Condition 对象也可以创建多个。那么一个 Condition 对象和多个 Condition 对象有什么区别呢?

public class MyService {
    private Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();

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

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

    public void singleAll(){
        try {
            lock.lock();
            System.out.println("singleAll time = " + System.currentTimeMillis() + " ThreadName : " + Thread.currentThread().getName());
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

public class ThreadA extends Thread {
    private MyService service;

    public ThreadA(MyService service) {
        super();
        this.service = service;
    }

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

public class ThreadB extends Thread {
    private MyService service;

    public ThreadB(MyService service) {
        super();
        this.service = service;
    }

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

public class Run {
    public static void main(String[] args) throws InterruptedException{
        MyService myService = new MyService();
        ThreadA a = new ThreadA(myService);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(myService);
        b.setName("B");
        b.start();
        Thread.sleep(3000);
        myService.singleAll();
    }
}

运行结果:
begin awaitA time = 1543846773351 ThreadName : A
begin awaitB time = 1543846773352 ThreadName : B
singleAll time = 1543846776350 ThreadName : main
end awaitA time = 1543846776350 ThreadName : A
end awaitB time = 1543846776350 ThreadName : B

从结果上看,线程 A 和 B 都被唤醒了,如果像单独唤醒部分线程该怎么做呢?这时候就有必要使用多个 Condition 对象了,也就是 Condition 对象可以唤醒部分指定线程。

使用多个 Condition 对象实现通知部分线程

public class MyService {
    private Lock lock = new ReentrantLock();
    public Condition conditionA = lock.newCondition();
    public Condition conditionB = lock.newCondition();
    public void awaitA(){
        try {
            lock.lock();
            System.out.println("begin awaitA time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
            conditionA.await();
            System.out.println("end awaitA time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void awaitB(){
        try {
            lock.lock();
            System.out.println("begin awaitB time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
            conditionB.await();
            System.out.println("end awaitB time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void singleAll_A(){
        try {
            lock.lock();
            System.out.println("singleAll_A time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void singleAll_B(){
        try {
            lock.lock();
            System.out.println("singleAll_B time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
            conditionB.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

public class ThreadA extends Thread {
    private MyService service;

    public ThreadA(MyService service) {
        super();
        this.service = service;
    }

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

public class ThreadB extends Thread {
    private MyService service;

    public ThreadB(MyService service) {
        super();
        this.service = service;
    }

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

public class Run {
    public static void main(String[] args) throws InterruptedException{
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
        Thread.sleep(3000);
        service.singleAll_A();
    }
}

运行结果:
begin awaitA time = 1543848404904ThreadName = A
begin awaitB time = 1543848404904ThreadName = B
singleAll_A time = 1543848407902ThreadName = main
end awaitA time = 1543848407902ThreadName = A

只有 A 线程被唤醒了。通过此实验可知,使用 ReentrantLock 对象可以唤醒指定种类的线程,这是控制部分线程行为的方便方式。

公平锁与非公平锁

Lock 分为公平锁和非公平锁,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的。而非公平锁是一种获取锁的抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定先得到锁,这种方式可能造成某些线程一直拿不到锁,结果也就不公平了。

公平锁

public class Service {
    private ReentrantLock lock;

    public Service(boolean isFair) {
        super();
        lock = new ReentrantLock(isFair);
    }

    public void serviceMethod(){
        try {
            lock.lock();
            System.out.println("ThreadName = " + Thread.currentThread().getName() + "获得锁定");
        } finally {
            lock.unlock();
        }
    }
}

public class RunFair {
    public static void main(String[] args) throws InterruptedException{
        final Service service = new Service(true);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("*线程" + Thread.currentThread().getName() + "运行了");
                service.serviceMethod();
            }
        };
        Thread[] threadArray = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threadArray[i] = new Thread(runnable);
        }
        for (int i = 0; i < 10; i++) {
            threadArray[i].start();
        }
    }
}

运行结果:
*线程Thread-0运行了
ThreadName = Thread-0获得锁定
*线程Thread-1运行了
ThreadName = Thread-1获得锁定
*线程Thread-2运行了
ThreadName = Thread-2获得锁定
*线程Thread-4运行了
ThreadName = Thread-4获得锁定
*线程Thread-6运行了
ThreadName = Thread-6获得锁定
*线程Thread-8运行了
ThreadName = Thread-8获得锁定
*线程Thread-3运行了
ThreadName = Thread-3获得锁定
*线程Thread-7运行了
ThreadName = Thread-7获得锁定
*线程Thread-5运行了
ThreadName = Thread-5获得锁定
*线程Thread-9运行了
ThreadName = Thread-9获得锁定

使用了公平锁的到的结果说明先运行的程序先的到锁。

非公平锁

public class RunFair {
    public static void main(String[] args) throws InterruptedException{
        final Service service = new Service(false);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("*线程" + Thread.currentThread().getName() + "运行了");
                service.serviceMethod();
            }
        };
        Thread[] threadArray = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threadArray[i] = new Thread(runnable);
        }
        for (int i = 0; i < 10; i++) {
            threadArray[i].start();
        }
    }
}

运行结果:
*线程Thread-0运行了
ThreadName = Thread-0获得锁定
*线程Thread-1运行了
ThreadName = Thread-1获得锁定
*线程Thread-2运行了
ThreadName = Thread-2获得锁定
*线程Thread-3运行了
*线程Thread-4运行了
ThreadName = Thread-3获得锁定
*线程Thread-5运行了
ThreadName = Thread-4获得锁定
*线程Thread-7运行了
ThreadName = Thread-7获得锁定
*线程Thread-8运行了
ThreadName = Thread-5获得锁定
ThreadName = Thread-8获得锁定
*线程Thread-6运行了
ThreadName = Thread-6获得锁定
*线程Thread-9运行了
ThreadName = Thread-9获得锁定

使用非公平锁的运行结果显示先运行的线程不一定先得到锁,谁得到锁是随机的。比如有可能出现这种情况:线程 A 获得 CPU 时间,执行了 run 方法的第一行代码,然后线程 B 取得 CPU 时间,并且得到了锁,执行了 run 方法中所有代码。

方法 getHoldCount()、getQueueLength() 和 getWaitQueueLength() 的测试

方法 getHoldCount() 的作用是查询当前线程保持此锁定的个数,也就是调用 lock() 方法的次数。

public class Service {
    private ReentrantLock lock = new ReentrantLock();
    public void serviceMethod1(){
        lock.lock();
        System.out.println("serviceMethod1 getHoldCount = " + lock.getHoldCount());
        serviceMethod2();
    }
    public void serviceMethod2(){
        try {
            lock.lock();
            System.out.println("serviceMethod2 getHoldCount = " + lock.getHoldCount());
        } finally {
            lock.unlock();
        }
    }
}

public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        service.serviceMethod1();
    }
}

运行结果:
serviceMethod1 getHoldCount = 1
serviceMethod2 getHoldCount = 2

方法 getQueueLength() 的作用是返回正在等待获取此锁定的线程的估计数,比如有 5 个线程,1 个线程首先执行 await() 方法,那么调用 getQueueLength() 方法返回值是 4,说明有 4 个线程同时在等待 lock 的释放。

public class Service {
    public ReentrantLock lock = new ReentrantLock();
    public void serviceMethod1(){
        try {
            lock.lock();
            System.out.println("ThreadName = " + Thread.currentThread().getName() + "进入方法!");
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class Run {
    public static void main(String[] args) throws InterruptedException{
        final Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.serviceMethod1();
            }
        };
        Thread[] threadArray = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threadArray[i] = new Thread(runnable);
        }
        for (int i = 0; i < 10; i++) {
            threadArray[i].start();
        }
        Thread.sleep(2000);
        System.out.println("有线程数:" + service.lock.getQueueLength() + "在等待获取锁");
    }
}

运行结果:
ThreadName = Thread-0进入方法!
有线程数:9在等待获取锁

方法 getWaitQueueLength(Condition condition) 的作用是返回等待与此锁定相关的给定条件 Condition 的线程估计数,比如有 5 个线程,每个线程都执行了同一个 condition 对象的 await() 方法,则调用 getWaitQueueLength(Condition condition) 方法返回的 int 值是 5。

方法 isFair()、isHeldByCurrentThread() 和 isLocked()

方法 boolean isFair() 的作用是判断是不是公平锁。
方法 boolean isHeldByCurrentThread() 的作用是查询当前线程是否保持此锁定。
方法 isLocked() 的作用是查询此锁定是否由任意线程保持。

方法 lockInterruptibly()、tryLock() 和 tryLock(long timeout, TimeUnit unit)

方法 lockInterruptibly() 的作用是:如果当前线程未被中断,则获取锁定;如果已被中断则抛出异常。
方法 boolean tryLock() 的作用是,仅在调用时锁定未被另另一个线程保持的情况下,才获取该锁定。

使用 ReentrantReadWriteLock 类

类 ReentrantLock 具有完全互斥排他的效果,即同一时刻只有一个线程执行 ReentrantLock.lock() 方法后边的任务。这样做,虽然保证了实例变量的线程安全性,但效率却非常低下。在某些不需要操作实例变量的方法中,完全可以使用读写锁 ReentrantReadWriteLock 来提升该方法的代码运行速度。

读写锁表示也有两个锁,一个是读操作相关的锁,称为共享锁;另一个是写操作相关的锁,称为排他锁。也就是多个读锁时间不互斥,读锁与写锁互斥,写锁与写锁互斥。在没有线程 Thread 进行写入操作时,进行读操作的多个 Thread 都可以获取读锁,而进行写入操作的 Thread 只有在获取写操作后才能进行写入操作。即多个 Thread 可以同时进行读取操作,但是同一时刻只允许 一个 Thread 进行写入操作。

类 ReentrantReadWriteLock 的使用:读读共享

public class Service {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void read(){
        try {
            try {
                lock.readLock().lock();
                System.out.println("获取读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                Thread.sleep(1000);
            } finally {
                lock.readLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.read();
    }
}

public class ThreadB extends Thread {
    private Service service;

    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.read();
    }
}

public class Test {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("a");
        ThreadB b = new ThreadB(service);
        b.setName("b");
        a.start();
        b.start();
    }
}

运行结果:
获取读锁a 1544430449341
获取读锁b 1544430449341

从运行结果来看,两个线程几乎同时进入 lock() 方法后边的代码。说明在此使用 lock.readLock() 读锁可以提高程序运行效率,允许多个线程同时执行 lock() 方法后边的代码。

类 ReentrantReadWriteLock 的使用:写写互斥

public class Service {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void read(){
        try {
            try {
                lock.readLock().lock();
                System.out.println("获取读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                Thread.sleep(5000);
            } finally {
                lock.readLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:
获取写锁a 1544431488159
获取写锁b 1544431493161

类 ReentrantReadWriteLock 的使用:读写互斥

public class Service {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void read(){
        try {
            try {
                lock.readLock().lock();
                System.out.println("获取读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                Thread.sleep(10000);
            } finally {
                lock.readLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void write(){
        try {
            try {
                lock.writeLock().lock();
                System.out.println("获取写锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                Thread.sleep(5000);
            } finally {
                lock.writeLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.read();
    }
}

public class ThreadB extends Thread {
    private Service service;

    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.write();
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException{
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();
        Thread.sleep(2000);//为了保证先让线程a执行,因为为了先获取读锁,看看读的时候能不能获取写锁
        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
    }
}

运行结果:
获取读锁a 1544432402668
获取写锁b 1544432412669

实验结果证明,在执行读锁后边的代码的时候,是不能进行写锁后边的操作的。说明读写是互斥的,当然写读也是互斥的。

在本章完全可以用 Lock 对象将 synchronized 关键字替换掉,而且具有的独特功能是 synchronized 是不具有的。在学习并发时,Lock 是 synchronized 关键字的进阶,掌握 Lock 有助于学习并发包中源代码的实现原理。

猜你喜欢

转载自blog.csdn.net/qq_32682177/article/details/84704330