2、AQS之ReentrantLock详解

目录

  • ReentrantLock特点
  • synchronized和ReentrantLock的区别
  • 应用
    • 可重入
    • 可中断
    • 锁超时
      • 立即失败
      • 超时失败
    • 公平锁
    • 条件变量
  • 源码分析图

ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全

ReentrantLock特点

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

synchronized和ReentrantLock的区别

  • synchronized是JVM层次的锁实现,ReentrantLock是JDK层次的锁实现;
  • synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过ReentrantLock#isLocked判断;
  • synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;
  • synchronized是不可以被中断的,而ReentrantLock#lockInterruptibly方法是可以被中断的;
  • 在发生异常时synchronized会自动释放锁,而ReentrantLock需要开发者在finally块中显示释放锁;
  • ReentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待指定时长的获取,更加灵活;
  • synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得锁,而ReentrantLock对于已经在等待的线程是先来的线程先获得锁

应用

使用示例

ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
ReentrantLock lock = new ReentrantLock(true); //公平锁
//加锁
lock.lock();
try {
	//临界区
} finally {
	// 解锁
	lock.unlock();
}

测试

public class ReentrantLockDemo {
	private static  int sum = 0;
	private static final Lock lock = new ReentrantLock();
	public static void main(String[] args) throws InterruptedException {
		for (int i = 0; i < 3; i++) {
			Thread thread = new Thread(()->{
				//加锁
				lock.lock();
				try {
					// 临界区代码
					for (int j = 0; j < 10000; j++) {
						sum++;
					}
				} finally {
					// 解锁
					lock.unlock();
				}
			});
			thread.start();
		}
		Thread.sleep(2000);
		System.out.println(sum);
	}
}
//运行结果:30000

可重入

@Slf4j
public class ReentrantLockDemo2 {
    public static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        method1();
    }
    public static void method1() {
        lock.lock();
        try {
            log.debug("execute method1");
            method2();
        } finally {
            lock.unlock();
        }
    }
    public static void method2() {
        lock.lock();
        try {
            log.debug("execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }
    public static void method3() {
        lock.lock();
        try {
            log.debug("execute method3");
        } finally {
            lock.unlock();
        }
    }
}

运行结果

22:25:01.850 [main] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo2 - execute method1
22:25:01.863 [main] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo2 - execute method2
22:25:01.863 [main] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo2 - execute method3

可中断

@Slf4j
public class ReentrantLockDemo3 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("t1启动...");
            try {
                lock.lockInterruptibly();
                try {
                    log.debug("t1获得了锁");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("t1等锁的过程中被中断");
            }
        }, "t1");
        lock.lock();
        try {
            log.debug("main线程获得了锁");
            t1.start();
            //先让线程t1执行
            Thread.sleep(1000);

            t1.interrupt();
            log.debug("线程t1执行中断");
        } finally {
            lock.unlock();
        }
    }
}

运行结果

22:25:45.220 [main] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo3 - main线程获得了锁
22:25:45.237 [t1] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo3 - t1启动…
22:25:46.247 [main] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo3 - 线程t1执行中断
22:25:46.247 [t1] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo3 - t1等锁的过程中被中断
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo3.lambda$main$0(ReentrantLockDemo3.java:17)
at java.lang.Thread.run(Thread.java:748)

锁超时

立即失败

@Slf4j
public class ReentrantLockDemo4 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("t1启动...");
            if (!lock.tryLock()) {
                log.debug("t1获取锁失败,立即返回false");
                return;
            }
            try {
                log.debug("t1获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        try {
            log.debug("main线程获得了锁");
            t1.start();
            //先让线程t1执行
            Thread.sleep(2000);
        } finally {
            lock.unlock();
        }
    }
}

运行结果

22:32:14.464 [main] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo4 - main线程获得了锁
22:32:14.480 [t1] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo4 - t1启动…
22:32:14.480 [t1] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo4 - t1获取锁失败,立即返回false

超时失败

@Slf4j
public class ReentrantLockDemo4 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("t1启动...");
            //超时
            try {
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("等待 1s 后获取锁失败,返回");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }
            try {
                log.debug("t1获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        try {
            log.debug("main线程获得了锁");
            t1.start();
            //先让线程t1执行
            Thread.sleep(2000);
        } finally {
            lock.unlock();
        }
    }
}

运行结果

22:29:02.020 [main] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo4 - main线程获得了锁
22:29:02.033 [t1] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo4 - t1启动…
22:29:03.035 [t1] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo4 - 等待 1s 后获取锁失败,返回

公平锁

ReentrantLock 默认是不公平的

@Slf4j
public class ReentrantLockDemo5 {
    public static void main(String[] args) throws InterruptedException {
        //ReentrantLock lock = new ReentrantLock(true); //公平锁
        ReentrantLock lock = new ReentrantLock(); //非公平锁
        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.debug(Thread.currentThread().getName() + " running...");
                } finally {
                    lock.unlock();
                }
            }, "t" + i).start();
        }
        // 1s 之后去争抢锁
        Thread.sleep(1000);
        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    log.debug(Thread.currentThread().getName() + " running...");
                } finally {
                    lock.unlock();
                }
            }, "强行插入" + i).start();
        }
    }
}

运行结果
可以看出,在队列中的线程没有执行完的情况下,还是可以插队执行

在这里插入图片描述


但是从性能角度来说,非公平锁的性能是要高于公平锁的

条件变量

java.util.concurrent类库中提供Condition类来实现线程之间的协调。调用Condition.await() 方法使线程等待,其他线程调用Condition.signal() 或Condition.signalAll() 方法唤醒等待的线程
注意:调用Condition的await()和signal()方法,都必须在lock保护之内

@Slf4j
public class ReentrantLockDemo6 {
    private static final ReentrantLock lock = new ReentrantLock();
    private static final Condition cigCon = lock.newCondition();
    private static final Condition takeCon = lock.newCondition();
    private static boolean hashCig = false;
    private static boolean hasTakeout = false;
    //送烟
    public void cigratee() {
        lock.lock();
        try {
            while (!hashCig) {
                try {
                    log.debug("没有烟,歇一会");
                    cigCon.await();

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            log.debug("有烟了,干活");
        } finally {
            lock.unlock();
        }
    }
    //送外卖
    public void takeout() {
        lock.lock();
        try {
            while (!hasTakeout) {
                try {
                    log.debug("没有饭,歇一会");
                    takeCon.await();

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            log.debug("有饭了,干活");
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        ReentrantLockDemo6 test = new ReentrantLockDemo6();
        new Thread(test::cigratee).start();
        new Thread(test::takeout).start();
        new Thread(() -> {
            lock.lock();
            try {
                hashCig = true;
                log.debug("唤醒送烟的等待线程");
                cigCon.signal();
            } finally {
                lock.unlock();
            }
        }, "t1").start();
        new Thread(() -> {
            lock.lock();
            try {
                hasTakeout = true;
                log.debug("唤醒送饭的等待线程");
                takeCon.signal();
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}

运行结果

22:40:07.812 [Thread-0] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo6 - 没有烟,歇一会
22:40:07.825 [Thread-1] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo6 - 没有饭,歇一会
22:40:07.825 [t1] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo6 - 唤醒送烟的等待线程
22:40:07.825 [t2] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo6 - 唤醒送饭的等待线程
22:40:07.825 [Thread-0] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo6 - 有烟了,干活
22:40:07.825 [Thread-1] DEBUG com.example.demo.seven_two_six.concurrent.aqs.reentrantlock.ReentrantLockDemo6 - 有饭了,干活

源码分析图

借鉴fox大佬的源码分析图:https://www.processon.com/view/link/6191f070079129330ada1209
源码逻辑图:
公平锁非公平锁

你可能感兴趣的:(进阶,并发编程-JDK,java,悲观锁,aqs,lock,ReentrantLock)