Java Lock 显式锁案例|ReentrantLock 解决多线程安全问题(超详细实战)

一、案例需求

自定义数组集合,开启两个线程同时执行添加元素操作:

  • 线程 1:添加字符串 "F"
  • 线程 2:添加字符串 "e"

不做同步:会出现数据覆盖、索引错乱、线程安全问题 使用 ReentrantLock 显式锁 保证多线程原子操作,保证数据安全。

二、知识点铺垫

1. Lock 锁简介

  • Lock 是 java.util.concurrent.locks 包下的显式锁接口
  • 常用实现类:ReentrantLock(可重入锁)
  • 区别于 synchronized(隐式锁自动上锁解锁)
  • 手动上锁、手动解锁,控制粒度更精细、更灵活

2. 核心方法

  • lock():手动获取锁,锁被占用则阻塞等待
  • unlock():手动释放锁(必须放在 finally 中,保证一定释放)

三、完整可运行代码

复制代码
package Test_07;

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

/**
 * 本案例演示:ReentrantLock 显式锁解决多线程数据安全问题
 */
public class Test_03 {
	public static void main(String[] args) throws InterruptedException {
		// 共享资源对象(临界资源)
		MyList3 list = new MyList3();
		
		// 创建两个线程
		ThreadF t1 = new ThreadF(list);
		ThreadE t2 = new ThreadE(list);
		
		// 启动线程
		t1.start();
		t2.start();
		
		// 主线程等待两个子线程执行完毕
		t1.join();
		t2.join();
		
		// 最终遍历输出集合数据
		list.printList();
	}
}

// 线程1:添加元素 F
class ThreadF extends Thread {
	private MyList3 list;

	public ThreadF(MyList3 list) {
		this.list = list;
	}

	@Override
	public void run() {
		list.add("F");
	}
}

// 线程2:添加元素 e
class ThreadE extends Thread {
	private MyList3 list;

	public ThreadE(MyList3 list) {
		this.list = list;
	}

	@Override
	public void run() {
		list.add("e");
	}
}

// 自定义数组集合类(存在临界资源)
class MyList3 {
	// 底层数组容器
	private String[] str = {"A", "B", "", ""};
	// 元素下标计数器(临界资源,多线程共享)
	private int index = 2;

	// 创建显式锁对象(唯一锁对象,保证线程互斥)
	private Lock lock = new ReentrantLock();

	/**
	 * 加锁的添加方法
	 */
	public void add(String s) {
		// 1.手动上锁
		lock.lock();
		try {
			// 原子操作:赋值 + 休眠模拟业务耗时 + 索引自增
			str[index] = s;
			Thread.sleep(500);
			index++;
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			// 2.手动释放锁(finally 保证锁一定释放,不死锁)
			lock.unlock();
		}
	}

	/**
	 * 遍历打印集合所有元素
	 */
	public void printList() {
		System.out.println("数据中实际存储元素的个数:" + index);
		for (int i = 0; i < str.length; i++) {
			System.out.print(str[i] + " ");
		}
		System.out.println();
	}
}

四、代码核心解析

1. 临界资源

  • String[] str 数组容器
  • int index 共享索引计数器

两个线程同时操作这两个变量,属于多线程临界资源。

2. 加锁原理

  1. 两个线程争抢 同一个 ReentrantLock 锁对象
  2. 抢到锁的线程进入 try 代码块执行完整业务
  3. 执行完毕进入 finally 强制释放锁
  4. 另一个线程才能进入执行

3. sleep 不释放锁

Thread.sleep(500)

  • 休眠只会释放 CPU 资源
  • 不会释放 Lock 锁
  • 保证整个添加操作是完整原子操作,不会被打断

五、运行结果

复制代码
数据中实际存储元素的个数:4
A B F e 

结果说明

  • 两个线程依次执行,没有数据覆盖
  • 索引正常递增,数据完整有序
  • 线程同步成功,解决并发安全问题

六、如果不加锁会出现什么问题?

  1. 两个线程同时赋值同一个下标
  2. 数据覆盖、丢失数据
  3. index 错乱,元素个数统计错误
  4. 多线程并发安全问题

七、Lock 锁 VS synchronized 面试总结

  1. synchronized:隐式锁,自动上锁、自动解锁,语法简单
  2. Lock(ReentrantLock):显式锁,手动上锁、手动解锁
  3. Lock 灵活性更高、可重试、可超时、可公平锁
  4. Lock 必须在 finally 解锁,防止死锁

八、核心考点背诵

  1. ReentrantLock 属于 java.util.concurrent.locks 包
  2. Lock 是显式锁,需要手动 lock ()、unlock ()
  3. unlock () 必须放在 finally 中,保证锁释放
  4. sleep 不释放锁、保证原子操作完整性
  5. 多线程必须使用同一个锁对象才能实现同步
相关推荐
海鸥-w3 小时前
什么是AOP
java
Cc.Y3 小时前
Java零基础入门:可变字符串与包装类:StringBuilder、StringBuffer 与 通讯录管理系统实战
java·开发语言·python
启观川3 小时前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·笔记·python·算法
paopaokaka_luck3 小时前
基于springboot+vue的智慧家政管理系统(AI问答、 WebSocket实时沟通、ECharts运营分析、订单规则与服务追踪)
java·javascript·spring boot·数据分析·echarts·mybatis
sm_926787053 小时前
RFID 标签打印的技术实现要点与二次开发实践
java·大数据·前端·c++·编辑器
阿森_dev3 小时前
2027 届毕设选题怎么选:30 个 Java 方向选题的实现难度排序
java·开发语言·spring boot·毕业设计·课程设计
Sarvartha4 小时前
Lambda 表达式和函数式接口
java·开发语言
计算机毕业设计杰瑞4 小时前
【计算机毕设精品】基于SpringBoot的一体化教务与学业质量管理系统的设计与实现,附源码_增删改查_java项目_可完美运行部署_网站项目
java·spring boot·课程设计
一木 之林4 小时前
Dify学习笔记 01 · 平台入门与实战:部署、五类应用、RAG与MCP(第1-16集)
人工智能·笔记·学习·计算机视觉
colinu4 小时前
C++ 异步任务与线程生命周期:`std::async`、`future`、`thread`、`jthread` 与协作式退出
java·jvm·c++