线程安全问题生活举例以及解决方案(ReentrantLock)
1. 前言
2. 线程安全问题举例以及解决方案
- 等待
2.1 存在线程安全的代码
- 为了方便,我这里把“T”给去掉了,直接用数字,那也不是我们演示的重点,直接看代码吧:
package com.liu.susu.thread.lock.example3;
public class OrderNumberRun implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
if (number <= 100) {
System.out.println(Thread.currentThread().getName() + "-田老师家,取单号为-->" + number);
number++;
}else {
break;
}
}
}
}
class clientTest3 {
public static void main(String[] args) {
OrderNumberRun orderNumberRun = new OrderNumberRun();
Thread thread1 = new Thread(orderNumberRun);
Thread thread2 = new Thread(orderNumberRun);
Thread thread3 = new Thread(orderNumberRun);
thread1.setName("线程1");
thread2.setName("线程2");
thread3.setName("线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
- 效果展示:
明显不安全,上篇也有介绍,不多说了,重点往下看处理方案……2.2 用 ReentrantLock 解决线程安全
- 修改代码部分如图:
- 完整代码如下:
package com.liu.susu.thread.lock.example4;
import java.util.concurrent.locks.ReentrantLock;
public class OrderNumberRun implements Runnable {
private int number = 1;
private final ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
lock.lock();
if (number <= 100) {
System.out.println(Thread.currentThread().getName() + "-田老师家,取单号为-->" + number);
number++;
}else {
break;
}
} finally {
lock.unlock();
}
}
}
}
class clientTest4 {
public static void main(String[] args) {
OrderNumberRun orderNumberRun = new OrderNumberRun();
Thread thread1 = new Thread(orderNumberRun);
Thread thread2 = new Thread(orderNumberRun);
Thread thread3 = new Thread(orderNumberRun);
thread1.setName("线程1");
thread2.setName("线程2");
thread3.setName("线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
- 运行效果如下:
3. synchronized 与 ReentrantLock 区别
3.1 解释说明
3.1.1 相同点
- synchronized 与 ReentrantLock 都可以解决线程安全问题是肯定的
- ReentrantLock 表示可重入锁,与synchronized一样,都是属于可重入锁;
可重入锁:同一个线程 如果首次获取到该锁资源,则它就有权力再次获取到该锁,这就是锁的重入! - 都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善)。
3.1.1 不同点
(1)实现上区别
- 使用 lock 初始化可选择公平锁、非公平锁
- lock 锁只有代码块锁,synchronized 有代码块儿锁和方法锁
- synchronized 是隐式锁,自动释放同步监视器(自动释放锁)
lock是显示锁,需要手动启动同步(lock()方法加锁),结束同步时需要手动解锁(调用unclock()方法) - 对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要 JVM 实现。而 ReentrantLock 是JDK5.0 之后提供的API层面的互斥锁,需要lock()和unlock()方法配合 try/finally 语句块来完成.
- Synchronized的使用更方便,并且由编译器去保证锁的加锁和释放,而 ReenTrantLock 需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在 finally 中声明释放锁。
(2)性能上区别
- 使用 lock 锁,JVM 将花费较少的时间来调度线程,性能更好,还具有很好的扩展性(提供更多的子类)
3.2 表格对比
对比 | synchronized | ReentrantLock |
---|
出现 | 一直有 | JDK5.0新增 | 可重入性 | 可重入 | 可重入 | 锁类型 | 公平锁 / 非公平锁 | 非公平锁 | 释放形式 | 必须显示调用unclock()释放锁 | 自动释放监视器 | 灵活性 | 不够灵活 | | 锁实现机制 | 依赖AQS | 监视器模式 |
|