java多线程之ReentrantLock详解

1.背景

2.基本语法

public class Test01 {
    // 定义锁
    static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) {
        // 获取锁
        reentrantLock.lock();
        try {
            // 临界区业务处理
        } finally {
            // 释放锁
            reentrantLock.unlock();
        }
    }
}

 

3.特点

1.可重入

2.可打断

3.锁超时

4.公平锁

5.条件变量

3.1.可重入

package com.ldp.demo03ReentrantLock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 02/02 9:03
 * @description <p>
 * ReentrantLock特点
 * 1.可重入
 * 2.可打断
 * 3.锁超时
 * 4.公平锁
 * 5.条件变量
 * </p>
 * 本节演示可重入特性:
 * 可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
 * 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住,导致获取锁失败
 */
@Slf4j
public class Test02 {
    static ReentrantLock reentrantLock = new ReentrantLock();
    /**
     * 可重入特性演示
     *
     * @param args
     */
    public static void main(String[] args) {
        reentrantLock.lock();
        try {
            log.info("进入main方法锁");
            method01();
        } finally {
            reentrantLock.unlock();
        }
        log.info("测试结束");
    }

    public static void method01() {
        reentrantLock.lock();
        try {
            log.info("进入method01方法锁");
            method02();
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void method02() {
        reentrantLock.lock();
        try {
            log.info("进入method02方法锁");
        } finally {
            reentrantLock.unlock();
        }
    }
}

 

测试结果:

09:09:18.253 [main] -> 进入main方法锁
09:09:18.253 [main] -> 进入method01方法锁
09:09:18.253 [main] -> 进入method02方法锁
09:09:18.253 [main] -> 测试结束

 

3.2.可打断

演示代码:

@Slf4j
public class Test03 {
    // 定义锁
    static ReentrantLock reentrantLock = new ReentrantLock();

    /**
     * 可打断特性
     * @param args
     */
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                // 获取一个可以被打断的锁
                reentrantLock.lockInterruptibly();
            } catch (InterruptedException e) {
                log.info("锁被打断....");
                e.printStackTrace();
            }
            try {
                log.info("获得锁....");
            } finally {
                reentrantLock.unlock();
            }
        }, "t1");
        // 主线程获得锁
        log.info("获得了锁");
        reentrantLock.lock();
        t1.start();
        try {
            // 等待2秒钟
            MyThreadUtil.sleep(2);
            t1.interrupt();
        } finally {
            reentrantLock.unlock();
        }
    }
}

 

结果:

10:00:51.012 [main] -> 获得了锁
10:00:53.033 [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.ldp.demo03ReentrantLock.Test03.lambda$main$0(Test03.java:32)
    at java.lang.Thread.run(Thread.java:748)
10:00:53.033 [t1] -> 获得锁....
Exception in thread "t1" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
    at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
    at com.ldp.demo03ReentrantLock.Test03.lambda$main$0(Test03.java:40)
    at java.lang.Thread.run(Thread.java:748)

 

3.3.锁超时

演示代码:

@Slf4j
public class Test04 {
    // 定义锁
    static ReentrantLock reentrantLock = new ReentrantLock();

    /**
     * 锁超时
     *
     * @param args
     */
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                // 获取一个超时锁
                boolean tryLock = reentrantLock.tryLock(1, TimeUnit.SECONDS);
                if (!tryLock) {
                    log.info("获取锁超时....");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                log.info("获得锁....");
            } finally {
                reentrantLock.unlock();
            }
        }, "t1");
        // 主线程获得锁
        log.info("获得了锁");
        reentrantLock.lock();
        t1.start();
        try {
            // 等待2秒钟
            MyThreadUtil.sleep(2);
        } finally {
            reentrantLock.unlock();
        }
    }
}

 

结果:

10:05:47.059 [main] -> 获得了锁
10:05:48.087 [t1] -> 获取锁超时....

 

3.4.公平锁

// 定义锁,默认是非公平锁(生产上一般使用非公平锁)
    static ReentrantLock reentrantLock = new ReentrantLock();
    // 定义个【非】公平锁
    static ReentrantLock reentrantLock01 = new ReentrantLock(false);
    // 定义个公平锁
    static ReentrantLock reentrantLock02 = new ReentrantLock(true);

 

3.5.条件变量

ReentrantLock条件变量

synchronized 中也有条件变量,相当于只有一个条件变量(一个等待空间),即所有的线程都在一个空间等待,要么唤醒空间中的所有,要么唤醒空间中的一个;

ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持【多个条件变量】(多个等待空间),可以将线程按业务分类,相同业务在同一个等待空间,唤醒时可以唤醒相同类型的业务;

使用要点:

1.await 前需要获得锁

2.await 执行后,会释放锁,进入 conditionObject 等待

3.await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁,竞争 lock 锁成功后,从 await 后继续执行

java多线程之ReentrantLock详解

 

 案例:

package com.ldp.demo03ReentrantLock;

import com.common.MyThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

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

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 02/02 8:53
 * @description <p>
 * 条件变量
 * <p>
 * ReentrantLock条件变量
 * synchronized 中也有条件变量,相当于只有一个条件变量(一个等待空间),即所有的线程都在一个空间等待,要么唤醒空间中的所有,要么唤醒空间中的一个;
 * ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持【多个条件变量】(多个等待空间),可以将线程按业务分类,相同业务在同一个等待空间,唤醒时可以唤醒相同类型的业务;
 * <p>
 * 使用要点:
 * 1.await 前需要获得锁
 * 2.await 执行后,会释放锁,进入 conditionObject 等待
 * 3.await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁,竞争 lock 锁成功后,从 await 后继续执行
 * <p>
 * 特别提醒:使用await()而不是 wait,否则报错:java.lang.IllegalMonitorStateException
 * </p>
 * 案例
 * 假设家长到学校接孩子放学,不同年级的家长在不同的等待区等待孩子;
 * 这里模拟2个年级
 * </p>
 */
@Slf4j
public class Test05 {
    // 锁
    static ReentrantLock reentrantLock = new ReentrantLock();
    // 一年级等待区
    static Condition firstGradeCondition = reentrantLock.newCondition();
    // 二年级等待区
    static Condition secondGradeCondition = reentrantLock.newCondition();
    // 一年级是否放学
    static volatile boolean hasFirst = false;
    // 二年级是否放学
    static volatile boolean hasSecond = false;

    /**
     * 条件变量
     */
    @Test
    public void test01() throws InterruptedException {
        log.info("测试开始");
        Gradle firstGradle = new Gradle("一年级", reentrantLock, firstGradeCondition, hasFirst);
        Gradle secondGradle = new Gradle("二年级", reentrantLock, secondGradeCondition, hasSecond);
        // 启动线程
        firstGradle.start();
        secondGradle.start();

        // 模拟家长提前到了5秒钟
        MyThreadUtil.sleep(5);
        // 模拟二年级放学
        secondGradle.leaveSchool();
        // 2秒后,一年级放学
        MyThreadUtil.sleep(1);
        firstGradle.leaveSchool();

        // 等待线程结束
        firstGradle.join();
        secondGradle.join();
        log.info("测试结束");
    }

    class Gradle extends Thread {
        private String name;
        private ReentrantLock reentrantLock;
        private Condition condition;
        private boolean hasGradle;

        public Gradle(String name, ReentrantLock reentrantLock, Condition condition, boolean hasGradle) {
            this.name = name;
            this.reentrantLock = reentrantLock;
            this.condition = condition;
            this.hasGradle = hasGradle;
        }

        @Override
        public void run() {
            this.setName(name);
            reentrantLock.lock();
            log.info("获得了锁...");
            try {
                while (!hasGradle) {
                    try {
                        // 没有放学等待
                        log.info("没有放学,家长在休息室睡觉....");
                        // condition.wait(); 特别注意这个方法,别错了,wait()是Object的等待方法,会报错:java.lang.IllegalMonitorStateException
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("放学了可以带着孩子回家了.....");
            } finally {
                reentrantLock.unlock();
            }
        }

        /**
         * 模拟放学
         */
        public void leaveSchool() {
            reentrantLock.lock();
            try {
                log.info(name + " 放学了");
                // 设置放学标记为true
                hasGradle = true;
                // 唤醒当前年级所有睡觉的家长
                condition.signalAll();
            } finally {
                reentrantLock.unlock();
            }
        }
    }
}

 

结果:

11:49:42.176 [main] -> 测试开始
11:49:42.200 [一年级] -> 获得了锁...
11:49:42.200 [一年级] -> 没有放学,家长在休息室睡觉....
11:49:42.201 [二年级] -> 获得了锁...
11:49:42.201 [二年级] -> 没有放学,家长在休息室睡觉....
11:49:47.201 [main] -> 二年级 放学了
11:49:47.201 [二年级] -> 放学了可以带着孩子回家了.....
11:49:48.215 [main] -> 一年级 放学了
11:49:48.215 [一年级] -> 放学了可以带着孩子回家了.....
11:49:48.215 [main] -> 测试结束

Process finished with exit code 0

完美

上一篇:golang:%v,%+v,%#v区别


下一篇:完全背包问题(DP)