Java多线程编程8:ReentrantLock

640 阅读8分钟

Lock

锁是用于多个线程控制对共享资源的访问的工具。通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获得锁,并且对共享资源的所有访问都要求先获取锁。但是,一些锁可能运行并发访问共享资源。

在Java多线程中,可以使用synchronized关键字来实现线程之间同步互斥,但在JDK1.5后增加了Lock接口以及相关实现类来达到同样的效果,甚至有更多的拓展功能:嗅探锁定、多路分支;使用上也比synchronized更加灵活。

ReentrantLock

ReentrantLock和synchronized关键字一样可以用来实现线程之间的同步互斥,但是在功能是比synchronized关键字更强大而且更灵活。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyService {
    private Lock lock = new ReentrantLock();

    public void methodA() {
        try {
            lock.lock();
            System.out.println("methodA begin, thread: " +
                    Thread.currentThread().getName() + " time: " +
                    System.currentTimeMillis());
            Thread.sleep(500);
            System.out.println("methodA end, thread: " +
                    Thread.currentThread().getName() + " time: " +
                    System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void methodB() {
        try {
            lock.lock();
            System.out.println("methodB begin, thread: " +
                    Thread.currentThread().getName() + " time: " +
                    System.currentTimeMillis());
            Thread.sleep(500);
            System.out.println("methodB end, thread: " +
                    Thread.currentThread().getName() + " time: " +
                    System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

class ThreadA extends Thread {
    private MyService service;

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

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


class ThreadB extends Thread {
    private MyService service;

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

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


public class Run {
    public static void main(String[] args) {
        MyService service = new MyService();
        ThreadA a1 = new ThreadA(service);
        a1.setName("a1");
        a1.start();

        ThreadA a2 = new ThreadA(service);
        a2.setName("a2");
        a2.start();

        ThreadB b1 = new ThreadB(service);
        b1.setName("b1");
        b1.start();

        ThreadB b2 = new ThreadB(service);
        b2.setName("b2");
        b2.start();
    }
}

运行结果:

methodA begin, thread: a1 time: 1544441978238
methodA end, thread: a1 time: 1544441978739
methodA begin, thread: a2 time: 1544441978739
methodA end, thread: a2 time: 1544441979239
methodB begin, thread: b1 time: 1544441979239
methodB end, thread: b1 time: 1544441979740
methodB begin, thread: b2 time: 1544441979740
methodB end, thread: b2 time: 1544441980241

可以看到,四个线程使用的是一个锁,一个线程运行结束后才把相应的锁释放,其他线程才能执行。也就是说调用lock.lock()代码的线程就持有了“对象监视器”,其他线程要等待锁被释放时再次争抢。

使用Condition实现等待/通知

synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。

Condition类具有更好的灵活性,比如可以实现多路通知功能。在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。

在使用notify/notifyAll()方法进行通知时,被通知的线程是有JVM选择的,使用ReentrantLock类结合Condition实例可以实现“选择性通知”,这个功能非常重要,而且是Condition接口默认提供的。

而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。 示例代码:

class MyService{
    private Lock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();
    public void await(){
        try {
            lock.lock();
            System.out.println("await time:"+System.currentTimeMillis());
            condition.await();//相当于wait()
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signal(){
        try {
            lock.lock();
            System.out.println("signal time:"+System.currentTimeMillis());
            condition.signal();//相当于notify()
        } finally {
            lock.unlock();
        }
    }
}

class ThreadA extends Thread{
    private MyService service;
    public ThreadA(MyService service){
        this.service=service;
    }

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

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

输出结果:

await time:1544443991123
signal time:1544443994126

在使用wait/notify实现等待通知机制的时候我们知道必须执行完notify()方法所在的synchronized代码块后才释放锁。在这里也差不多,必须执行完signal所在的try语句块之后才释放锁,condition.await()后的语句才能被执行。

注意: 必须在condition.await()方法调用之前调用lock.lock()代码获得同步监视器,不然会报错。

唤醒指定线程

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: "+System.currentTimeMillis()+" "+Thread.currentThread().getName());
            conditionA.await();
            System.out.println("end awaitA: "+System.currentTimeMillis()+" "+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

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

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

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

class ThreadA extends Thread{
    private MyService service;
    public ThreadA(MyService service){
        this.service=service;
    }

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

class ThreadB extends Thread{
    private MyService service;
    public ThreadB(MyService service){
        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);
        ThreadB b=new ThreadB(service);
        a.setName("A");
        b.setName("B");
        a.start();
        b.start();
        Thread.sleep(2000);
        service.signalAll_A();
    }
}

输出为:

begin awaitB: 1544445749497 B
begin awaitA: 1544445749498 A
signalAll_A time: 1544445751511 main
end awaitA: 1544445751511 A

可以看到,signalAll_A将对应的A线程唤醒了。注意,此时程序仍然在运行,因为conditionB没有对应的方法去唤醒,一直在wait。

实现生产者/消费者交替打印

class MyService{
    private ReentrantLock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();
    private boolean hasValue=false;
    public void produce(int value){
        try {
            lock.lock();
            while(hasValue){
                condition.await();
            }
            System.out.println("produce "+value);
            hasValue=true;
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void consume(int value){
        try {
            lock.lock();
            while(hasValue==false){
                condition.await();
            }
            System.out.println("consume "+value);
            hasValue=false;
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

class ProduceThread extends Thread{
    private MyService service;
    public ProduceThread(MyService service){
        this.service=service;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            service.produce(i);
        }
    }
}

class ConsumeThread extends Thread{
    private MyService service;
    public ConsumeThread(MyService service){
        this.service=service;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            service.consume(i);
        }
    }
}

public class Run {
    public static void main(String[] args) {
        MyService service=new MyService();
        ProduceThread producer=new ProduceThread(service);
        producer.start();
        ConsumeThread consumer=new ConsumeThread(service);
        consumer.start();
    }
}

运行结果:

produce 0
consume 0
produce 1
consume 1
produce 2
consume 2
produce 3
consume 3
produce 4
consume 4
produce 5
consume 5
produce 6
consume 6
produce 7
consume 7
produce 8
consume 8
produce 9
consume 9

使用Condition对线程执行的业务进行排序规划

public class Run {
    volatile private static int nextPrint=1;
    private static ReentrantLock lock=new ReentrantLock();
    final private static Condition conditionA=lock.newCondition();
    final private static Condition conditionB=lock.newCondition();
    final private static Condition conditionC=lock.newCondition();

    public static void main(String[] args) {
        Thread a=new Thread(){
            @Override
            public void run() {
                try {
                    lock.lock();
                    while(nextPrint!=1){
                        conditionA.await();
                    }
                    for(int i=0;i<3;i++){
                        System.out.println("ThreadA "+(char)('a'+i));
                    }
                    nextPrint=2;
                    conditionB.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };

        Thread b=new Thread(){
            @Override
            public void run() {
                try {
                    lock.lock();
                    while(nextPrint!=2){
                        conditionB.await();
                    }
                    for(int i=0;i<3;i++){
                        System.out.println("ThreadB "+(char)('a'+i));
                    }
                    nextPrint=3;
                    conditionC.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };

        Thread c=new Thread(){
            @Override
            public void run() {
                try {
                    lock.lock();
                    while(nextPrint!=3){
                        conditionC.await();
                    }
                    for(int i=0;i<3;i++){
                        System.out.println("ThreadC "+(char)('a'+i));
                    }
                    nextPrint=1;
                    conditionA.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };
        Thread[] aArray=new Thread[5];
        Thread[] bArray=new Thread[5];
        Thread[] cArray=new Thread[5];
        for(int i=0;i<5;i++){
            aArray[i]=new Thread(a);
            bArray[i]=new Thread(b);
            cArray[i]=new Thread(c);
            aArray[i].start();
            bArray[i].start();
            cArray[i].start();
        }
    }
}

运行结果如下:

ThreadA a
ThreadA b
ThreadA c
ThreadB a
ThreadB b
ThreadB c
ThreadC a
ThreadC b
ThreadC c
ThreadA a
ThreadA b
ThreadA c
ThreadB a
ThreadB b
ThreadB c
ThreadC a
ThreadC b
ThreadC c
ThreadA a
ThreadA b
ThreadA c
ThreadB a
ThreadB b
ThreadB c
ThreadC a
ThreadC b
ThreadC c
ThreadA a
ThreadA b
ThreadA c
ThreadB a
ThreadB b
ThreadB c
ThreadC a
ThreadC b
ThreadC c
ThreadA a
ThreadA b
ThreadA c
ThreadB a
ThreadB b
ThreadB c
ThreadC a
ThreadC b
ThreadC c

实现了三个线程的轮流执行。

公平锁和非公平锁

Lock锁分为公平锁和非公平锁。公平锁表示线程获取锁的顺序是按照线程加锁的顺序分配的,即先来先得FIFO。非公平锁是抢占机制,是随机获取锁的,可能造成某些线程一致拿不到锁,结果就是不公平的了。

使用公平锁,测试代码:

class Service{
    private ReentrantLock lock;
    public Service(boolean isFair){
        lock=new ReentrantLock(isFair);
    }
    public void service(){
        try {
            lock.lock();
            System.out.println("-"+Thread.currentThread().getName()+"获得锁");
        } finally {
            lock.unlock();
        }
    }
}

public class Run {
    public static void main(String[] args) {
        final Service service=new Service(true);
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"运行了");
                service.service();
            }

        };
        Thread[] threads=new Thread[10];
        for(int i=0;i<10;i++){
            threads[i]=new Thread(runnable);
        }
        for(int i=0;i<10;i++){
            threads[i].start();
        }
    }
}

运行结果为:

Thread-0运行了
Thread-2运行了
-Thread-0获得锁
Thread-1运行了
-Thread-2获得锁
Thread-3运行了
Thread-4运行了
Thread-5运行了
Thread-6运行了
-Thread-1获得锁
-Thread-3获得锁
-Thread-4获得锁
-Thread-5获得锁
-Thread-6获得锁
Thread-7运行了
-Thread-7获得锁
Thread-8运行了
-Thread-8获得锁
Thread-9运行了
-Thread-9获得锁

可以看出,线程运行的顺序和获得锁的顺序是一样的,先运行的线程是先获得锁的。

更改isFair为false,输出结果为:

Thread-0运行了
-Thread-0获得锁
Thread-1运行了
-Thread-1获得锁
Thread-2运行了
-Thread-2获得锁
Thread-3运行了
-Thread-3获得锁
Thread-5运行了
-Thread-5获得锁
Thread-8运行了
Thread-9运行了
-Thread-8获得锁
-Thread-9获得锁
Thread-4运行了
-Thread-4获得锁
Thread-7运行了
Thread-6运行了
-Thread-7获得锁
-Thread-6获得锁

可以看到不是先运行的线程先获得锁的了。

常见方法

  • int getHoldCount():查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
  • int getQueueLength():返回等待获取此锁的线程数的估计。
  • int getWaitQueueLength(Condition condition):返回与此锁相关联的给定条件等待的线程数的估计。
  • boolean hasQueuedThread(Thread thread):查询给定线程是否等待获取此锁。
  • boolean hasQueuedThreads():查询是否有线程正在等待获取此锁。
  • boolean hasWaiters(Condition condition):查询任何线程是否等待与此锁相关联的给定条件
  • boolean isFair():返回是否是公平锁
  • boolean isHeldByCurrentThread():查询此锁是否由当前线程持有。
  • boolean isLocked():查询此锁是否由任何线程持有。 参考资料
  • 高洪岩. Java多线程编程核心技术[M]. 机械工业出版社, 2015
  • blog.csdn.net/qq_34337272…
  • github.com/CyC2018/CS-…
  • crossoverjie.top/JCSprout/#/…