Java进阶篇--公平锁 & 非公平锁

目录

ReentrantLock的介绍

重入性的实现原理

代码示例:

公平锁与非公平锁

代码示例:


ReentrantLock的介绍

ReentrantLock是Java中实现Lock接口的一种重入锁(Reentrant Lock)实现类。它提供了与synchronized关键字相似的功能(在java关键字synchronized隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入),但比synchronized更加灵活和强大。ReentrantLock可以用于实现多线程之间的互斥访问和同步操作。

下面是ReentrantLock主要的特点和用法介绍:

1、可重入性:
ReentrantLock允许同一个线程多次获取同一把锁,而不会发生死锁。这种特性叫做可重入性,也就是可重入锁。当一个线程已经持有锁时,再次获取锁时会增加锁的计数器,每次释放锁时计数器减一,只有当计数器为0时锁才会完全释放。

2、获取锁的方式:
通过调用ReentrantLock的lock()方法可以获取锁,如果锁已被其他线程获取,则当前线程会被阻塞,直到获取到锁为止。另外,ReentrantLock还提供了tryLock()方法,该方法尝试立即获取锁,如果锁未被其他线程获取,则返回true,否则返回false。tryLock()方法也可以设置超时时间,指定在一段时间内尝试获取锁。

3、线程阻塞与唤醒:
与synchronized不同,ReentrantLock提供了显示的线程挂起和恢复的能力。通过调用lock()方法后,如果锁已被其他线程占用,则当前线程会进入休眠状态,直到获取锁成功。当不再需要锁时,需要调用unlock()方法显式地释放锁,以唤醒其他等待线程。

4、锁的公平性:
ReentrantLock支持公平锁和非公平锁。可以在创建ReentrantLock对象时指定构造函数参数来设置锁的公平性。默认情况下,ReentrantLock是非公平锁。

5、条件变量:
与ReentrantLock配套使用的还有条件变量(Condition),可以通过ReentrantLock的newCondition()方法创建一个特定的条件变量。条件变量可以使线程在某个条件满足时等待,或者唤醒等待在该条件上的线程。

总结:
ReentrantLock是Java提供的一种可重入的高级锁实现,相比synchronized关键字更加灵活,并提供了更多功能,如可控的锁公平性、显示的线程挂起和恢复、超时锁等等。在多线程编程中,使用ReentrantLock可以更好地控制资源的访问和线程的同步。。

那么,要想完完全全的弄懂ReentrantLock的话,主要也就是ReentrantLock同步语义的学习:1. 重入性的实现原理;2. 公平锁和非公平锁。

重入性的实现原理

ReentrantLock支持重入性的实现是通过在锁对象上维护一个计数器来实现的。

在Java中,每个线程都拥有自己的线程栈(Thread Stack)。如果一个方法被调用,那么就会在线程栈上分配一段空间,这段空间被称为栈帧(Stack Frame)。当一个方法被另一个方法调用时,就会在栈上分配另一个栈帧,这就形成了方法调用链。

在ReentrantLock中,每个线程都有一个变量叫做"holdCount",表示该线程目前获取锁的次数。当一个线程第一次获取锁时,holdCount的值为1;如果再次获取锁,则holdCount递增,以此类推。当这个线程释放锁时,holdCount递减。只有当holdCount的值为0时,锁才真正释放。

也就是说,ReentrantLock对于每个线程来说,维护了一个独立的计数器。当一个线程试图获取锁时,首先判断当前线程是否已经获取了该锁,如果已经获取,则直接返回true;如果未获取,则需要尝试获取锁。当该线程成功获取锁时,holdCount递增,表示当前线程又多获取了一次该锁。当该线程释放锁时,holdCount递减,表示当前线程又释放了一次该锁。只有当holdCount的值为0时,锁才会真正释放。

总结一下:在ReentrantLock中,通过为每个线程维护一个计数器(即holdCount)来实现重入性。当一个线程多次获取锁时,计数器递增;每次释放锁时,计数器相应地递减。只有当计数器为0时,锁才完全释放,其他线程才能获取该锁。这种机制可以避免死锁和线程饥饿等问题,是一种非常有效的并发控制手段。

代码示例:

import java.util.concurrent.locks.ReentrantLock;

public class Main {
    private ReentrantLock lock = new ReentrantLock();
    private double balance;

    public Main(double initialBalance) {
        balance = initialBalance;
    }

    // 存款
    public void deposit(double amount) {
        lock.lock(); // 获取锁
        try {
            balance += amount; // 更新余额
            System.out.println("存款成功,当前余额为:" + balance);
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    // 取款
    public void withdraw(double amount) {
        lock.lock(); // 获取锁
        try {
            if (balance >= amount) {
                balance -= amount; // 更新余额
                System.out.println("取款成功,当前余额为:" + balance);
            } else {
                System.out.println("余额不足");
            }
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public static void main(String[] args) {
        Main account = new Main(1000.0);

        // 创建两个线程模拟两个用户进行存款和取款操作
        Thread depositThread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                account.deposit(100.0);
            }
        });

        Thread withdrawThread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                account.withdraw(200.0);
            }
        });

        depositThread.start();
        withdrawThread.start();
    }
}

在这个例子中,我们模拟了一个银行账户,通过使用ReentrantLock实现对余额的线程安全操作。在存款和取款方法中,我们都先获取锁,执行相应的操作,然后释放锁。

在主函数中,我们创建了两个线程,一个用于模拟存款操作,另一个用于模拟取款操作。每个线程都执行5次操作。通过使用ReentrantLock,我们确保了每次存款或取款操作是原子的,不会发生并发访问的问题。同时,由于ReentrantLock支持重入性,所以同一个线程可以多次获取锁,这也是允许的。

公平锁与非公平锁

公平锁和非公平锁是ReentrantLock(可重入锁)中的两种获取锁的策略。

  • 公平锁在多线程竞争下,按照线程的请求顺序来获取锁,保证了请求资源时间上的绝对顺序。当一个线程释放锁后,同步队列中的第一个等待线程会获取到锁,其他线程按照先来后到的顺序依次获取锁。公平锁可以避免线程的饥饿现象,但可能因为频繁的上下文切换而降低系统的吞吐量。
  • 非公平锁在多线程竞争下,不保证线程获取锁的顺序。当一个线程释放锁后,新的线程有机会直接获取到锁,即使其他线程在等待获取锁。非公平锁的实现较为简单高效,可以减少上下文切换的次数,提高系统的吞吐量。但是,某些线程可能会长时间等待,造成其他线程一直获取锁的情况。

ReentrantLock默认是非公平锁,因为在大多数情况下,非公平锁性能更好。如果需要创建公平锁,可以通过构造函数参数传入true,创建一个公平锁的实例:

ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁
//注意:使用ReentrantLock类创建非公平锁时,不需要传入参数或者传入false

需要注意的是,公平锁会增加一定的上下文切换次数,适用于对线程获取锁的顺序有严格要求的场景,而非公平锁适用于追求更高吞吐量的场景。

总结:

  • 公平锁按照线程申请锁的顺序获取锁,避免线程饥饿,但可能会引起较大的开销。
  • 非公平锁没有严格的获取顺序,可能会导致后申请的线程先获取锁,但能够获得更高的吞吐量。

在实际应用中,选择公平锁还是非公平锁取决于具体的场景和需求。一般来说,在线程竞争激烈的情况下,非公平锁可能更适合,可以提升系统性能。

代码示例:

import java.util.concurrent.locks.ReentrantLock;

public class Main {
    private static ReentrantLock fairLock = new ReentrantLock(true);  // 创建公平锁
    private static ReentrantLock nonFairLock = new ReentrantLock(false);  // 创建非公平锁

    public static void main(String[] args) {
        // 公平锁示例
        Thread fairThread1 = new Thread(() -> {
            fairLock.lock();  // 获取公平锁
            try {
                System.out.println("公平锁 - 线程1获取到锁");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                fairLock.unlock();  // 释放公平锁
                System.out.println("公平锁 - 线程1 释放公平锁");
            }
        });

        Thread fairThread2 = new Thread(() -> {
            fairLock.lock();  // 获取公平锁
            try {
                System.out.println("公平锁 - 线程2获取到锁");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                fairLock.unlock();  // 释放公平锁
                System.out.println("公平锁 - 线程2 释放公平锁");
            }
        });

        // 非公平锁示例
        Thread nonFairThread1 = new Thread(() -> {
            nonFairLock.lock();  // 获取非公平锁
            try {
                System.out.println("非公平锁 - 线程1获取到锁");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                nonFairLock.unlock();  // 释放非公平锁
                System.out.println("非公平锁 - 线程1 释放非公平锁");
            }
        });

        Thread nonFairThread2 = new Thread(() -> {
            nonFairLock.lock();  // 获取非公平锁
            try {
                System.out.println("非公平锁 - 线程2获取到锁");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                nonFairLock.unlock();  // 释放非公平锁
                System.out.println("非公平锁 - 线程2 释放非公平锁");
            }
        });

        fairThread1.start();
        fairThread2.start();
        nonFairThread1.start();
        nonFairThread2.start();
    }
}

在上述示例中,我们创建了一个公平锁(fairLock)和一个非公平锁(nonFairLock),然后创建了两个线程来演示这两种锁的行为。

  • 对于公平锁,我们创建了两个线程(fairThread1和fairThread2),它们按照先来后到的顺序获取锁。由于是公平锁,fairThread1首先获取到锁,然后才轮到fairThread2获取锁。
  • 对于非公平锁,我们创建了两个线程(nonFairThread1和nonFairThread2),它们竞争获取锁。由于是非公平锁,在某些情况下,刚释放锁的线程有机会再次获取锁,因此可能会导致其他线程长时间等待。

通过运行上述代码,我们可以观察到公平锁和非公平锁的不同行为。请注意,输出结果可能因操作系统调度而有所差异,但公平锁会按照线程的请求顺序获取锁,而非公平锁则不保证顺序。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Redisson的分布式(`RLock`)默认是公平。它使用Redis的`SETNX`命令来实现的获取,不会按照请求的先后顺序进行排序。任何线程都有机会在任意时间点获取到,无论是否是在先前请求的线程之前。这种公平性可以提高并发性能,但不能保证公平性。 如果你需要使用公平,Redisson也提供了相应的实现。你可以使用`getFairLock`方法来获取一个公平实例。公平会按照请求的先后顺序进行排序,先到先得。 下面是使用Redisson实现公平的示例代码: ```java import org.redisson.Redisson; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.redisson.config.Config; public class FairLockExample { public static void main(String[] args) { // 创建Redisson客户端连接 Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); // 获取公平 RLock fairLock = redisson.getFairLock("fairLock"); try { // 尝试加并执行业务逻辑 fairLock.lock(); // 执行业务逻辑 System.out.println("Executing business logic..."); } finally { // 释放 fairLock.unlock(); } // 关闭Redisson客户端连接 redisson.shutdown(); } } ``` 在这个示例中,我们使用`redisson.getFairLock("fairLock")`获取一个名为`fairLock`的公平实例。然后,我们使用`lock()`方法来获取,并在业务逻辑执行完毕后使用`unlock()`方法释放。 请注意,公平相对于公平可能会带来额外的性能开销,因为它需要维护请求的先后顺序。因此,在使用公平时应权衡其对性能的影响。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

正在奋斗的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值