ReentrantLock 学习

文章详细对比了ReentrantLock与synchronized的区别,包括用法、锁的获取与释放、锁类型、响应中断和底层实现。ReentrantLock提供了更多的特性,如可中断、可设置超时、公平锁以及条件变量,使其在复杂同步场景下更为灵活。同时,文章通过示例展示了ReentrantLock如何解决死锁问题和实现顺序控制。
摘要由CSDN通过智能技术生成

参考:
https://blog.csdn.net/ZSA222/article/details/123433746
https://blog.csdn.net/sufu1065/article/details/124464245

一、ReentrantLock 与 Synchronized 的区别:

小结:
synchronized 和 ReentrantLock 都是 Java 中提供的可重入锁,二者的主要区别有以下 5 个:
1、用法不同:synchronized 可以用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用于代码块。
2、获取锁和释放锁的机制不同:synchronized 是自动加锁和释放锁的,而 ReentrantLock 需要手动加锁和释放锁。
3、锁类型不同:synchronized 是非公平锁,而 ReentrantLock 默认为非公平锁,也可以手动指定为公平锁。
4、响应中断不同:ReentrantLock 可以响应中断,解决死锁的问题,而 synchronized 不能响应中断。
5、底层实现不同:synchronized 是 JVM 层面通过监视器实现的,而 ReentrantLock 是基于 AQS 实现的。

区别1:用法不同
  • synchronized 可用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用在代码块上。
区别2:获取锁和释放锁方式不同
  • synchronized 会自动加锁和释放锁,当进入 synchronized 修饰的代码块之后会自动加锁,当离开 synchronized 的代码段之后会自动释放锁
public void method() {
    // 加锁代码
    synchronized (this) {
        // ...
    }
}
  • ReentrantLock 需要手动加锁和释放锁

PS:在使用 ReentrantLock 时要特别小心,unlock 释放锁的操作一定要放在 finally 中,否者有可能会出现锁一直被占用,从而导致其他线程一直阻塞的问题。


public class LockExample {
    // 创建锁对象
    private final ReentrantLock lock = new ReentrantLock();
    public void method() {
        // 加锁操作
        lock.lock();
        try {
            // ...
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

区别3:锁类型不同

synchronized 属于非公平锁,而 ReentrantLock 既可以是公平锁也可以是非公平锁。默认情况下 ReentrantLock 为非公平锁,这点查看源码可知:

区别4:响应中断不同

ReentrantLock 可以使用 lockInterruptibly 获取锁并响应中断指令,而 synchronized 不能响应中断,也就是如果发生了死锁,使用 synchronized 会一直等待下去,而使用 ReentrantLock 可以响应中断并释放锁,从而解决死锁的问题,比如以下 ReentrantLock 响应中断的示例:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class ReentrantLockInterrupt {
    static Lock lockA = new ReentrantLock();
    static Lock lockB = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        // 线程 1:先获取 lockA 再获取 lockB
        Thread t1 = new Thread(() -> {
            try {
                // 先获取 LockA
                lockA.lockInterruptibly();
                // 休眠 10 毫秒
                TimeUnit.MILLISECONDS.sleep(100);
                // 获取 LockB
                lockB.lockInterruptibly();
            } catch (InterruptedException e) {
                System.out.println("响应中断指令");
            } finally {
                // 释放锁
                lockA.unlock();
                lockB.unlock();
                System.out.println("线程 1 执行完成。");
            }
        });
        // 线程 2:先获取 lockB 再获取 lockA
        Thread t2 = new Thread(() -> {
            try {
                // 先获取 LockB
                lockB.lockInterruptibly();
                // 休眠 10 毫秒
                TimeUnit.MILLISECONDS.sleep(100);
                // 获取 LockA
                lockA.lockInterruptibly();
            } catch (InterruptedException e) {
                System.out.println("响应中断指令");
            } finally {
                // 释放锁
                lockB.unlock();
                lockA.unlock();
                System.out.println("线程 2 执行完成。");
            }
        });
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(1);
        // 线程1:执行中断
        t1.interrupt();
    }
}

以上程序的执行结果如下所示:
在这里插入图片描述

区别5:底层实现不同

synchronized 是 JVM 层面通过监视器(Monitor)实现的,而 ReentrantLock 是通过 AQS(AbstractQueuedSynchronizer)程序级别的 API 实现。synchronized 通过监视器实现,可通过观察编译后的字节码得出结论,如下图所示:
在这里插入图片描述
其中 monitorenter 表示进入监视器,相当于加锁操作,而 monitorexit 表示退出监视器,相当于释放锁的操作。ReentrantLock 是通过 AQS 实现,可通过观察 ReentrantLock 的源码得出结论,核心实现源码如下:
在这里插入图片描述

二、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、可重入
  • 可重入锁是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此 有权利再次获取这把锁
  • 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
/**
 * 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
     }
}

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;
//            }

            log.debug("尝试获得锁");
            lock.lock();

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

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

}

调用thread.interrupt();后 thread线程并没被打断。
在这里插入图片描述

3 设置超时时间 tryLock()

使用 lock.tryLock() 方法会返回获取锁是否成功。如果成功则返回true,反之则返回false。

并且tryLock方法可以设置指定等待时间,参数为:tryLock(long timeout, TimeUnit unit) , 其中timeout为最长等待时间,TimeUnit为时间单位

获取锁的过程中, 如果超过等待时间, 或者被打断, 就直接从阻塞队列移除, 此时获取锁就失败了, 不会一直阻塞着 ! (可以用来实现死锁问题)

不设置等待时间, 立即失败:

/**
 * @ClassName ReentrantTest2
 * @author: shouanzh
 * @Description 测试锁超时
 * @date 2022/3/11 22:18
 */
@Slf4j
public class ReentrantTest2 {

    private static ReentrantLock lock = new ReentrantLock();

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

        Thread t1 = new Thread(() -> {
            log.debug("尝试获得锁");
            // 此时肯定获取失败, 因为主线程已经获得了锁对象
            if (!lock.tryLock()) {
                log.debug("获取立刻失败,返回");
                return;
            }
            try {
                log.debug("获得到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

		// 主线程先获得锁
        lock.lock();
        log.debug("获得到锁");
        t1.start();
        // 主线程2s之后才释放锁
        Thread.sleep(2000);
        log.debug("释放了锁");
        lock.unlock();
    }

}

2022-03-11 22:20:09 [main] - 获得到锁
2022-03-11 22:20:09 [t1] - 尝试获得锁
2022-03-11 22:20:09 [t1] - 获取立刻失败,返回
2022-03-11 22:20:11 [main] - 释放了锁

Process finished with exit code 0

设置等待时间:

/**
 * @ClassName ReentrantTest2
 * @author: shouanzh
 * @Description 测试锁超时
 * @date 2022/3/11 22:18
 */
@Slf4j
public class ReentrantTest2 {

    private static ReentrantLock lock = new ReentrantLock();

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

        Thread t1 = new Thread(() -> {
            log.debug("尝试获得锁");
            // 此时肯定获取失败, 因为主线程已经获得了锁对象
            try {
                // 等待1s
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("等待1s获取不到锁");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("获取不到锁");
                return;
            }
            try {
                log.debug("获得到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        log.debug("获得到锁");
        t1.start();
        // 主线程2s之后才释放锁
        Thread.sleep(2000);
        log.debug("释放了锁");
        lock.unlock();
    }

}

2022-03-11 22:32:32 [main] - 获得到锁
2022-03-11 22:32:32 [t1] - 尝试获得锁
2022-03-11 22:32:33 [t1] - 等待1s获取不到锁
2022-03-11 22:32:34 [main] - 释放了锁

Process finished with exit code 0

4、通过lock.tryLock()来解决, 哲学家就餐问题
5、公平锁 new ReentrantLock(true)
  • ReentrantLock默认是非公平锁, 可以指定为公平锁new ReentrantLock(true)。
  • 在线程获取锁失败,进入阻塞队列时,先进入的会在锁被释放后先获得锁。这样的获取方式就是公平的。一般不设置ReentrantLock为公平的, 会降低并发度
  • Synchronized底层的Monitor锁就是不公平的, 和谁先进入阻塞队列是没有关系的。
    公平锁
    多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
    优点:所有的线程都能得到资源,不会饿死在队列中。
    缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
    非公平锁
    多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
    优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
    缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
6 条件变量 Condition

传统对象等待集合只有一个 waitSet, Lock可以通过newCondition()方法 生成多个等待集合Condition对象。 Lock和Condition 是一对多的关系

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet等待
ReentrantLock的条件变量比 synchronized强大之处在于,它是支持多个条件变量的,这就好比
synchronized 是那些不满足条件的线程都在一间休息室等消息
而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来 唤醒

使用流程
1.await 前需要 获得锁
2.await 执行后,会释放锁,进入 conditionObject (条件变量)中等待
3.await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
4.竞争 lock 锁成功后,从 await 后继续执行
5.signal 方法用来唤醒条件变量(等待室)汇总的某一个等待的线程
6.signalAll方法, 唤醒条件变量(休息室)中的所有线程

相关方法:

	/**
     使当前线程等待,直到发出siganal信号或中断。
     与此condition相关联的Lock将被自动释放,当前线程出于线程调度目的被禁用并处于休眠状态,
     直到发生以下四种情况之一:
     	1,另一个线程为此condition调用signal方法,并且当前线程恰好被选为要唤醒的线程;
        2,其他线程为此condition调用signalAll方法
        3.其他线程中断当前线程,支持线程挂起中断
        4.出现“虚假唤醒”。
	跟wait一样被唤醒还是的竞争锁竞争到了才能执行await之后的代码
	如果当前线程:在进入此方法时设置其中断状态;或等待时中断,支持线程挂起中断,
	然后抛出InterruptedException并清除当前线程的中断状态。
	在第一种情况下,没有规定是否在释放锁之前进行中断测试。
     */
    void await() throws InterruptedException;
    
 	/**
    	与await 相同  不可被中断
    	与此condition相关联的Lock将被自动释放,当前线程出于线程调度目的被禁用并处于休眠状态,
     	直到发生以下四种情况之一:
    	1,另一个线程为此condition调用signal方法,并且当前线程恰好被选为要唤醒的线程;
        2,其他线程为此condition调用signalAll方法
        4.出现“虚假唤醒”
     */
    void awaitUninterruptibly();
    
     /**
   	使当前线程等待,直到发出siganal信号或中断,或指定的等待时间过去。
   	1,另一个线程为此condition调用signal方法,并且当前线程恰好被选为要唤醒的线程;
       2,其他线程为此condition调用signalAll方法
       3.其他线程中断当前线程,支持线程挂起中断
       4.出现“虚假唤醒”。
       5.指定的时间到了,会被唤醒
	 如下
	 nanos = theCondition.awaitNanos(500); 
	 nanos = 300 
	 意思就是 等待200纳秒就被唤醒
	 返回值为500-200 返回值是估值 近似 其次该等待时间使用纳秒为了更准确
 
	如果返回时给定提供的nanoTimeout值,则该方法返回要等待的纳秒数的估计值,如果超时,
	则返回小于或等于零的值。此值可用于确定在等待返回但等待的条件仍不成立的情况下是否重新
	等待以及重新等待多长时间。典型的方法有以下几种:
	boolean aMethod(long timeout, TimeUnit unit) {
    long nanos = unit.toNanos(timeout);//转成纳秒
    lock.lock();
    try {
     while (!conditionBeingWaitedFor()) {
       if (nanos <= 0L) // <=0 时 返回超时了  
         return false;
       nanos = theCondition.awaitNanos(nanos);
     }
     // ...
      } finally {
       lock.unlock();
      }
    }
	Params:nanoTimeout–等待的最长时间,以纳秒为单位
	return:nanoTimeout值的估计值减去从该方法返回时所花费的时间。正值可以用作后续调用此方法以完成等待所需时间的参数。小于或等于零的值表示没有时间剩余。
     */
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    

     /**
	   	使当前线程等待,直到发出信号或中断,或指定的等待时间过去。这种方法在行为上等同于:
	    awaitNanos(unit.toNanos(time)) > 0
  
     * @param 等待的最长时间
     * @param 时间参数的时间单位
     * @return 如果超过等待时间,则为false,否则为true
    
     */
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    
	/*
		使当前线程等待,直到发出信号或中断,或指定的截止时间过去。
		 boolean aMethod(Date deadline) {
		   boolean stillWaiting = true;
		   lock.lock();
		   try {
		     while (!conditionBeingWaitedFor()) {
		       if (!stillWaiting)
		         return false;
		       stillWaiting = theCondition.awaitUntil(deadline);
		     }
		     // ...
		   } finally {
		     lock.unlock();
		   }
		 }
	Params 截止日期–等待的绝对时间
	return 如果返回时已过截止日期,则为false,否则为true
	*/
    boolean awaitUntil(Date deadline) throws InterruptedException;

 	/**
   		唤醒一个等待的线程。
		如果有多个线程正在此condition实例中等待,则选择一个线程进行唤醒。
		然后,该线程必须在从await返回之前重新获取锁。(意思就是被唤醒还是的竞争锁)
		signal调用必须在lock 获取 和锁释放之间
     */
    void signal();
    
    /**
    唤醒所有等待的线程。
    如果有线程在此condition实例下等待,那么它们都将被唤醒。每个线程必须重新获取锁,然后才能从await返回。(意思就是被唤醒还是的竞争锁)
    signalAll调用必须在lock 获取 和锁释放之间
     */
    void signalAll();

代码举例:

/**
 * Description: ReentrantLock可以设置多个条件变量(多个休息室), 相对于synchronized底层monitor锁中waitSet
 */
@Slf4j(topic = "z.ConditionVariable")
public class ConditionVariable {
    private static boolean hasCigarette = false;
    private static boolean hasTakeout = false;
    private static final ReentrantLock lock = new ReentrantLock();

    // 等待烟的休息室(条件变量)
    static Condition waitCigaretteSet = lock.newCondition();
    // 等外卖的休息室(条件变量)
    static Condition waitTakeoutSet = lock.newCondition();

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

        new Thread(() -> {
            lock.lock();
            try {
                log.debug("有烟没?[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        // 此时小南进入到 等烟的休息室
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("烟来咯, 可以开始干活了");
            } finally {
                lock.unlock();
            }
        }, "小南").start();

        new Thread(() -> {
            lock.lock();
            try {
                log.debug("外卖送到没?[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("没外卖,先歇会!");
                    try {
                        // 此时小女进入到 等外卖的休息室
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外卖来咯, 可以开始干活了");
            } finally {
                lock.unlock();
            }
        }, "小女").start();

        Thread.sleep(1000);
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("送外卖的来咯~");
                hasTakeout = true;
                // 唤醒等外卖的小女线程
                waitTakeoutSet.signal();
            } finally {
                lock.unlock();
            }
        }, "送外卖的").start();

        Thread.sleep(1000);
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("送烟的来咯~");
                hasCigarette = true;
                // 唤醒等烟的小南线程
                waitCigaretteSet.signal();
            } finally {
                lock.unlock();
            }
        }, "送烟的").start();
    }
}

允许:

2022-03-11 23:31:22 [小南] - 有烟没?[false]
2022-03-11 23:31:22 [小南] - 没烟,先歇会!
2022-03-11 23:31:22 [小女] - 外卖送到没?[false]
2022-03-11 23:31:22 [小女] - 没外卖,先歇会!
2022-03-11 23:31:23 [送外卖的] - 送外卖的来咯~
2022-03-11 23:31:23 [小女] - 外卖来咯, 可以开始干活了
2022-03-11 23:31:24 [送烟的] - 送烟的来咯~
2022-03-11 23:31:24 [小南] - 烟来咯, 可以开始干活了

Process finished with exit code 0

2.同步模式之顺序控制

2.1 固定运行顺序
比如必须先打印2再打印1

使用wait/notify来实现顺序打印 2, 1

/**
 * Description: 使用wait/notify来实现顺序打印 2, 1
 */
@Slf4j(topic = "z.SyncPrintWaitTest")
public class SyncPrintWaitTest {

    public static final Object lock = new Object();
    // t2线程释放执行过
    public static boolean t2Runned = false;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                while (!t2Runned) {
                    try {
                    	// 进入等待(waitset), 会释放锁
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("1");
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                log.debug("2");
                t2Runned = true;
                lock.notify();
            }
        }, "t2");

        t1.start();
        t2.start();
    }
}

使用LockSupport中的park/unpark

/**
 * Description: 使用LockSupport中的park,unpark来实现, 顺序打印 2, 1
 */
@Slf4j(topic = "z.SyncPrintWaitTest")
public class SyncPrintWaitTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
            log.debug("1");
        }, "t1");
        t1.start();

        new Thread(() -> {
            log.debug("2");
            LockSupport.unpark(t1);
        }, "t2").start();
    }
}

使用ReentrantLock的await/signal

/**
 * @ClassName SyncPrintWaitTest2
 * @author: shouanzh
 * @Description 使用ReentrantLock的await/signal 顺序打印 2、1
 * @date 2022/3/12 11:01
 */
@Slf4j
public class SyncPrintWaitTest2 {

    private static final ReentrantLock LOCK = new ReentrantLock();
    static Condition condition = LOCK.newCondition();
    // t2线程 是否执行过
    public static boolean t2Runned = false;

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            LOCK.lock();
            try {
                while (!t2Runned) {
                    condition.await();
                }
                log.debug("1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                LOCK.unlock();
            }
        },"t1");


        Thread t2 = new Thread(() -> {
            LOCK.lock();
            try {
                log.debug("2");
                t2Runned = true;
                condition.signal();
            } finally {
                LOCK.unlock();
            }
        },"t2");

        t1.start();
        t2.start();
    }
}

2.2 交替输出
线程1输出a5次,线程2输出b5次,线程3输出c5次。现在要求输出 abcabcabcabcabc怎么实现

wait/notify版本

/**
 * @ClassName AlternatePrint
 * @author: shouanzh
 * @Description 线程1输出a5次,线程2输出b5次,线程3输出c5次。现在要求输出 abcabcabcabcabc怎么实现
 * @date 2022/3/12 11:19
 */
@Slf4j
public class AlternatePrint {

    public static void main(String[] args) {

        WaitNotify waitNotify = new WaitNotify(1, 5);

        new Thread(() -> {
            waitNotify.print("a", 1, 2);
        }, "a线程").start();

        new Thread(() -> {
            waitNotify.print("b", 2, 3);
        }, "b线程").start();

        new Thread(() -> {
            waitNotify.print("c", 3, 1);
        }, "c线程").start();

    }

}

class WaitNotify {

    // 等待标记
    private int flag;
    // 循环次数
    private int loopNumber;

    public WaitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }

    /**
     * print
     *
     * @param str      打印的内容
     * @param waitFlag 等待标记
     * @param nextFlag 下一个标记
     */
     
     /**
	 * 输出内容    等待标记    下一个标记
	 * a           1          2
	 * b           2          3
	 * c           3          1
	 */
    public void print(String str, int waitFlag, int nextFlag) {

        for (int i = 0; i < loopNumber; i++) {
            synchronized (this) {
                while (waitFlag != flag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.print(str);
                flag = nextFlag;
                this.notifyAll();
            }
        }
    }
}

abcabcabcabcabc
Process finished with exit code 0

await/signal版本:

/**
 * @ClassName AlternatePrintTest2
 * @author: shouanzh
 * @Description 线程1输出a5次,线程2输出b5次,线程3输出c5次。现在要求输出 abcabcabcabcabc怎么实现
 * @date 2022/3/12 11:49
 */
@Slf4j
public class AlternatePrintTest2 {

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

        AwaitSignal awaitSignal = new AwaitSignal(5);

        Condition a_condition = awaitSignal.newCondition();
        Condition b_condition = awaitSignal.newCondition();
        Condition c_condition = awaitSignal.newCondition();

        new Thread(() -> {
            awaitSignal.print("a", a_condition, b_condition);
        }, "a").start();

        new Thread(() -> {
            awaitSignal.print("b", b_condition, c_condition);
        }, "b").start();

        new Thread(() -> {
            awaitSignal.print("c", c_condition, a_condition);
        }, "c").start();

        Thread.sleep(1000);
        System.out.println("开始...");
        awaitSignal.lock();
        try {
            a_condition.signal();  // 首先唤醒a线程
        } finally {
            awaitSignal.unlock();
        }
    }

}

class AwaitSignal extends ReentrantLock {
    private final int loopNumber;

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    /**
     * print
     * @param str 打印的内容
     * @param current 进入那间休息室
     * @param next 下一间休息室
     */
    public void print(String str, Condition current, Condition next) {
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                try {
                    current.await();
                    System.out.print(str);
                    // 唤醒下一间休息室
                    next.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                unlock();
            }
        }
    }
}

LockSupport的park/unpark实现

/**
 * Description: 线程1输出a5次,线程2输出b5次,线程3输出c5次。现在要求输出 abcabcabcabcabc怎么实现
 */
@Slf4j
public class TestParkUnpark {
    static Thread a;
    static Thread b;
    static Thread c;

    public static void main(String[] args) {
        ParkUnpark parkUnpark = new ParkUnpark(5);

        a = new Thread(() -> {
            parkUnpark.print("a", b);
        }, "a");

        b = new Thread(() -> {
            parkUnpark.print("b", c);
        }, "b");

        c = new Thread(() -> {
            parkUnpark.print("c", a);
        }, "c");

        a.start();
        b.start();
        c.start();

        LockSupport.unpark(a);

    }
}

class ParkUnpark {
    private final int loopNumber;

    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str, Thread nextThread) {
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            System.out.print(str);
            LockSupport.unpark(nextThread);
        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值