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. 多线程必须使用同一个锁对象才能实现同步
相关推荐
箓维16 小时前
TCP Socket的进阶实现
网络·笔记·tcp/ip
二哈赛车手16 小时前
新人笔记---hermes定时功能设计,具体讲解以及可优化点
java·人工智能·笔记·spring·ai·typescript
ZWZhangYu16 小时前
从 Dify 实践理解大模型上下文缓存
java·后端·spring
小羊没烦恼!16 小时前
关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
java·服务器·网络·学习·负载均衡
星恒随风17 小时前
Linux网络编程:应用层协议究竟是什么?从网络计算器到TCP Socket抽象基类完整实现
linux·网络·笔记·tcp/ip
Fuyo_111917 小时前
C++ AVL树保姆级详解:从平衡因子到四大旋转机制
java·c++·算法
Sarvartha18 小时前
Date、LocalDate、LocalTime、LocalDateTime、DateTimeFormatter类
java
阿俊-全栈开发18 小时前
LikeShop 多商户 Java 版商城微服务模块拆分与部署架构演进实践
java·微服务·架构·likeshop开源商城·多商户源码
Wang's Blog18 小时前
Java 项目实战:交友项目 - 联系人列表
java·开发语言·交友
智学AI18 小时前
AI博主写作素材管理方法分享:资料归集、笔记关联与文档归档
人工智能·笔记