3 Lock API控制多线程
⭐⭐⭐⭐⭐⭐
Github主页👉https://github.com/A-BigTree
笔记仓库👉https://github.com/A-BigTree/tree-learning-notes
个人主页👉https://www.abigtree.top
⭐⭐⭐⭐⭐⭐
如果可以,麻烦各位看官顺手点个star~😊
如果文章对你有所帮助,可以点赞👍收藏⭐支持一下博主~😆
3.1 HelloWorld
3.1.1 买票
public class Demo01HelloWorld {
// 声明成员变量维护票库存
private int stock = 100;
// 创建锁对象
// 变量类型:java.util.concurrent.locks.Lock 接口
// 对象类型:Lock 接口的最常用的实现类 ReentrantLock
private Lock lock = new ReentrantLock();
// 声明卖票的方法
public void saleTicket() {
try {
// 加锁
lock.lock(); // synchronized (this) {
if (stock > 0) {
// 卖票的核心操作
System.out.println(Thread.currentThread().getName() + " 卖了一张,还剩 " + --stock + " 张票。");
} else {
System.out.println(Thread.currentThread().getName() + " 卖完了。");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock(); // }
}
}
public static void main(String[] args) {
// 1、创建当前类对象
Demo01HelloWorld demo = new Demo01HelloWorld();
// 2、开启三个线程调用卖票方法
new Thread(()->{
for (int i = 0; i < 40; i++) {
demo.saleTicket();
try {
TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {
}
}
}, "thread-01").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
demo.saleTicket();
try {
TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {
}
}
}, "thread-02").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
demo.saleTicket();
try {
TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {
}
}
}, "thread-03").start();
}
}
3.1.2 需要注意的点
确保锁被释放
使用 Lock API 实现同步操作,是一种面向对象的编码风格。这种风格有很大的灵活性,同时可以在常规操作的基础上附加更强大的功能。但是也要求编写代码更加谨慎:如果忘记调用 lock.unlock()
方法则锁不会被释放,从而造成程序运行出错。
加锁和解锁操作对称执行
不管同步操作是一层还是多层,有多少个加锁操作,就应该相应的有多少个解锁操作。
避免锁对象的线程私有化
锁对象如果是线程内部自己创建的,是自己独占的,其它线程访问不到这个对象,那么这个锁将无法实现**『排他』**效果,说白了就是:锁不住。
3.2 Lock接口
全类名:java.util.concurrent.locks.Lock
方法功能说明:
方法名 | 功能 |
---|---|
void lock() | 加同步锁,如果没有得到锁会一直等 |
void unlock() | 解除同步锁 |
boolean tryLock() | 尝试获取锁。如果没有获取到则立即返回,不做任何等待 返回 true:表示获取成功 返回 false:表示获取失败 |
boolean tryLock(long time, TimeUnit unit) | 尝试获取锁,且等待指定时间 返回 true:表示获取成功 返回 false:表示获取失败 |
void lockInterruptibly() | 以『支持响应中断』的模式获取锁 |
Condition newCondition() | 获取用于线程间通信的 Condition 对象 |
3.3 可重入锁
全类名:java.util.concurrent.locks.ReentrantLock
这是 Lock 接口最典型、最常用的一个实现类。
3.3.1 基本用法
基本要求1:将解锁操作放在 finally
块中,确保解锁操作能够被执行到。
基本要求2:加锁和解锁操作要对称。
try {
// 加锁
lock.lock();
// 同步代码部分
} catch(Exception e) {
// ...
} finally {
// 解锁
lock.unlock();
}
3.3.2 验证可重入性
// 测试目标:验证可重入性
// 测试方式:在同一个线程内,嵌套使用 try ... catch ... finally 结构
// 由于可重入性的大前提就是已经加了一个锁,然后再加一个锁,所以不可能有多个线程,就在 main 线程里测试即可
// 测试标准:线程不会被自己锁住,不会陷入死锁就证明当前使用的 API 支持可重入
// 创建锁对象
Lock lock = new ReentrantLock();
try {
// 外层加锁操作
lock.lock();
System.out.println(Thread.currentThread().getName() + " 外层加锁成功。");
try {
// 内层加锁操作
lock.lock();
System.out.println(Thread.currentThread().getName() + " 内层加锁成功。");
} finally {
// 内层解锁操作
lock.unlock();
System.out.println(Thread.currentThread().getName() + " 内层解锁成功。");
}
} finally {
// 外层解锁操作
lock.unlock();
System.out.println(Thread.currentThread().getName() + " 外层解锁成功。");
}
3.3.3 tryLock()
public class Demo03TryLock {
private Lock lock = new ReentrantLock();
public void showMessage() {
boolean lockResult = false;
try {
// 尝试获取锁
// 返回true:获取成功
// 返回false:获取失败
lockResult = lock.tryLock();
if (lockResult) {
try {
TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {
}
System.out