天天看点

多线程编程(八)——Lock锁和ReentrantLock类详解四、等待可中断和等待自动唤醒五、读写锁ReentrantReadWriteLock——写锁互斥六、ReentranLock类常用API

        同步代码(synchronized)依赖于一种简单的可重入锁。这种锁使用方便,但有很多限制。在 java.util.concurrent.locks 包中提供了更复杂的锁的用法(Lock)。

        Lock 对象的工作原理非常类似于 synchronized 使用的隐式锁。与隐式锁一样,一次只能有一个线程拥有一个 Lock 对象。Lock 对象还通过其关联的 Condition 对象支持等待/通知机制( wait / notify)。

        与隐式锁相比,Lock 对象的最大优点是它们能够从获取锁的尝试中退出。如果锁不能立即获得或在超时到期(如果指定)之前可用,tryLock() 方法将退出。如果另一个线程在获得锁之前设置了中断,lockInterruptibly 方法将从锁竞争中退出。

        ReentrantLock(可重入锁)是对 Lock 接口的实现,在拓展功能上比 synchronized 关键字更加强大。

什么情况下使用 ReentrantLock?

        需要使用 ReentrantLock 的三个独有功能时(等待可中断,实现公平锁,条件通知)

ReentrantLock 和 synchronized 在实现方面的区别:

  • synchronized关键字同步功能通过JVM实现
  • Lock类通过JDK代码进行实现

1、如何使用 ReentrantLock 避免死锁问题

        使用方法:调用 ReentrantLock 对象的 lock() 方法获取锁,调用 unlock() 方法释放锁。

        如下,ReentrantLock 对象的代码使用示例:

public class Safelock {

    static class Friend {

        private final String name;
        private final Lock   lock = new ReentrantLock();

        public Friend(String name) {
            this.name = name;
        }

        public String getName() {
            return this.name;
        }

        public boolean impendingBow(Friend bower) {
            Boolean myLock = false;
            Boolean yourLock = false;
            try {
                myLock   = lock.tryLock();
                yourLock = bower.lock.tryLock();
            } finally {
                if (!(myLock && yourLock)) {
                    if (myLock) {
                        lock.unlock();
                    }
                    if (yourLock) {
                        bower.lock.unlock();
                    }
                }
            }
            return myLock && yourLock;
        }

        public void bow(Friend bower) {
            if (impendingBow(bower)) {
                try {
                    System.out.format("%s: %s has" + " bowed to me!%n", this.name, bower.getName());
                    bower.bowBack(this);
                } finally {
                    lock.unlock();
                    bower.lock.unlock();
                }
            } else {
                System.out.format("%s: %s started"
                        + " to bow to me, but saw that"
                        + " I was already bowing to"
                        + " him.%n",
                    this.name, bower.getName());
            }
        }

        public void bowBack(Friend bower) {
            System.out.format("%s: %s has" + " bowed back to me!%n", this.name, bower.getName());
        }
    }

    static class BowLoop implements Runnable {

        private Friend bower;
        private Friend bowee;

        public BowLoop(Friend bower, Friend bowee) {
            this.bower = bower;
            this.bowee = bowee;
        }

        public void run() {
            Random random = new Random();
            for (; ; ) {
                try {
                    Thread.sleep(random.nextInt(10));
                } catch (InterruptedException e) {
                }
                bowee.bow(bower);
            }
        }
    }

    public static void main(String[] args) {
        final Friend alphonse =
            new Friend("Alphonse");
        final Friend gaston =
            new Friend("Gaston");
        new Thread(new BowLoop(alphonse, gaston)).start();
        new Thread(new BowLoop(gaston, alphonse)).start();
    }
}
           

        上边示例,通过 Lock 锁展示了两个线程之间获取锁和释放锁的过程,从而避免了出现死锁的情况。这该示例中,程序不会应死锁问题造成阻塞,所以会不停的执行。

2、使用 Condition 实现等待/通知机制

        Condition 对象相比 synchronized 关键字的等待通知机制具有更好的灵活性,可以有选择性的进行线程通知。

        使用方法:线程等待 condition.await();线程通知 condition.signal() / condition.signalAll()

        根据 Condition 条件唤醒不同的线程,示例如下:

public class Conditions {

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

    // 唤醒条件A的线程
    public void signalAll_A() {
        try {
            lock.lock();
            System.out.println("线程:" + Thread.currentThread().getName() + ":唤醒条件A ...");
            conditionA.signalAll();
            System.out.println("线程:" + Thread.currentThread().getName() + ":唤醒条件A结束...");
        } finally {
            // 不管执行是否异常,都要释放锁
            lock.unlock();
        }
    }

    // 唤醒条件B的线程
    public void signalAll_B() {
        try {
            lock.lock();
            System.out.println("线程:" + Thread.currentThread().getName() + ":唤醒条件B ...");
            conditionB.signalAll();
            System.out.println("线程:" + Thread.currentThread().getName() + ":唤醒条件B结束 ...");
        } finally {
            // 不管执行是否异常,都要释放锁
            lock.unlock();
        }
    }

    public class ThreadA extends Thread {

        @Override
        public void run() {
            try {
                lock.lock();
                System.out.println("线程:" + Thread.currentThread().getName() + ":条件A等待开始,释放锁...");
                conditionA.await();// 线程等待,使用条件一
                System.out.println("线程:" + Thread.currentThread().getName() + ":条件A等待结束,继续执行...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 不管执行是否异常,都要释放锁
                lock.unlock();
            }
        }
    }

    public class ThreadB extends Thread {

        @Override
        public void run() {
            try {
                lock.lock();
                System.out.println("线程:" + Thread.currentThread().getName() + ":条件B等待开始,释放锁...");
                conditionB.await();// 线程等待,使用条件二
                System.out.println("线程:" + Thread.currentThread().getName() + ":条件B等待开始...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 不管执行是否异常,都要释放锁
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Conditions c = new Conditions();
        c.new ThreadA().start();
        c.new ThreadB().start();
        Thread.sleep(3000);
        c.signalAll_A();// 选择性唤醒线程,置唤醒A
    }
}
           

        程序执行结果如下,只有 conditionA 的等待线程被唤醒:

线程:Thread-0:条件A等待开始,释放锁...
线程:Thread-1:条件B等待开始,释放锁...
线程:main:唤醒条件A ...
线程:main:唤醒条件A结束...
线程:Thread-0:条件A等待结束,继续执行...
           

        上述示例,展示了使用 condition 对象可以实现线程的选择性通知。

3、使用 ReentrantLock 实现公平锁和非公平锁

        Lock 锁分为公平锁与非公平锁,公平锁是有顺序的,先进先出(FIFO),非公平锁是无顺序的,任何程序都有锁竞争的权利,不用排队。

        下边展示 ReentranLock 类在公平锁与非公平锁上的实现

测试代码:

创建MyService类

public class MyService {

    private Lock lock;

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

    public void method(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"- 被锁定");
        } finally {
            lock.unlock();
        }
    }
}
           

创建执行方法——此时创建公平锁

public class ExecuteMethod {
    public static void main(String[] args) throws InterruptedException {
        // 设置为true 表示此锁为公平锁
        MyService myService = new MyService(true);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                myService.method();
            }
        };
        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();
        }
    }
}
           

执行结果:

多线程编程(八)——Lock锁和ReentrantLock类详解四、等待可中断和等待自动唤醒五、读写锁ReentrantReadWriteLock——写锁互斥六、ReentranLock类常用API

接下来修改代码,修改为非公平锁

public class ExecuteMethod {
    public static void main(String[] args) throws InterruptedException {
        // 设置为false 表示此锁为非公平锁
        MyService myService = new MyService(false);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                myService.method();
            }
        };
        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();
        }
    }
}
           

执行结果:

多线程编程(八)——Lock锁和ReentrantLock类详解四、等待可中断和等待自动唤醒五、读写锁ReentrantReadWriteLock——写锁互斥六、ReentranLock类常用API

四、等待可中断和等待自动唤醒

1、condition.awaitUniterruptibly() 等待可中断

condition.await() 线程在等待的时候被中断抛出异常

condition.awaitUniterruptibly() 线程在等待的时候被中断不会抛出异常

测试代码:

首先看一下condition.await()等待抛出异常的情况

public class MyService {

    private ReentrantLock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    public void testMethod(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"等待开始...");
            condition.await();// await()中断会抛出异常
            System.out.println(Thread.currentThread().getName()+"等待结束...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    static class ExcuteThread extends Thread{
        private MyService myService;

        public ExcuteThread(MyService myService){
            super();
            this.myService = myService;
        }

        public void run(){
            myService.testMethod();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyService myService = new MyService();
        ExcuteThread ex = new ExcuteThread(myService);
        ex.start();
        Thread.sleep(3000);
        ex.interrupt();//中断线程
    }
}
           

执行结果:

多线程编程(八)——Lock锁和ReentrantLock类详解四、等待可中断和等待自动唤醒五、读写锁ReentrantReadWriteLock——写锁互斥六、ReentranLock类常用API

接下来,把等待的方法换成condition.awaitUniterruptibly(),再次进行测试

public void testMethod(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"等待开始...");
            condition.awaitUninterruptibly();// 更换方法中的等待方式
            System.out.println(Thread.currentThread().getName()+"等待结束...");
        }finally {
            lock.unlock();
        }
    }
           

执行结果:

多线程编程(八)——Lock锁和ReentrantLock类详解四、等待可中断和等待自动唤醒五、读写锁ReentrantReadWriteLock——写锁互斥六、ReentranLock类常用API

2、condition.awaitUnitil(time) 等待自动唤醒

condition.awaitUnitil(time) 线程在一定时间后自动唤醒自己,在这个时间到达前也可以被其他线程唤醒

测试代码:

public class MyService {

    private ReentrantLock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    public void testMethod(){
        try {
            lock.lock();
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.SECOND,2);
            System.out.println(Thread.currentThread().getName()+"等待开始...");
            condition.awaitUntil(calendar.getTime());// 2s后将自动唤醒
            System.out.println(Thread.currentThread().getName()+"等待结束,自动唤醒...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
        lock.unlock();
        }
    }

    static class ExcuteThread extends Thread{
        private MyService myService;

        public ExcuteThread(MyService myService){
            super();
            this.myService = myService;
        }

        public void run(){
            myService.testMethod();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyService myService = new MyService();
        ExcuteThread ex = new ExcuteThread(myService);
        ex.start();
    }
}
           

五、读写锁ReentrantReadWriteLock——写锁互斥

ReentrantReadWriteLock读写锁可以使在不需要操作实例变量的方法中(比如写数据),使代码能够异步执行,提高运行效率。

读写锁的互斥逻辑:只要程序运行中有写的逻辑,执行就是互斥的,只读程序不会进行同步。

测试代码:

创建MyService类

public class MyService {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void readOnly(){
        try {
            lock.readLock().lock();// 使用只读锁
            System.out.println(Thread.currentThread().getName()+":获取只读锁...");
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+":执行只读方法结束...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

    public void writeOnly(){
        try {
            try {
                lock.writeLock().lock();// 使用只写锁
                System.out.println(Thread.currentThread().getName()+":获取只写锁...");
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName()+":执行只写方法结束...");
            } finally {
                lock.writeLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
           

创建线程A:先获取读锁,后获取写锁

public class ThreadA extends Thread {

    private MyService myService;

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

    @Override
    public void run() {
        myService.readOnly();
        myService.writeOnly();
    }
}
           

创建线程B:先获取读锁,后获取写锁

public class ThreadB extends Thread {

    private MyService myService;

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

    @Override
    public void run() {
        myService.readOnly();
        myService.writeOnly();
    }
}
           

创建执行类:验证读为——共享锁,写为——互斥锁

public class ExecuteMethod {
    public static void main(String[] args) throws InterruptedException {
        // 设置为false 表示此锁为非公平锁
        MyService myService = new MyService();
        ThreadA threadA = new ThreadA(myService);
        threadA.setName("A线程");
        ThreadB threadB = new ThreadB(myService);
        threadB.setName("B线程");
        threadA.start();
        threadB.start();
    }
}
           

执行结果:

多线程编程(八)——Lock锁和ReentrantLock类详解四、等待可中断和等待自动唤醒五、读写锁ReentrantReadWriteLock——写锁互斥六、ReentranLock类常用API

六、ReentranLock类常用API

1、统计线程个数

int getHoldCount() 统计锁定线程的个数

int getQueueLength() 获取等待线程的个数

int getWaitQueueLength() 获取condition下执行了await()方法的线程数

2、判断线程状态

boolean hasQueueThread() 查询指定线程是否正在等待锁

boolean hasQueueThreads() 查询是否有线程正在等待锁

boolean hasWaiters(Condition condition) 查询有没有线程执行了此condition的await()方法

3、判断锁状态

boolean isFair() 判断是不是公平锁

boolean isHeldByCurrentThread() 判断线程是否被锁定

boolean isLocked() 判断锁是否被占用

4、获取锁的方式

void lockInterruptibly() 线程中断剖出异常

boolean tryLock() 尝试获取未被占用的锁

boolean tryLock(Long timeout,TimeUnit unit) 锁在一定时间类没有被占用,获取该锁

继续阅读