一、实现描述
synchronized 是 Java 中的关键字,它可以用来保证一段代码的原子性,synchronized 是一种比较简单的线程同步方式。在Java中,每个对象都有一个monitor监视器,当一个线程获取了一个对象的monitor时,其他线程就不能再获取该对象的monitor,直到第一个线程释放该对象的monitor。因此,使用synchronized关键字可以保证同一时间只有一个线程执行该对象中的synchronized代码块。
ReentrantLock 是一个可重入的互斥锁,它与 synchronized 有着类似的功能,但是它提供了更多的灵活性和更高的性能。ReentrantLock 提供了一些高级功能,如公平锁和可中断锁等。
ReentrantLock锁的实现步骤总结为三点:
1、未竞争到锁的线程将会被CAS视为一个链表结构并且被挂起。
2、竞争到锁的线程执行完后释放锁并且将唤醒链表中的下一个节点。
3、被唤醒的节点将从被挂起的地方继续执行逻辑。
二、代码案例
synchronized 可用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用在代码块上。
synchronized 会自动加锁和释放锁,当进入 synchronized 修饰的代码块之后会自动加锁,当离开 synchronized 的代码段之后会自动释放锁。synchronized基础使用:
public class Demo1 {
public static void main(String[] args) {
//公共票池,默认数量为20张
TicketPool ticketPool = new TicketPool(20);
//创建三个线程,用于模拟三个不同的售票窗口,共同卖出公共票池中的20张门票
Thread thread1 = new Thread(ticketPool,"窗口1");
Thread thread2 = new Thread(ticketPool,"窗口2");
Thread thread3 = new Thread(ticketPool,"窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
//票池类(门票数+售票任务)
class TicketPool implements Runnable {
private int ticketNum;
public TicketPool(int ticketNum){
this.ticketNum = ticketNum;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "准备好了,可以开始售票!");
//线程竞争CPU执行权(this锁)
synchronized (this){
while (true){
if (ticketNum <= 0){
System.out.println(Thread.currentThread().getName() + ":不好意思,票卖完了");
return;
}else {
System.out.println(Thread.currentThread().getName() + "卖出了1张门票,还剩" + --ticketNum + "张门票");
try {
//当前线程休眠1000毫秒
//注意:休眠过程中,当前线程不会让出”this锁“,此处为引发错误的原因
// Thread.sleep(1000);
//持有当前this锁的对象进入等待,等待时间1000毫秒
this.wait(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
而 ReentrantLock 需要手动加锁和释放锁,ReentrantLock的基础使用:
//同步
public class Demo5 {
public static void main(String[] args) {
Counter1 counter1 = new Counter1();
Thread thread1 = new Thread(){
@Override
public void run() {
counter1.add();
}
};
Thread thread2 = new Thread(){
@Override
public void run() {
counter1.dec();
}
};
System.out.println(Counter1.count);
}
}
//计数器类
class Counter1{
//用于计数的公共变量
public static int count = 0;
//创建锁
private final ReentrantLock lock = new ReentrantLock();
//递增
public void add(){
for (int i = 0;i <= 10000;i++){
//加锁
lock.lock();
try{
count += i;
}finally {
//释放锁
lock.unlock();
}
}
}
//递减
public void dec(){
for (int i = 0;i <= 10000;i++){
//加锁
lock.lock();
try{
count -= i;
}finally {
//释放锁
lock.unlock();
}
}
}
}
三、区别
ReentrantLock | Synchroniaed | |
---|---|---|
锁实现机制 | AQS | 监视器Monitor |
获取锁 | 可以通过tryLock()尝试获取锁,更加灵活 | 线程抢占模型 |
释放锁 | 必须手动通过unlock()释放锁 | 自动释放 |
锁类型 | 支持公平锁和非公平锁 | 非公平锁 |
可重入性 | 可重入 | 可重入 |
响应中断 | 可响应中断 | 不可响应中断(可能会死锁) |
ReentrantLock构造函数:默认是采用的非公平策略获取锁。
ReentrantLock总共有三个内部类:Sync、NonfairSync、FairSync。
NonfairSync类,表示采用非公平策略获取锁:每一次都尝试获取锁,不会按照公平等待的原则进行等待,不会让等待时间最久的线程获得锁。
FairSync类,表示采用公平策略获取锁:当资源空闲时,它总是会先判断 sync队列是否有等待时间更长的线程,如果存在,则将当前线程加入到等待队列的尾部,实现了公平获取原则。