Java多线程核心技术(四)Lock的使用

简介: 本文主要介绍使用Java5中Lock对象也能实现同步的效果,而且在使用上更加方便。

本文主要介绍使用Java5中Lock对象也能实现同步的效果,而且在使用上更加方便。

本文着重掌握如下2个知识点:

ReentrantLock 类的使用。

ReentrantReadWriteLock 类的使用。

1. 使用ReentrantLock 类

在Java多线程中,可以使用 synchronized 关键字来实现线程之间同步互斥,但在JDK1.5中新增加了 ReentrantLock 类也能达到同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定、多路分支通知等功能,而且在使用上也比 synchronized 更加的灵活。

1.1 使用ReentrantLock实现同步

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

下面是初步的程序示例:

public class Demo {
    private Lock lock = new ReentrantLock();

    public void test(){
        lock.lock();
        for (int i= 0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+" - "+i);
        }
        lock.unlock();
    }

    public static void main(String[] args) {
        Demo demo =  new Demo();
        for (int i = 0;i<5;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.test();
                }
            }).start();
        }
    }
}

运行结果:

Thread-0 - 0
Thread-0 - 1
Thread-0 - 2
Thread-0 - 3
Thread-0 - 4
Thread-1 - 0
Thread-1 - 1
Thread-1 - 2
Thread-1 - 3
Thread-1 - 4
Thread-2 - 0
Thread-2 - 1
Thread-2 - 2
Thread-2 - 3
Thread-2 - 4
Thread-3 - 0
Thread-3 - 1
Thread-3 - 2
Thread-3 - 3
Thread-3 - 4
Thread-4 - 0
Thread-4 - 1
Thread-4 - 2
Thread-4 - 3
Thread-4 - 4

从运行的结果来看,当前线程打印完毕后将锁进行释放,其他线程才可以继续打印。

1.1.2 锁住类的所有实例对象

上面的示例是所有线程调用一个ReentrantLock实例对象实现同步,如果每个线程都调用各自ReentrantLock实例对象的同一段代码呢?

示例代码:

public class MyService implements Runnable{
    private ReentrantLock lock = new ReentrantLock();

    public void method(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"锁定...");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName()+"解锁。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) {
        new Thread(new MyService()).start();
        new Thread(new MyService()).start();
        new Thread(new MyService()).start();
    }

    @Override
    public void run() {
        method();
    }
}

运行结果:

Thread-0锁定...
Thread-2锁定...
Thread-1锁定...
Thread-2解锁。
Thread-0解锁。
Thread-1解锁。

从运行结果来看,并没有实现想要的方法同步的效果。如果我们想要实现类似synchronized(class),也就是给Class类上锁,可以把 ReentrantLock 声明为 static 静态变量。

示例代码:

public class MyService implements Runnable{
    private static ReentrantLock lock = new ReentrantLock();

    public void method(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"锁定...");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName()+"解锁。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) {
        new Thread(new MyService()).start();
        new Thread(new MyService()).start();
        new Thread(new MyService()).start();
    }

    @Override
    public void run() {
        method();
    }
}

运行结果:

Thread-0锁定...
Thread-0解锁。
Thread-1锁定...
Thread-1解锁。
Thread-2锁定...
Thread-2解锁。

从运行结果来看,成功实现了预期的结果。

1.2 使用Condition 实现等待 / 通知

关键字 synchronized 与 wait() 和 notify() / notifyAll() 方法相结合可以实现等待 / 通知模式,类 ReentrantLock 也可以实现同样的功能,但需要借助于 Condition(即对象监视器)实例,线程对象可以注册在指定的 Condition 中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。

在使用 notify() / notifyAll() 方法进行通知时,被通知的线程却是由JVM随机选择的。但使用 ReentrantLock 结合 Condition 类是可以实现前面介绍过的“选择性通知”,这个功能是非常重要的,而且在 Condition 类中是默认提供的。

示例代码:

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

    public void await() {
        try {
            lock.lock();
            System.out.println("开始等待:" + System.currentTimeMillis());
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signal() {
        try {
            lock.lock();
            System.out.println("结束等待:" + System.currentTimeMillis());
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Demo demo = new Demo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.await();
            }
        }).start();
        Thread.sleep(3000);
        demo.signal();
    }
}

运行结果:

开始等待:1537352883839
结束等待:1537352886839
成功实现等待 / 通知模式。

在Object中,有wait() 、wait(long)、notify()、notifyAll()方法。

在Condition类中,有 await()、await(long)、signal()、signalAll()方法。

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

示例代码:

public class Demo {
    private Lock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println("A开始等待:" + System.currentTimeMillis());
            conditionA.await();
            System.out.println("A结束等待:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println("B开始等待:" + System.currentTimeMillis());
            conditionB.await();
            System.out.println("B结束等待:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll_B() {
        try {
            lock.lock();
            conditionB.signalAll();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Demo demo = new Demo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.awaitA();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.awaitB();
            }
        }).start();
        Thread.sleep(3000);
        demo.signalAll_B();
    }
}

运行结果:

A开始等待:1537354021740
B开始等待:1537354021741
B结束等待:1537354024738
可以看到,只有B线程被唤醒了。

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

1.4 公平锁和非公平锁

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

设置公平锁:

Lock lock = new ReentrantLock(true);

使用ReentrantLock类设置公平锁只需要在构造时传入boolean参数即可。默认false。需要明白的是,即使设置为true也不能保证百分百公平。

总结:

公平锁:先去判断等待队列是否为空,也就是是否有线程在等待,没有就去获取锁,否则把自己加入等待队列。

非公平锁:先去尝试获取锁,如果失败再加入到等待队列。

1.5 方法getHoldCount()、getQueryLength()和getWaitQueryLength()

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

示例代码:

public class Service {
    private ReentrantLock lock = new ReentrantLock();

    public void method() {
        try {
            lock.lock();
            System.out.println("getHoldCount() " + lock.getHoldCount());
            method2();
        } finally {
            lock.unlock();
        }
    }

    public void method2() {
        try {
            lock.lock();
            System.out.println("getHoldCount() " + lock.getHoldCount());
        } finally {
            lock.unlock();
        }
    }

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

}

运行结果:

getHoldCount() 1
getHoldCount() 2

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

示例代码:

public class Service {
    private ReentrantLock lock = new ReentrantLock();

    public void method() {
        try {
            lock.lock();
            System.out.println("Name: " + Thread.currentThread().getName());
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.method();
            }
        };
        for (int i = 0; i < 5; i++) {
            new Thread(runnable).start();
        }
        Thread.sleep(1000);
        ReentrantLock lock = service.getLock();
        System.out.println("有多少线程在等待:"+lock.getQueueLength());
    }

    private ReentrantLock getLock() {
        return lock;
    }

}

运行结果:

Name: Thread-1
有多少线程在等待:4

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

示例代码:

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

    public void method() {
        try {
            lock.lock();
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void notifyMethod() {
        try {
            lock.lock();
            System.out.println("等待condition的线程数" + lock.getWaitQueueLength(condition));
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.method();
            }
        };
        for (int i = 0; i < 5; i++) {
            new Thread(runnable).start();
        }
        Thread.sleep(1000);
        service.notifyMethod();
    }

}

运行结果:

等待condition的线程数5

1.6 方法hasQueuedThread()、hasQueuedThreads()和hasWaiters()

1.方法 boolean hasQueuedThread(Thread thread) 的作用是查询指定的线程是否正在等待获取此锁定。

2.方法 boolean hasQueuedThreads() 的作用是查询是否有线程正在等待获取此锁定。

1、2示例代码:

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

    public void waitMethod(){
        try {
            lock.lock();
            Thread.sleep(Integer.MAX_VALUE);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public ReentrantLock getLock(){
        return lock;
    }

    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.waitMethod();
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
        Thread.sleep(1000);
        ReentrantLock lock = service.getLock();
        System.out.println(lock.hasQueuedThreads());
        System.out.println(lock.hasQueuedThread(thread1));
        System.out.println(lock.hasQueuedThread(thread2));
    }

}

运行结果:

true
false
true

3.方法 boolean hasWaiters(Condition condition) 的作用是查询是否有线程正在等待与此锁定有关的 condition 条件。

示例代码:

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

    public void waitMethod(){
        try {
            lock.lock();
            condition.await();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void notifyMethod(){
        try {
            lock.lock();
            System.out.println("有没有线程正在等待 condition ?" + lock.hasWaiters(condition) + " 线程数是多少?" + lock.getWaitQueueLength(condition));
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.waitMethod();
            }
        };
        for (int i = 0; i < 10; i++) {
            new Thread(runnable).start();
        }
        Thread.sleep(2000);
        service.notifyMethod();
    }

}

运行结果:

有没有线程正在等待 condition ?true 线程数是多少?10

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

方法boolean isFair() 的作用是判断是不是公平锁。

方法boolean isHeldByCurrentThread() 的作用是查询当前线程是否保持此锁定。

方法boolean isLocked() 的作用是查询此锁定是否由任意线程保持。

更改上面的部分代码:

System.out.println(lock.isHeldByCurrentThread());
System.out.println(lock.isLocked());
lock.lock();
System.out.println(lock.isLocked());
System.out.println(lock.isHeldByCurrentThread());

运行结果:

false
false
true
true

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

下面的三个方法都是对lock.lock()方法的另一种变形:

方法void lockInterruptibly()的作用是:如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常。

而使用 lock() 方法,即使线程被中断(调用thread.interrupt()方法),也不会出现异常。

方法boolean tryLock() 的作用是,仅在未被另一个线程保持的情况下,才获取该锁定。

假设有两个线程同时调用同一个lock对象的tryLock()方法,那么除了第一个获得锁(返回true),其它都获取不到锁(返回false)。

方法 boolean tryLock(long timeout, TimeUnit unit) 的作用是,如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。

1.9 方法 condition.awaitUninterruptibly()的使用

前面讲到,执行condition.await()方法后,线程进入等待状态,如果这时线程被中断(调用thread.interrupt()方法)则会抛出异常。而使用 condition.awaitUninterruptibly() 方法代替 condition.await() 方法则不会抛出异常。

1.10 方法 condition.awaitUntil(Date deadline)的使用

使用方法 condition.awaitUntil(Date deadline) 可以代替 await(long time, TimeUnit unit) 方法进行线程等待,该方法在等待时间到达前是可以被提前唤醒的。

1.11 使用Condition实现顺序执行

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

示例代码:

public class DThread{
    volatile private static int nextPrintWho = 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 threadA = new Thread(){
            @Override
            public void run() {
                try {
                    lock.lock();
                    while (nextPrintWho != 1){
                        conditionA.await();
                    }
                    for (int i = 0;i<3;i++){
                        System.out.println("ThreadA "+(i+1));
                    }
                    nextPrintWho = 2;
                    conditionB.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        };
        Thread threadB = new Thread(){
            @Override
            public void run() {
                try {
                    lock.lock();
                    while (nextPrintWho != 2){
                        conditionA.await();
                    }
                    for (int i = 0;i<3;i++){
                        System.out.println("ThreadB "+(i+1));
                    }
                    nextPrintWho = 3;
                    conditionB.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        };
        Thread threadC = new Thread(){
            @Override
            public void run() {
                try {
                    lock.lock();
                    while (nextPrintWho != 3){
                        conditionA.await();
                    }
                    for (int i = 0;i<3;i++){
                        System.out.println("ThreadC "+(i+1));
                    }
                    nextPrintWho = 1;
                    conditionB.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        };
        for (int i= 0;i<5;i++){
            new Thread(threadA).start();
            new Thread(threadB).start();
            new Thread(threadC).start();
        }
    }

}

打印结果:

ThreadA 1
ThreadA 2
ThreadA 3
ThreadB 1
ThreadB 2
ThreadB 3
ThreadC 1
ThreadC 2
ThreadC 3
....

2.使用ReentrantReadWriteLock类

类 ReentrantLock 具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock() 方法后面的任务。这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的。所以在JDK中提供了一种读写锁 ReentrantReadWriteLock 类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,完全可以使用读写 ReentrantReadWriteLock 来提升该方法的代码运行速度。
读写锁表示也有两个锁,一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也叫排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。在没有线程 Thread进行写入操作时,进行读取操作的多个 Thread 都可以获取读锁,而进行写入操作的 Thread 只有在获取写锁后才能进行写入操作。即多个 Thread可以同时进行读取操作但是同一时刻只允许一个 Thread 进行写入操作。

总结起来就是:读读共享,写写互斥,读写互斥,写读互斥。

声明读写锁:

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

获取读锁:

lock.readLock().lock();

获取写锁:

lock.writeLock().lock();

3.文末总结

学习完本文完全可以使用Lock对象将 synchronized关键字替换掉,而且其具有的独特功能也是 synchronized 所不具有的。在学习并发时,Lock是synchronized关键字的进阶,掌握Lock有助于学习并发包中源代码的实现原理,在并发包中大量的类使用了Lock 接口作为同步的处理方式。

嗨,你还在看吗?

文章来源:微信公众号 薛勤的博客

目录
相关文章
|
6天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
32 7
|
6天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
6天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
24 3
|
7天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
7天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
11天前
|
监控 Java 数据库连接
Java线程管理:守护线程与用户线程的区分与应用
在Java多线程编程中,线程可以分为守护线程(Daemon Thread)和用户线程(User Thread)。这两种线程在行为和用途上有着明显的区别,了解它们的差异对于编写高效、稳定的并发程序至关重要。
19 2
|
11天前
|
监控 Java 开发者
Java线程管理:守护线程与本地线程的深入剖析
在Java编程语言中,线程是程序执行的最小单元,它们可以并行执行以提高程序的效率和响应性。Java提供了两种特殊的线程类型:守护线程和本地线程。本文将深入探讨这两种线程的区别,并探讨它们在实际开发中的应用。
15 1
|
22天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
13天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####