ReentrantLock详解(可重入、可中断、锁超时、公平锁、条件变量)、同步模式之顺序控制

本文详细介绍了Java并发编程中的ReentrantLock,包括其可重入、可中断、超时获取、公平锁特性,以及如何通过Condition实现线程间的同步控制。通过示例代码展示了ReentrantLock在解决哲学家就餐问题和顺序控制中的应用。

1.ReentrantLock详解

相对于synchronized 它具备如下特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量
  • 与 synchronized 一样,都支持可重入

基本语法

// 获取ReentrantLock对象
private ReentrantLock lock = new ReentrantLock();
// 加锁 获取不到锁一直等待直到获取锁
lock.lock();
try {
   
   
	// 临界区
	// 需要执行的代码
}finally {
   
   
	// 释放锁 如果不释放其他线程就获取不到锁
	lock.unlock();
}
    /**
     * 获取锁。
     * 如果锁不可用,则当前线程将因线程调度而被禁用,并处于休眠状态,直到获得锁为止。
     */
    void lock();
    
 	/**
    当前线程被中断,获取锁
    获取锁(如果可用)并立即返回。
    如果锁不可用,则当前线程将因线程调度而被禁用,并处于休眠状态,直到发生以下两种情况之一:
    1. 锁被当前线程获取;或者
    (获取锁正常返回)
	2.其他线程中断当前线程,并支持当前线程在获取锁时中断.
	如果当前线程:
	在进入此方法时设置其中断状态;或获取锁时中断,支持锁获取中断,
	然后抛出InterruptedException并清除当前线程的中断状态。 
	(意思睡眠时其他线程中断了当前线程获取锁直接清除当前线程睡眠状态)
     */
    void lockInterruptibly() throws InterruptedException;
    
	 /**
    只有在调用时它是空闲的时才获取锁。 (意思锁可能拿不到 lock是一定能拿得到)
    获取锁(如果可用),并立即返回值true。如果此方法不可用,则该方法将立即返回false。
    此方法的典型用法是:
    Lock lock = ...;
	 if (lock.tryLock()) {
	   try {
	     // manipulate protected state
	   } finally {
	     lock.unlock();
	   }
	 } else {
	   // perform alternative actions
	 }
	 这种用法确保锁在被获取时被解锁,而在未获得锁时不尝试解锁。
     */
    boolean tryLock();
    
    /**
   	tryLock重载方法
    如果锁在给定的等待时间内空闲并且当前线程没有中断,则获取该锁。
    如果指定的等待时间为false,则返回的值为false。如果时间小于或等于零,则该方法根本不会等待。
	time–等待锁定的最长时间
	unit–时间参数的时间单位
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
     /**
     释放锁。
     注意:
	锁实现通常会对线程释放锁施加限制(通常只有锁的持有者才能释放锁),如果违反了限制,
	则可能会抛出(未检查的)异常。任何限制和异常类型都必须由该锁实现记录。
     */
    void unlock();
    
    /**
   	返回绑定到此Lock实例的新条件实例。

	在等待条件之前,锁必须由当前线程持有。打电话给Condition.await() 将在等待之前自动释放锁,
	并在等待返回之前重新获取锁。
	
	施注意事项
	
	条件实例的确切操作取决于锁实现,并且必须由该实现记录。
	Condition  实现 wait notify 的功能 并且功能更强大
     */
    Condition newCondition();

1.1 可重入

  • 可重入锁是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此 有权利再次获取这把锁
  • 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
/**
 * Description: ReentrantLock 可重入锁, 同一个线程可以多次获得锁对象
 */
@Slf4j(topic = "z.ReentrantTest")
public class ReentrantTest {
   
   

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
   
   
    	// 如果有竞争就进入`阻塞队列`, 一直等待着,不能被打断
    	// 主线程main获得锁
        lock.lock();
        try {
   
   
            log.debug("entry main...");
            m1();
        } finally {
   
   
            lock.unlock();
        }
    }

    private static void m1() {
   
   
        lock.lock();
        try {
   
   
            log.debug("entry m1...");
            m2();
        } finally {
   
   
            lock.unlock();
        }
    }

    private static void m2() {
   
   
        log.debug("entry m2....");
    }
}

运行结果

2022-03-11 21:15:34 [main] - entry main...
2022-03-11 21:15:34 [main] - entry m1...
2022-03-11 21:15:34 [main] - entry m2....

Process finished with exit code 0

synchronized的可重入

static final Object obj = new Object();
public static void method1() {
   
   
     synchronized( obj ) {
   
   
         // 同步块 A
         method2();
     }
}
public static void method2() {
   
   
     synchronized( obj ) {
   
   
         // 同步块 B
     }
}

1.2 可中断 lockInterruptibly()

synchronized 和 reentrantlock.lock() 的锁, 是不可被打断的; 也就是说别的线程已经获得了锁, 线程就需要一直等待下去,不能中断,直到获得到锁才运行。

通过reentrantlock.lockInterruptibly(); 可以通过调用阻塞线程的t1.interrupt();方法
打断。

/**
 * @ClassName ReentrantTest1
 * @author: shouanzh
 * @Description ReentrantLock, 演示RenntrantLock中的可打断锁方法 lock.lockInterruptibly();
 * @date 2022/3/11 21:31
 */
@Slf4j
public class ReentrantTest1 {
   
   

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
   
   

        Thread thread = new Thread(() -> {
   
   
            try {
   
   
                // 如果没有竞争那么此方法就会获取 lock 对象锁
                // 如果有竞争就进入阻塞队列,可以被其它线程用 interruput 方法打断
                log.debug("尝试获得锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
   
   
                e.printStackTrace();
                log.debug("t1线程没有获得锁,被打断...return");
                return;
            }

            try {
   
   
                log.debug("t1线程获得了锁");
            } finally {
   
   
                lock.unlock();
            }
        }, "t1");

        // t1启动前 主线程先获得了锁
        lock.lock();
        thread.start();
        Thread.sleep(1000);
        log.debug("interrupt...打断t1");
        thread.interrupt();
    }

}
2022-03-11 21:42:43 [t1] - 尝试获得锁
2022-03-11 21:42:44 [main] - interrupt...打断t1
2022-03-11 21:42:44 [t1] - t1线程没有获得锁,被打断...return
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:900)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1225)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
	at com.concurrent.reentrantlocktest.ReentrantTest1.lambda$main$0(ReentrantTest1.java:25)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

测试使用lock.lock()不可以从阻塞队列中打断, 一直等待别的线程释放锁

@Slf4j
public class ReentrantTest1 {
   
   

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
   
   

        Thread thread = new Thread(() -> {
   
   
//            try {
   
   
//                // 如果没有竞争那么此方法就会获取 lock 对象锁
//                // 如果有竞争就进入阻塞队列,可以被其它线程用 interruput 方法打断
//                log.debug("尝试获得锁");
//                lock.lockInterruptibly();
//            } catch (InterruptedException e) {
   
   
//                e.printStackTrace();
//                log.debug("t1线程没有获得锁,被打断...return");
//                return;
//            }

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值