多线程编程(八)——Lock锁和ReentrantLock类详解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/swadian2008/article/details/100145405

目录

一、ReentrantLock实现同步

二、使用Condition实现等待/通知——选择通知

三、公平锁和非公平锁

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

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

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

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

六、ReentranLock类常用API

1、统计线程个数

2、判断线程状态

3、判断锁状态

4、获取锁的方式


除了synchronized关键字可以实现线程之间的互斥,ReentrantLock也可以实现同步的效果,而且在拓展功能上比synchronized关键字更加强大。

什么情况下使用ReentrantLock?

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

实现不同:

synchronized关键字同步功能通过JVM实现

Lock类通过JDK代码进行实现

一、ReentrantLock实现同步

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

测试代码:

创建MyService类

public class MyService {

    private Lock lock = new ReentrantLock();

    public void methodA(){
        try {
            lock.lock();
            System.out.println("线程:"+Thread.currentThread().getName()+"——A方法执行开始...");
            Thread.sleep(3000);
            System.out.println("线程:"+Thread.currentThread().getName()+"——A方法执行结束...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            // 不管执行是否异常,都要释放锁
            lock.unlock();
        }
    }

    public void methodB(){
        try {
            lock.lock();
            System.out.println("线程:"+Thread.currentThread().getName()+"——B方法执行开始...");
            Thread.sleep(3000);
            System.out.println("线程:"+Thread.currentThread().getName()+"——B方法执行结束...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            // 不管执行是否异常,都要释放锁
            lock.unlock();
        }
    }
}

创建线程——A:执行方法A,争夺锁

public class ThreadA extends Thread {

    private MyService myService;

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

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

创建线程——B:执行方法B,争夺锁

public class ThreadB extends Thread {

    private MyService myService;

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

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

创建线程——C:执行方法C,争夺锁

public class ThreadC extends Thread {

    private MyService myService;

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

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

创建执行类:

public class ExecuteMethod {
    public static void main(String[] args) {
        MyService myService = new MyService();
        ThreadA a = new ThreadA(myService);
        a.setName("a");
        ThreadB b = new ThreadB(myService);
        b.setName("b");
        ThreadC c = new ThreadC(myService);
        c.setName("c");
        a.start();
        b.start();
        c.start();
    }
}

执行结果:

二、使用Condition实现等待/通知——选择通知

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

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

测试代码:

创建MyService类

public class MyService {

    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()+"——A方法await()开始...");
            conditionA.await();// 线程等待,使用条件一
            System.out.println("线程:"+Thread.currentThread().getName()+"——A方法await()结束...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            // 不管执行是否异常,都要释放锁
            lock.unlock();
        }
    }

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

    public void signalAll_A(){
        try {
            lock.lock();
            System.out.println("线程:"+Thread.currentThread().getName()+"——methodAll_A方法signal()开始...");
            conditionA.signalAll();
            System.out.println("线程:"+Thread.currentThread().getName()+"——methodAll_A方法signal()结束...");
        } finally {
            // 不管执行是否异常,都要释放锁
            lock.unlock();
        }
    }

    public void signalAll_B(){
        try {
            lock.lock();
            System.out.println("线程:"+Thread.currentThread().getName()+"——methodAll_B方法signal()开始...");
            conditionB.signalAll();
            System.out.println("线程:"+Thread.currentThread().getName()+"——methodAll_B方法signal()结束...");
        } finally {
            // 不管执行是否异常,都要释放锁
            lock.unlock();
        }
    }
}

创建线程A:执行方法A,使线程等待

public class ThreadA extends Thread {

    private MyService myService;

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

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

创建线程B:执行方法B,使线程等待

public class ThreadB extends Thread {

    private MyService myService;

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

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

创建执行类:在进行等待后,调用唤醒方法

public class ExecuteMethod {
    public static void main(String[] args) throws InterruptedException {
        MyService myService = new MyService();
        ThreadA a = new ThreadA(myService);
        a.setName("a");
        ThreadB b = new ThreadB(myService);
        b.setName("b");
        a.start();
        b.start();
        Thread.sleep(3000);
        myService.signalAll_A();// 选择性唤醒线程,置唤醒A
    }
}

测试结果:

以上输出结果,展示了使用condition对象可以实现线程的选择性通知。

三、公平锁和非公平锁

锁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();
        }
    }
}

执行结果:

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

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();
        }
    }
}

执行结果:

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

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();//中断线程
    }
}

执行结果:

接下来,把等待的方法换成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();
        }
    }

执行结果:

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();
    }
}

执行结果:

六、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) 锁在一定时间类没有被占用,获取该锁

猜你喜欢

转载自blog.csdn.net/swadian2008/article/details/100145405