Java 并发编程详解:Lock 接口及其实现 ReentrantLock

简介: Java 并发编程详解:Lock 接口及其实现 ReentrantLock

Java 并发编程详解:Lock 接口及其实现 ReentrantLock

在 Java 并发编程中,锁机制是确保多线程环境下数据一致性和安全性的关键。Java 提供了 Lock 接口及其实现类 ReentrantLock,相比于传统的 synchronized 关键字,提供了更多的控制和灵活性。本文将详细介绍 Lock 接口及其实现 ReentrantLock,包括其特性、使用方法和实际应用示例。


一、Lock 接口

Lock 接口是 Java 并发包中定义的一个接口,用于实现锁的机制。与 synchronized 相比,Lock 接口提供了更多的锁控制方式和更加灵活的操作。

1. 主要方法

  • void lock(): 获取锁。如果锁已经被另一个线程持有,则当前线程会被阻塞,直到获得锁。
  • void lockInterruptibly() throws InterruptedException: 获取锁,但在等待时可以响应中断。
  • boolean tryLock(): 尝试获取锁。如果成功获取到锁,返回 true,否则返回 false。不会阻塞线程。
  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException: 在指定的时间内尝试获取锁。
  • void unlock(): 释放锁。
  • Condition newCondition(): 返回一个绑定到此 Lock 实例的新 Condition 实例,用于实现等待/通知机制。


二、ReentrantLock 类

ReentrantLock 是 Lock 接口的一个具体实现类,是一个可重入锁。可重入锁的特点是,持有锁的线程可以多次获取该锁而不会被阻塞。

1. 特性

  • 可重入性: 持有锁的线程可以再次获取该锁,不会被阻塞。
  • 公平锁和非公平锁: ReentrantLock 支持公平锁和非公平锁。公平锁按照线程请求的顺序分配锁,而非公平锁则无序分配,可能会使某些线程长期得不到锁。
  • Condition 支持: 提供 Condition 对象,实现线程间的协调,类似于 Object 类中的 wait、notify 和 notifyAll 方法。

2. 构造方法

  • ReentrantLock(): 创建一个默认的非公平锁。
  • ReentrantLock(boolean fair): 创建一个公平或非公平锁,取决于参数 fair 的值。


三、ReentrantLock 的使用

1. 基本使用

使用 ReentrantLock 进行锁操作,通常需要配合 try-finally 语句,确保锁在不再需要时被释放。

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

public class ReentrantLockExample {
    private final Lock lock = new ReentrantLock();
    private int counter = 0;

    public void increment() {
        lock.lock(); // 获取锁
        try {
            counter++;
        } finally {
            lock.unlock(); // 确保在最后释放锁
        }
    }

    public int getCounter() {
        lock.lock();
        try {
            return counter;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();

        // 创建多个线程来测试 ReentrantLock
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final Counter: " + example.getCounter());
    }
}

2. 使用 Condition 实现生产者-消费者模型

ReentrantLock 提供的 Condition 可以更灵活地实现线程间的协调,类似于传统的 wait 和 notify。

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

public class ProducerConsumerExample {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final int[] buffer = new int[10];
    private int count, putptr, takeptr;

    public void put(int value) throws InterruptedException {
        lock.lock();
        try {
            while (count == buffer.length) { // 缓冲区已满
                notFull.await();
            }
            buffer[putptr] = value;
            if (++putptr == buffer.length) putptr = 0;
            count++;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public int take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) { // 缓冲区为空
                notEmpty.await();
            }
            int value = buffer[takeptr];
            if (++takeptr == buffer.length) takeptr = 0;
            count--;
            notFull.signal();
            return value;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ProducerConsumerExample example = new ProducerConsumerExample();

        Runnable producer = () -> {
            for (int i = 0; i < 100; i++) {
                try {
                    example.put(i);
                    System.out.println("Produced: " + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Runnable consumer = () -> {
            for (int i = 0; i < 100; i++) {
                try {
                    int value = example.take();
                    System.out.println("Consumed: " + value);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread producerThread = new Thread(producer);
        Thread consumerThread = new Thread(consumer);

        producerThread.start();
        consumerThread.start();

        try {
            producerThread.join();
            consumerThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


四、总结

  • Lock 接口 提供了比 synchronized 关键字更灵活和丰富的锁机制。
  • ReentrantLock 是 Lock 接口的一个实现,支持可重入、可选择的公平锁以及提供 Condition 对象来实现复杂的线程间协调。
  • 使用 ReentrantLock 可以提高多线程环境下程序的并发性能和可控性,适用于需要复杂锁控制的场景。


通过合理使用 Lock 接口及其实现 ReentrantLock,可以更有效地管理并发,确保线程安全,提高程序的可靠性和性能。

目录
打赏
0
1
1
0
22
分享
相关文章
JAVA接入DeepSeek大模型接口开发---阿里云的百炼模型
随着大模型的越来越盛行,现在很多企业开始接入大模型的接口,今天我从java开发角度来写一个demo的示例,用于接入DeepSeek大模型,国内的大模型有很多的接入渠道,今天主要介绍下阿里云的百炼模型,因为这个模型是免费的,只要注册一个账户,就会免费送百万的token进行学习,今天就从一个简单的可以执行的示例开始进行介绍,希望可以分享给各位正在学习的同学们。
138 3
JAVA接入DeepSeek大模型接口开发---阿里云的百炼模型
k8s的出现解决了java并发编程胡问题了
Kubernetes通过提供自动化管理、资源管理、服务发现和负载均衡、持续交付等功能,有效地解决了Java并发编程中的许多复杂问题。它不仅简化了线程管理和资源共享,还提供了强大的负载均衡和故障恢复机制,确保应用程序在高并发环境下的高效运行和稳定性。通过合理配置和使用Kubernetes,开发者可以显著提高Java应用程序的性能和可靠性。
64 31
注解的艺术:Java编程的高级定制
注解是Java编程中的高级特性,通过内置注解、自定义注解及注解处理器,可以实现代码的高度定制和扩展。通过理解和掌握注解的使用方法,开发者可以提高代码的可读性、可维护性和开发效率。在实际应用中,注解广泛用于框架开发、代码生成和配置管理等方面,展示了其强大的功能和灵活性。
64 25
在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境
以上内容是一个简单的实现在Java后端中通过DockerClient操作Docker生成python环境并执行代码,最后销毁的案例全过程,也是实现一个简单的在线编程后端API的完整流程,你可以在此基础上添加额外的辅助功能,比如上传文件、编辑文件、查阅文件、自定义安装等功能。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
263 5
课时6:Java编程起步
课时6:Java编程起步,主讲人李兴华。课程摘要:介绍Java编程的第一个程序“Hello World”,讲解如何使用记事本或EditPlus编写、保存和编译Java源代码(*.java文件),并解释类定义、主方法(public static void main)及屏幕打印(System.out.println)。强调类名与文件名一致的重要性,以及Java程序的编译和执行过程。通过实例演示,帮助初学者掌握Java编程的基本步骤和常见问题。
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
109 5
Java 并发编程——volatile 关键字解析
|
4月前
|
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
331 2
|
10月前
|
关于《Java并发编程之线程池十八问》的补充内容
【6月更文挑战第6天】关于《Java并发编程之线程池十八问》的补充内容
71 5
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。