JDK 5.0
开始,Java
提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock
对象充当。java.util.concurrent.locks.Lock
接口 是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock
对象加锁,线程开始访问共享资源之前应先获得Lock
对象。ReentrantLock
类 是实现了 Lock
接口的一个实现类 ,它拥有与 synchronized
相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock
类,可以显式加锁、释放锁。- 在 Java 5.0 之前,协调共享对象的访问时可以使用的机制只有 synchronized 和 volatile 。Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内置锁不适用时,作为一种可选择的高级功能。
- ReentrantLock 实现了 Lock 接口,并提供了与 synchronized 相同的互斥性和内存可见性。但相较于 synchronized 提供了更高的处理锁的灵活性。
import java.util.concurrent.locks.ReentrantLock;
class A{
private final ReentrantLock lock = new ReentrantLock(); // 创建锁
public void m(){
lock.lock();
try{
//保证线程安全的代码; }
finally{
lock.unlock();
}
}
}
// 注意:如果同步代码有异常,要将unlock()写入finally语句块
下面再写一个将前面账号取款的线程安全问题的示例。
//创建线程类: 售卖车票
class Ticket implements Runnable {
//成员属性
private int tick = 100;
//重写run
@Override
public void run() {
//循环卖票,直到票卖完
while (true) {
//存在有票,继续售卖
if (tick > 0) {
//休眠500毫秒
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票
tick--;
//打印当前的票数
System.out.println(Thread.currentThread().getName() + " 剩余票:" + tick);
} else {
break;
}
}
}
}
public class TestLock {
public static void main(String[] args) {
//创建线程类对象
Ticket ticket = new Ticket();
//创建三个线程
new Thread(ticket, "1号窗口").start();
new Thread(ticket, "2号窗口").start();
new Thread(ticket, "3号窗口").start();
}
}
测试执行如下:
image-20201102234826929
image-20201102235725467
//创建线程类: 售卖车票
class Ticket implements Runnable {
//成员属性
private int tick = 100;
//创建 lock 锁
private final ReentrantLock lock = new ReentrantLock();
//重写run
@Override
public void run() {
//循环卖票,直到票卖完
while (true) {
lock.lock(); // 上锁
//存在有票,继续售卖
try {
if (tick > 0) {
//休眠500毫秒
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票
tick--;
//打印当前的票数
System.out.println(Thread.currentThread().getName() + " 剩余票:" + tick);
}
} finally {
lock.unlock(); // 在finally释放锁
}
}
}
}
image-20201102235505984
可以看到已经逐个执行了,没有出现重复值。