Java那些“锁”事 - 公平锁和非公平锁

在Java中,锁可以分为公平锁(Fair Lock)和非公平锁(Nonfair Lock),它们的区别在于线程获取锁的顺序是否遵循公平性原则。

Java那些“锁”事 - 公平锁和非公平锁_第1张图片

 

公平锁

        公平锁是指多个线程按照它们发出请求的顺序获取锁,即先到先得的原则。当一个线程释放锁时,等待时间最长的线程将有更大的机会获取到锁。公平锁的优点是保证了资源的公平分配,并且避免饥饿现象。但是,由于需要维护一个等待队列,因此公平锁的性能通常相对较低。

非公平锁

        非公平锁是指多个线程获取锁的顺序没有明确的规定,线程获取锁的机会是随机分配的。即使有新的线程等待获取锁时,当前持有锁的线程有可能再次获取到锁。非公平锁的优点是相对较高的吞吐量,因为它省去了维护等待队列的开销。但是,非公平锁可能会导致一些线程长时间地等待,从而产生“插队”现象,不遵循公平性原则。

ReentrantLock非公平案例

        有一个资源类(Ticket ),包含一个number变量表示50张票,还有一个lock变量保证“票”资源被售票员有序的售出。

/**
 * 资源类
 */
class Ticket {

    private Integer number = 30;
    private ReentrantLock lock = new ReentrantLock();

    public void sale() {
        lock.lock();
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出第 " + (number--) + " 张票,还剩下:" + number);
            }
        } finally {
            lock.unlock();
        }
    }
}

        三个售票员卖完30张票:

 public static void main(String[] args) {

        Ticket ticket = new Ticket();

        //模拟三个售票员卖完50张票
        new Thread(() -> {
            for (int i = 0; i < 31; i++) {
                ticket.sale();
            }
        }, "售票员A").start();

        new Thread(() -> {
            for (int i = 0; i < 31; i++) {
                ticket.sale();
            }
        }, "售票员B").start();

        new Thread(() -> {
            for (int i = 0; i < 31; i++) {
                ticket.sale();
            }
        }, "售票员C").start();
    }

        结果打印:

售票员A卖出第 30 张票,还剩下:29
售票员A卖出第 29 张票,还剩下:28
售票员A卖出第 28 张票,还剩下:27
售票员A卖出第 27 张票,还剩下:26
售票员A卖出第 26 张票,还剩下:25
售票员A卖出第 25 张票,还剩下:24
售票员A卖出第 24 张票,还剩下:23
售票员C卖出第 23 张票,还剩下:22
售票员C卖出第 22 张票,还剩下:21
售票员C卖出第 21 张票,还剩下:20
售票员C卖出第 20 张票,还剩下:19
售票员C卖出第 19 张票,还剩下:18
售票员C卖出第 18 张票,还剩下:17
售票员C卖出第 17 张票,还剩下:16
售票员C卖出第 16 张票,还剩下:15
售票员C卖出第 15 张票,还剩下:14
售票员C卖出第 14 张票,还剩下:13
售票员C卖出第 13 张票,还剩下:12
售票员C卖出第 12 张票,还剩下:11
售票员C卖出第 11 张票,还剩下:10
售票员C卖出第 10 张票,还剩下:9
售票员C卖出第 9 张票,还剩下:8
售票员C卖出第 8 张票,还剩下:7
售票员C卖出第 7 张票,还剩下:6
售票员C卖出第 6 张票,还剩下:5
售票员C卖出第 5 张票,还剩下:4
售票员C卖出第 4 张票,还剩下:3
售票员C卖出第 3 张票,还剩下:2
售票员C卖出第 2 张票,还剩下:1
售票员C卖出第 1 张票,还剩下:0

        可以看到前面7张票是售票员A卖出去的,但是后面的27张票都是售票员C卖出去的。原因是我们在资源类中的ReentrantLock使用其默认的构造方法new出来的锁对象,ReentrantLock默认是非公平锁:

     //ReentrantLock构造方法创建的是非公平锁
     public ReentrantLock() {
        sync = new NonfairSync();
    }

ReentrantLock公平案例

       我们把资源类(Ticket)中的lock对象设置成公平锁。

/**
 * 资源类
 */
class Ticket {

    private Integer number = 30;
    private ReentrantLock lock = new ReentrantLock(true);

    public void sale() {
        lock.lock();
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出第 " + (number--) + " 张票,还剩下:" + number);
            }
        } finally {
            lock.unlock();
        }
    }
}

        三个售票员卖完30张票:

   public static void main(String[] args) {

        Ticket ticket = new Ticket();

        //模拟三个售票员卖完50张票
        new Thread(() -> {
            for (int i = 0; i < 31; i++) {
                ticket.sale();
            }
        }, "售票员A").start();

        new Thread(() -> {
            for (int i = 0; i < 31; i++) {
                ticket.sale();
            }
        }, "售票员B").start();

        new Thread(() -> {
            for (int i = 0; i < 31; i++) {
                ticket.sale();
            }
        }, "售票员C").start();
    }

        结果打印:

售票员A卖出第 30 张票,还剩下:29
售票员A卖出第 29 张票,还剩下:28
售票员A卖出第 28 张票,还剩下:27
售票员B卖出第 27 张票,还剩下:26
售票员A卖出第 26 张票,还剩下:25
售票员C卖出第 25 张票,还剩下:24
售票员B卖出第 24 张票,还剩下:23
售票员A卖出第 23 张票,还剩下:22
售票员C卖出第 22 张票,还剩下:21
售票员B卖出第 21 张票,还剩下:20
售票员A卖出第 20 张票,还剩下:19
售票员C卖出第 19 张票,还剩下:18
售票员B卖出第 18 张票,还剩下:17
售票员A卖出第 17 张票,还剩下:16
售票员C卖出第 16 张票,还剩下:15
售票员B卖出第 15 张票,还剩下:14
售票员A卖出第 14 张票,还剩下:13
售票员C卖出第 13 张票,还剩下:12
售票员B卖出第 12 张票,还剩下:11
售票员A卖出第 11 张票,还剩下:10
售票员C卖出第 10 张票,还剩下:9
售票员B卖出第 9 张票,还剩下:8
售票员A卖出第 8 张票,还剩下:7
售票员C卖出第 7 张票,还剩下:6
售票员B卖出第 6 张票,还剩下:5
售票员A卖出第 5 张票,还剩下:4
售票员C卖出第 4 张票,还剩下:3
售票员B卖出第 3 张票,还剩下:2
售票员A卖出第 2 张票,还剩下:1
售票员C卖出第 1 张票,还剩下:0

        可以看到30张票的出售情况,后面都是售票员A、售票员B、售票C个出售一张的情况。我们创建ReentrantLock实例通过指定构造方法中传入true创建了一个公平锁对象。

    //通过传入参数(false/ture)可以指定是公平锁还是非公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

选择使用公平锁还是非公平锁取决于具体的场景和需求。如果对线程之间的公平性要求比较高,或者需要避免饥饿现象,可以选择公平锁。如果对吞吐量更关注,并且能够容忍某些线程“插队”,可以选择非公平锁。

 

你可能感兴趣的:(Java并发编程,公平锁,非公平锁)