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
分享
相关文章
|
10天前
|
【源码】【Java并发】【ReentrantLock】适合中学者体质的ReentrantLock源码阅读
因为本文说的是ReentrantLock源码,因此会默认,大家对AQS有基本的了解(比如同步队列、条件队列大概> 长啥样?)。 不懂AQS的小朋友们,你们好呀!也欢迎先看看这篇
56 13
【源码】【Java并发】【ReentrantLock】适合中学者体质的ReentrantLock源码阅读
|
15天前
|
【源码】【Java并发】【AQS】从ReentrantLock、Semaphore、CutDownLunch、CyclicBarrier看AQS源码
前言 主播觉得,AQS的原理,就是通过这2个队列的协助,实现核心功能,同步队列(CLH队列)和条件队列(Condition队列)。 同步队列(CLH队列) 作用:管理需要获...
59 18
【源码】【Java并发】【AQS】从ReentrantLock、Semaphore、CutDownLunch、CyclicBarrier看AQS源码
|
13天前
|
【Java并发】【ReentrantLock】适合初学体质的ReentrantLock入门
前言 什么是ReentrantLock? ReentrantLock 是 Java 并发包 (java.util.concurrent.locks) 中的一个类,它实现了 Lock 接口,提供了与
58 10
【Java并发】【ReentrantLock】适合初学体质的ReentrantLock入门
JAVA接入DeepSeek大模型接口开发---阿里云的百炼模型
随着大模型的越来越盛行,现在很多企业开始接入大模型的接口,今天我从java开发角度来写一个demo的示例,用于接入DeepSeek大模型,国内的大模型有很多的接入渠道,今天主要介绍下阿里云的百炼模型,因为这个模型是免费的,只要注册一个账户,就会免费送百万的token进行学习,今天就从一个简单的可以执行的示例开始进行介绍,希望可以分享给各位正在学习的同学们。
219 3
JAVA接入DeepSeek大模型接口开发---阿里云的百炼模型
k8s的出现解决了java并发编程胡问题了
Kubernetes通过提供自动化管理、资源管理、服务发现和负载均衡、持续交付等功能,有效地解决了Java并发编程中的许多复杂问题。它不仅简化了线程管理和资源共享,还提供了强大的负载均衡和故障恢复机制,确保应用程序在高并发环境下的高效运行和稳定性。通过合理配置和使用Kubernetes,开发者可以显著提高Java应用程序的性能和可靠性。
72 31
注解的艺术:Java编程的高级定制
注解是Java编程中的高级特性,通过内置注解、自定义注解及注解处理器,可以实现代码的高度定制和扩展。通过理解和掌握注解的使用方法,开发者可以提高代码的可读性、可维护性和开发效率。在实际应用中,注解广泛用于框架开发、代码生成和配置管理等方面,展示了其强大的功能和灵活性。
68 25
|
11天前
|
Java Lambda 表达式:以 Foo 接口为例深入解析
本文深入解析了 Java 8 中 Lambda 表达式的用法及其背后的函数式接口原理,以 `Foo` 接口为例,展示了如何通过简洁的 Lambda 表达式替代传统匿名类实现。文章从 Lambda 基本语法、函数式接口定义到实际应用层层递进,并探讨默认方法与静态方法的扩展性,最后总结常见误区与关键点,助你高效优化代码!
33 0
|
11天前
|
java中一个接口A,以及一个实现它的类B,一个A类型的引用对象作为一个方法的参数,这个参数的类型可以是B的类型吗?
本文探讨了面向对象编程中接口与实现类的关系,以及里氏替换原则(LSP)的应用。通过示例代码展示了如何利用多态性将实现类的对象传递给接口类型的参数,满足LSP的要求。LSP确保子类能无缝替换父类或接口,不改变程序行为。接口定义了行为规范,实现类遵循此规范,从而保证了多态性和代码的可维护性。总结来说,接口与实现类的关系天然符合LSP,体现了多态性的核心思想。
22 0
在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境
以上内容是一个简单的实现在Java后端中通过DockerClient操作Docker生成python环境并执行代码,最后销毁的案例全过程,也是实现一个简单的在线编程后端API的完整流程,你可以在此基础上添加额外的辅助功能,比如上传文件、编辑文件、查阅文件、自定义安装等功能。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境
Java多线程之Lock的使用
import java.util.concurrent.ExecutorService;   import java.util.concurrent.
926 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等