Java多线程技术三:锁的使用——使用ReentrantLock类-1

发布时间:2023年12月18日

1 概述

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

2 使用ReentrantLock实现同步

? ? ? ? 下面介绍一下ReentrantLock的使用。

public class MyService {
    private Lock lock = new ReentrantLock();
    public void testMethod(){
        lock.lock();
        for (int i = 0; i < 5; i++) {
            System.out.println("ThreadName = " + Thread.currentThread().getName() + " = "+(i+1));
        }
        lock.unlock();
    }
}

? ? ? ? 调用ReentrantLock对象的lock方法获取锁,调用unlock方法释放锁,这两个方法成对出现。想要实现同步代码,把这些代码放在lock和unlock之间即可。

public class MyThread extends Thread{
    private MyService service;

    public MyThread(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.testMethod();
    }
}
public class Run1 {
    public static void main(String[] args) {
        MyService service = new MyService();
        MyThread a1 = new MyThread(service);
        MyThread a2 = new MyThread(service);
        MyThread a3 = new MyThread(service);
        MyThread a4 = new MyThread(service);
        MyThread a5 = new MyThread(service);
        a1.start();
        a2.start();
        a3.start();
        a4.start();
        a5.start();
    }
}

3dd0cdc4f3b84a2383bbe44be675c323.png

? ? ? ? 从运行结果看,当前线程打印完毕之后将锁释放,其他线程才可以继续抢锁并打印,每个线程打印的数据是有序的。即从1到5。因为当前线程已经持有锁,具有互斥排他性,而线程之间打印的顺序是随机的,所以谁抢到锁,谁打印。?

3 验证多个代码块之间的同步性

public class MyService {
    private Lock lock = new ReentrantLock();
    public void methodA(){
        try {
            lock.lock();
            System.out.println("methodA开始执行,线程名= " + Thread.currentThread().getName() + ",开始时间 = " + Utils.data(System.currentTimeMillis()));
            Thread.sleep(5000);
            System.out.println("methodA执行完成,线程名= " + Thread.currentThread().getName() + ",结束时间 = " + Utils.data(System.currentTimeMillis()));
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void methodB(){
        try {
            lock.lock();
            System.out.println("methodB 开始执行,线程名= " + Thread.currentThread().getName() + ",开始时间 = " + Utils.data(System.currentTimeMillis()));
            Thread.sleep(5000);
            System.out.println("methodB 执行完成,线程名= " + Thread.currentThread().getName() + ",结束时间 = " + Utils.data(System.currentTimeMillis()));
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class ThreadA extends Thread{
    private  MyService myService;

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

    @Override
    public void  run(){
        myService.methodA();
    }
}
public class ThreadAA extends Thread{
    private  MyService myService;

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

    @Override
    public void  run(){
        myService.methodA();
    }
}
public class ThreadB extends Thread{
    private  MyService myService;

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

    @Override
    public void  run(){
        myService.methodB();
    }
}
public class ThreadBB extends Thread{
    private  MyService myService;

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

    @Override
    public void  run(){
        myService.methodB();
    }
}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB aa = new ThreadB(service);
        aa.setName("AA");
        aa.start();
        Thread.sleep(100);
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
        ThreadB bb = new ThreadB(service);
        bb.setName("BB");
        bb.start();
    }
}

49798ba50d44441f9f9fe30de52e635b.png

? ? ? ? 这个实验说明,不管在一个方法还是多个方法的环境中,哪个线程持有锁,哪个线程就执行业务,其他线程只能等待锁被释放再次争抢,抢到了才开始执行业务,运行效果和使用synchronzied关键字一样。线程之间执行的顺序是随机的。

4 方法await()的错误用法与解决方案

? ? ? ? 关键字synchronzied与wait()方法和notify()/notifyAll()方法相结合,可以实现等待/通知模式,ReentrantLock类借助于Condition对象,也可以实现同样的功能。Condition类实在JDK5出现的技术,使用它可以获得更好的灵活性,比如实现多路通知功能,也就是在1个Lock对象里面创建多个Condition实例,并且线程对象注册在指定的Condition中,从而有选择性地进行线程通知,在调度 线程上更加灵活。

? ? ? ? 在使用notify/nofityAll()方法进行通知时,被通知的线程由JVM进行选择,而notifyAll()方法会通知所有的WAITING线程,没有选择权,这对运行效率有相当大的影响。使用ReentrantLock结合Condition类可以实现选择性通知,让通知更具有针对性,这个功能在condition类中是默认提供的。

? ? ? ? Condition?对象的作用是控制并处理线程的状态,它可以是线程处于等待状态,也可以让线程继续运行。

public class MyService {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    
    public void await(){
        try {
            condition.await();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

? ? ? ? await()方法的作用是使当前线程在接到通知或被中断之前,一直处于等待状态。它和wait方法一样。

public class ThreadA extends Thread{
    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }
    
    @Override
    public void run(){
        service.await();
    }
}
public class Run1 {
    public static void main(String[] args) {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.start();
    }
}

3c7b6403aa31499ab7a80f3dfa219c06.png

? ? ? ? 报错的异常信息是监视器出错,解决这个问题必须要在condition.await方法调用之前调用lock.lock方法获得锁。修改MyService.java类和Run1.java

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

    public void await(){
        try {
            lock.lock();
            System.out.println("A");
            condition.await();
            System.out.println("B");
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
            System.out.println("锁释放了");
        }
    }
}

?

public class Run1 {
    public static void main(String[] args) {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.start();
        ThreadA b = new ThreadA(service);
        b.start();
        ThreadA c = new ThreadA(service);
        c.start();
    }
}

70100243244c4811a6756f360fcd4f34.png

? ? ? ? 运行结果是在控制台打印了3个字母A,说明调用Condition 对象的await方法将当前执行任务的线程转换成等待状态并释放锁。

5 使用await()和signal()方法实现wait/notify

public class MyService {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void await(){
        try {
            lock.lock();
            System.out.println("开始执行await方法,时间 = " + Utils.data(System.currentTimeMillis()));
            condition.await();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public  void signal(){
        try {
            lock.lock();
            System.out.println("开始执行signal方法,时间 = " + Utils.data(System.currentTimeMillis()));
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
}
public class ThreadA extends Thread{
    private MyService service;

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

29227d87b7b14576a6b7cf4adc8a6a65.png

? ? ? ? 成功实现了等待/通知模式,注意以下几点:

? ? ? ? Object类的wait方法相当于Condition类中的await方法?。

? ? ? ? Object类中的wait(long timeout)方法相当于Condition类中的await(long time,TimeUnit unit)

? ? ? ?Object类的notify方法相当于Condition类中的signal方法?。

???????Object类中的notifyAll方法相当于Condition类中的signalAll方法

6 通知部分线程:错误用法

? ? ? ? 除了使用一个Condition对象来实现等待/通知模式,也可以创建多个Condition对象,那么一个Condition对象和多个Condition对象在使用上有什么区别?

public class MyService {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void awaitA(){
        try {
            lock.lock();
            System.out.println("开始执行awaitA方法的时间 = " + Utils.data(System.currentTimeMillis()) + ";线程名= " + Thread.currentThread().getName());
            condition.await();
            System.out.println("执行完成awaitA方法的时间 = " + Utils.data(System.currentTimeMillis()) + ";线程名= " + Thread.currentThread().getName());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void awaitB(){
        try {
            lock.lock();
            System.out.println("开始执行awaitB方法的时间 = " + Utils.data(System.currentTimeMillis()) + ";线程名= " + Thread.currentThread().getName());
            condition.await();
            System.out.println("执行完成awaitB方法的时间 = " + Utils.data(System.currentTimeMillis()) + ";线程名= " + Thread.currentThread().getName());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    
    public void signalAll(){
        try {
            lock.lock();
            System.out.println("执行  signalAll时间 = " + Utils.data(System.currentTimeMillis()) + ";线程名= " + Thread.currentThread().getName());
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
public class ThreadA extends Thread{
    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.awaitA();
    }
}
public class ThreadB extends Thread{
    private MyService service;

    public ThreadB(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.awaitB();
    }
}
public class Run1 {
    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.signalAll();
    }
}

28f1aab8b9c34a2eafb7225c8e7d1a89.png

? ? ? ? 根据运行结果发现,3秒后,线程A和线程B都被唤醒了。如果想单独唤醒部分线程该怎么处理呢。这时就有必要使用多个Condition对象了。?Condition对象可以用于唤醒部分指定线程,有助于提升程序运行的效率,可以先对线程进行分组,然后唤醒指定组中的线程。

7 通知部分线程:正确用法

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("开始执行awaitA方法,时间 = " + Utils.data(System.currentTimeMillis()) + ";线程名 = " + Thread.currentThread().getName());
            conditionA.await();
            System.out.println("执行完成awaitA方法,时间 = " + Utils.data(System.currentTimeMillis()) + ";线程名 = " + Thread.currentThread().getName());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void awaitB(){
        try {
            lock.lock();
            System.out.println("开始执行awaitB方法,时间 = " + Utils.data(System.currentTimeMillis()) + ";线程名 = " + Thread.currentThread().getName());
            conditionA.await();
            System.out.println("执行完成awaitB方法,时间 = " + Utils.data(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 方法,时间 = " + Utils.data(System.currentTimeMillis()) + ";线程名 = " + Thread.currentThread().getName());
            conditionA.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public void signalAll_B(){
        try {
            lock.lock();
            System.out.println("开始执行 signalAll_B 方法,时间 = " + Utils.data(System.currentTimeMillis()) + ";线程名 = " + Thread.currentThread().getName());
            conditionA.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
public class ThreadA extends Thread{
    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.awaitA();
    }
}
public class ThreadB extends Thread{
    private MyService service;

    public ThreadB(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.awaitB();
    }
}
public class Run1 {
    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.signalAll_A();
    }
}

cc36339e4cdf4694a438ce0563541661.png

? ? ? ? 通过运行结果可以看出,3秒后只有线程A被唤醒了,线程B没有被唤醒。

? ? ? ? 使用Condition对象可以唤醒执行种类的线程,这是控制部分线程行为的便捷方法。

8 实现生产者/消费者模式1对1交替执行

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

    public void set(){
        try {
            lock.lock();
            if(hasValue == true){
                condition.await();
            }
            System.out.println("打印A");
            hasValue = true;
            condition.signal();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void get(){
        try {
            lock.lock();
            if(hasValue == false){
                condition.await();
            }
            System.out.println("打印B");
            hasValue = false;
            condition.signal();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

?

public class ThreadA extends Thread{
    private MyService service = new MyService();

    public ThreadA(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            service.set();
        }
    }
}
public class ThreadB extends Thread{
    private MyService service;

    public ThreadB(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            service.get();
        }
    }
}
public class Run1 {
    public static void main(String[] args) {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.start();
        ThreadB b = new ThreadB(service);
        b.start();
    }
}

8456f9d7bf9a41f49d9180272bfe2ca8.png

通过使用Condition对象,实现生产者/消费者交替执行的效果。?

?

文章来源:https://blog.csdn.net/geminigoth/article/details/134853074
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。