简单了解 重入锁 (ReentrantLock)的使用以及特点

synchronized的功能扩展:重入锁 (ReentrantLock)

重入锁使用java.util.concurrent.locks.ReentrantLock类来实现,使用案例如下所示:

package test;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author zry
 * @Date 2022/6/13
 * @ApiNote
 */
public class ReenterLock implements Runnable{
//  创建重入锁实例对象
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
//            使用重入锁保护临界区资源i,确保多线程对i操作的安全性
             lock.lock();
            try {
                i++;
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReenterLock tl = new ReenterLock();
        Thread t1 = new Thread(tl);
        Thread t2 = new Thread(tl);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
     System.out.println(i);
    }
}
  • 重入锁与synchronized相比,有着明显的操作过程,开发必须手动指定何时加锁,注意,退出临界区的时候,必须释放锁,否则其他线程就没有机会访问临界区了。

      重入锁是可以**反复进入**的,**仅限一个线程**!
    
  • 重入锁提供了无条件的、可轮询的、定时的以及可中断的锁获取操作

             lock.lock();
             lock.lock();
            try {
                i++;
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
                lock.unlock();
            }

在这种情况下,一个线程连续获得同一把锁,这是被允许的,否则同一个线程就会在第二次获得锁的时候与自己产生死锁。

注意:如果同一个线程多次获得锁,那么在释放锁的时候也必须释放相同的次数!

如果释放的多了,就会得到一个java.lang.IllegalMonitorStateException异常;反之,则相当于线程还持有这个锁,因此其他线程无法进入临界区。

中断响应

对于synchronized来说,如果一个线程在等待锁,则结果只有两种:

  1. 获得这个锁继续执行

  2. 保持等待

而是用重入锁,则提供了另一种可能:线程可以被中断

也就是在等待所的过程中,程序可以根据需要取消对锁的请求。这种情况对处理死锁是有帮助的。

示例如下:

lock1.lockInterruptibly();

使用上述代码申请锁,可以进行中断,即这是一个可以对中断进行相应的锁申请动作。

锁申请等待限时

我们可以使用tryLock()方法进行一次限时的等待

tryLock(等待时间m,计时单位t):表示线程在这个锁请求中最多等待m,如果超过m还没有得到锁,就会返回false,反之会返回true

tryLock():如果锁未被其他线程占用,则申请锁会成功,直接返回true;否则会直接返回false。

示例代码如下:

package test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author zry
 * @Date 2022/6/14
 * @ApiNote
 */
public class TimeLock implements  Runnable{
    public static ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
//            tryLock(等待时常,计时单位)
            if (lock.tryLock(5, TimeUnit.SECONDS)){
                Thread.sleep(6000);
            }else {
                System.out.println("get lock failed");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TimeLock tl = new TimeLock();
        Thread t1 = new Thread(tl);
        Thread t2 = new Thread(tl);
        t1.start();
        t2.start();
    }
}

上述代码中,由于线程获得锁之后会休眠6s,即占用该锁6s,导致其他线程无法在规定时间内获得锁,因此,请求锁会失败。

公平锁

大多是情况下,锁申请并不是公平的,而是看运气,系统会在这个锁的等待队列中随机挑选一个。因此不能保证公平性。

如果我们使用synchronized进行锁控制,那么产生的锁就是非公平的,而重入锁允许我们对其公平性进行设置。其构造函数如下:

public ReentrantLock(boolean fair)

当参数fair为true时,表示锁是公平的。

实现公平锁必须要求系统维护一个有序队列,因此公平锁的成本较高、性能较低

公平锁一大特点:不会产生饥饿现象

公平锁示例代码如下:

package test;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author zry
 * @Date 2022/6/14
 * @ApiNote
 */
public class FairLock implements  Runnable{
//    指定锁是公平的
    public static ReentrantLock fairLock = new ReentrantLock(true);

    @Override
    public void run() {
        while (true){
            try {
                fairLock.lock();
                System.out.println(Thread.currentThread().getName()+"获得锁");
            }catch (Exception e){
             e.printStackTrace();
            }finally {
                fairLock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        FairLock rl = new FairLock();
        Thread t1 = new Thread(rl,"Thread_t1");
        Thread t2 = new Thread(rl,"Thread_t2");
        t1.start();
        t2.start();
    }
}

上述代码运行结果如下图:

可以看到都是交替获得锁的,几乎不会遇到一个线程多次获得锁的情况,这就是公平锁。

而不使用公平锁的话,则会由一个线程呢个多次获得一个锁。这种分配方式高效但是无公平性可言。

ReentrantLock的几个重要方法如下

  • lock():获得锁,如果锁已经被占用,则等待

  • lockInterruptibly():获得锁,但优先响应中断

  • tryLock():尝试获得锁,如果成功返回true,失败返回false,该方法不等待,立即返回!

  • tryLock(long time,TimeUnit.unit):在给定时间内尝试获得锁

  • unlock():释放锁

在重入锁的实现中,主要包含三个要素

  1. 原子状态,原子状态使用CAS操作来存储当前锁的状态,判断锁是否已经被别的线程持有。

  2. 等待队列,所有没有请求到锁的线程,会进入等待队列进行等待。待有线程释放锁后,系统就能从等待队列中唤醒一个线程,继续工作。

  3. 阻塞原语park()和unpark(),用来挂起和恢复线程。没有得到锁的线程会被挂起。

参考:《实战Java高并发程序设计》 葛一鸣 郭超 编著

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值