【JAVA进阶篇教学】第十一篇:Java中ReentrantLock锁讲解

简介: 【JAVA进阶篇教学】第十一篇:Java中ReentrantLock锁讲解

博主打算从0-1讲解下java进阶篇教学,今天教学第十篇:Java中ReentrantLock锁讲解。


在Java并发编程中,保证多线程环境下的数据安全是至关重要的。ReentrantLock 是Java中用于实现线程安全的一种锁机制。本篇博客将深入介绍 ReentrantLock 的原理、详细说明,并通过案例演示线程不安全情况以及如何使用 ReentrantLock 实现线程安全。


一、原理

ReentrantLock 是Java中的一种锁实现,它具有可重入性,即同一线程可以多次获取同一把锁而不会出现死锁。它使用了一种互斥锁的机制,确保了在同一时刻只有一个线程可以访问被锁定的代码块或方法。


ReentrantLock是 Java 中的一个可重入锁类,它实现了Lock接口。ReentrantLock的原理主要涉及以下几个方面:


  1. 锁状态:ReentrantLock通过一个内部的锁状态来表示当前锁的占用情况。锁状态可以是未锁定、锁定和重入锁定等状态。
  2. 获取锁:当线程调用lock方法时,它会尝试获取锁。如果锁当前没有被其他线程占用,那么该线程将成功获取锁,并将锁状态设置为锁定。如果锁已经被其他线程占用,那么当前线程将被阻塞,直到锁被释放。
  3. 释放锁:当线程调用unlock方法时,它会释放锁。如果当前线程持有锁,那么它将把锁状态设置为未锁定,并唤醒等待获取锁的线程。
  4. 可重入性:ReentrantLock支持可重入性,即同一个线程可以多次获取同一个锁。在获取锁时,锁的持有计数会增加,在释放锁时,锁的持有计数会减少。只有当锁的持有计数为 0 时,锁才会被完全释放。
  5. 公平性:ReentrantLock可以选择是否采用公平锁策略。公平锁保证线程按照先来先服务的顺序获取锁,而不公平锁则允许线程抢占锁。
  6. 条件变量:ReentrantLock还提供了条件变量的支持,可以用于实现线程的等待和通知机制。线程可以在满足特定条件时等待,直到其他线程通知条件满足。


二、代码测试

1.线程不安全案例

public class UnsafeCounter {
    private int count = 0;
 
    public void increment() {
        count++;
    }
 
    public int getCount() {
        return count;
    }
 
    public static void main(String[] args) {
        UnsafeCounter counter = new UnsafeCounter();
        // 创建两个线程并发增加计数
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
 
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
 
        thread1.start();
        thread2.start();
 
        // 等待两个线程执行完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        // 输出最终计数值
        System.out.println("Count: " + counter.getCount()); // 预期结果: 可能小于 2000
    }
}

在这个示例中,由于 increment() 方法没有同步控制,两个线程同时对 count 进行增加操作,可能导致计数不准确。得到的结果偶尔可能是正确的2000。

2.线程安全案例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class SafeCounter {
    private int count = 0;
    private Lock lock = new ReentrantLock();
 
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
 
    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
 
    public static void main(String[] args) {
        SafeCounter counter = new SafeCounter();
        // 创建两个线程并发增加计数
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
 
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
 
        thread1.start();
        thread2.start();
 
        // 等待两个线程执行完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        // 输出最终计数值
        System.out.println("Count: " + counter.getCount()); // 预期结果: 可能小于 2000
    }
}

在这个示例中,使用 ReentrantLock 来确保了对 count 的操作是线程安全的,保证了最终输出的计数值是准确的。

三、公平性

公平性是指在多线程环境下,锁的获取顺序应该遵循先来先服务的原则,即先请求锁的线程应该先获得锁。在 Java 中,可以通过设置ReentrantLock的构造函数参数来选择使用公平锁或非公平锁。以下是一个使用公平锁的示例代码:

public class ReentrantLockTest extends Thread {
    private static ReentrantLock lock = new ReentrantLock(true); // 创建公平锁
 
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            lock.lock(); // 获取锁
            try {
                System.out.println(Thread.currentThread().getName() + " 获得锁");
                // 执行临界区操作
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); // 释放锁
            }
        }
    }
 
    public static void main(String[] args) {
        ReentrantLockTest thread1 = new ReentrantLockTest();
        ReentrantLockTest thread2 = new ReentrantLockTest();
        ReentrantLockTest thread3 = new ReentrantLockTest();
 
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

默认情况下非公平锁

Lock lock = new ReentrantLock();

创建公平锁

Lock fairLock = new ReentrantLock(true);

四、条件变量

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
 
public class ConditionVariableExample {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
 
    public void waitForCondition() {
        try {
            lock.lock();
            System.out.println("线程等待条件满足...");
            condition.await();
            System.out.println("线程收到通知,条件满足!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
 
    public void notifyCondition() {
        try {
            lock.lock();
            System.out.println("通知线程,条件满足...");
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
 
    public static void main(String[] args) {
        ConditionVariableExample example = new ConditionVariableExample();
 
        // 创建并启动等待条件的线程
        Thread waitingThread = new Thread(() -> {
            example.waitForCondition();
        });
        waitingThread.start();
 
        // 模拟条件满足的情况
        // 可以在其他地方执行此操作,以通知等待线程条件已满足
        example.notifyCondition();
    }
}

在上述示例中,我们创建了一个ReentrantLock对象lock和一个与之关联的条件变量condition。


  1. waitForCondition方法用于线程等待条件满足。在方法内部,首先获取锁,然后打印出等待消息,并使用condition.await方法使线程等待条件满足。在等待过程中,线程会释放锁,并进入阻塞状态。
  2. notifyCondition方法用于通知等待条件的线程。在方法内部,获取锁后,打印出通知消息,并使用condition.signalAll方法通知所有等待条件的线程。


在main方法中,我们创建了一个等待条件的线程waitingThread,并启动它。然后,模拟条件满足的情况,调用notifyCondition方法通知等待线程。

通过使用ReentrantLock和条件变量,我们可以实现线程之间的同步和协作,确保在特定条件满足时执行相应的操作。

目录
打赏
0
2
2
0
18
分享
相关文章
|
9天前
|
【源码】【Java并发】【ReentrantLock】适合中学者体质的ReentrantLock源码阅读
因为本文说的是ReentrantLock源码,因此会默认,大家对AQS有基本的了解(比如同步队列、条件队列大概> 长啥样?)。 不懂AQS的小朋友们,你们好呀!也欢迎先看看这篇
56 13
【源码】【Java并发】【ReentrantLock】适合中学者体质的ReentrantLock源码阅读
|
14天前
|
【源码】【Java并发】【AQS】从ReentrantLock、Semaphore、CutDownLunch、CyclicBarrier看AQS源码
前言 主播觉得,AQS的原理,就是通过这2个队列的协助,实现核心功能,同步队列(CLH队列)和条件队列(Condition队列)。 同步队列(CLH队列) 作用:管理需要获...
59 18
【源码】【Java并发】【AQS】从ReentrantLock、Semaphore、CutDownLunch、CyclicBarrier看AQS源码
|
12天前
|
【Java并发】【ReentrantLock】适合初学体质的ReentrantLock入门
前言 什么是ReentrantLock? ReentrantLock 是 Java 并发包 (java.util.concurrent.locks) 中的一个类,它实现了 Lock 接口,提供了与
58 10
【Java并发】【ReentrantLock】适合初学体质的ReentrantLock入门
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
锁状态bits1bit是否是偏向锁2bit锁标志位无锁状态对象的hashCode001偏向锁线程ID101轻量级锁指向栈中锁记录的指针000重量级锁指向互斥量的指针010尼恩提示,讲完 如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等优化手段 , 可以得到 120分了。如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等‌。JVM锁的膨胀、锁的内存结构变化相关的面试题,是非常常见的面试题。也是核心面试题。
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
本文介绍了几种常见的锁机制,包括公平锁与非公平锁、可重入锁与不可重入锁、自旋锁以及读写锁和互斥锁。公平锁按申请顺序分配锁,而非公平锁允许插队。可重入锁允许线程多次获取同一锁,避免死锁。自旋锁通过循环尝试获取锁,减少上下文切换开销。读写锁区分读锁和写锁,提高并发性能。文章还提供了相关代码示例,帮助理解这些锁的实现和使用场景。
160 4
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
|
5月前
|
Java 中的锁是什么意思,有哪些分类?
在Java多线程编程中,锁用于控制多个线程对共享资源的访问,确保数据一致性和正确性。本文探讨锁的概念、作用及分类,包括乐观锁与悲观锁、自旋锁与适应性自旋锁、公平锁与非公平锁、可重入锁和读写锁,同时提供使用锁时的注意事项,帮助开发者提高程序性能和稳定性。
218 3
|
6月前
|
Java 中锁的主要类型
【10月更文挑战第10天】
java的synchronized有几种加锁方式
Java的 `synchronized`通过上述三种加锁方式,为开发者提供了从粗粒度到细粒度的并发控制能力,满足了不同场景下的线程安全需求。合理选择加锁方式对于提升程序的并发性能和正确性至关重要,开发者应根据实际应用场景的特性和性能要求来决定使用哪种加锁策略。
91 0
java源码 - ReentrantLock之FairSync
开篇  这篇文章主要是讲解FairSync公平锁的源码分析,整个内容分为加锁过程、解锁过程,CLH队列等概念。  首先一直困扰我的CLH队列的CLH的缩写我终于明白,看似三个人的人名的首字符缩写"CLH" (Craig, Landin, andHagersten)。
1052 0

热门文章

最新文章

AI助理

你好,我是AI助理

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