API及性质-ReentrantLock入门-并发编程(Java)

1、可重入

可重入是指同一线程如果首次获得了这把锁,那么因为是这把锁的拥有者,因此有权利再次获取这边锁。

/**
 * 测试ReentrantLock可重入性
 */
@Slf4j(topic = "c.TestReentrant")
public class TestReentrant {
   static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        method1();
    }

    public static void method1() {
        log.debug("执行方法1");
        try {
            lock.lock();
            log.debug("获得锁,调用方法2");
            method2();
        }finally {
            lock.unlock();
        }
    }
    public static void method2() {
        log.debug("执行方法2");
        try {
            lock.lock();
            log.debug("获得锁");
        }finally {
            lock.unlock();
        }
    }
}

2、可打断

可打断指在线程等待过程中,可以被打断。

  • 方法:lock.lockInterruptibly() 尝试获取锁,如果获取不到锁,进入等待;等待过程中可以被打断。
/**
 * 测试ReentrantLock可打断性
 */
@Slf4j(topic = "c.TestInterruptible")
public class TestInterruptible {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            try {
                // 尝试获取锁
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("获取锁失败,退出.");
                return;
            }
            try {
                log.debug("获取到锁,执行后续代码...");
            } finally {
                lock.unlock();
            }
        }, "t1");

        // 主线程先获取了锁
        lock.lock();

        t1.start();
        TimeUnit.SECONDS.sleep(1);
        log.debug("打断t1线程...");
        t1.interrupt();
    }
}

当使用如果获取锁调用的是lock.lock(),那么线程等待不会被打断,

/**
 * 测试ReentrantLock可打断性
 */
@Slf4j(topic = "c.TestInterruptible")
public class TestInterruptible {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
//            try {
//                // 尝试获取锁
//                lock.lockInterruptibly();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//                log.debug("获取锁失败,退出.");
//                return;
//            }

            try {
                // 尝试获取锁
                lock.lock();
                log.debug("获取到锁,执行后续代码...");
            } finally {
                lock.unlock();
            }
        }, "t1");

        // 主线程先获取了锁
        lock.lock();

        t1.start();
        TimeUnit.SECONDS.sleep(1);
        log.debug("打断t1线程...");
        t1.interrupt();
    }
}

3、锁超时

可打断能避免一直等待,防止死锁。锁超时呢,是主动性的。线程尝试获取锁,如果获取不到锁,不会一直等待。

  • 方法:lock.tryLock()

  • 方法:lock.tryLock(long timeout, TimeUnit unit)

    • timeout :超时时间
    • unit:时间单位
  • 测试无参tryLock()

    /**
     * 测试ReentrantLock 锁超时-tryLock无参
     */
    @Slf4j(topic = "c.TestTryLock01")
    public class TestTryLock01 {
        public static void main(String[] args) {
            ReentrantLock lock = new ReentrantLock();
            Thread t1 = new Thread(() -> {
                if(!lock.tryLock()) {
                    log.debug("没有获取锁,退出");
                    return;
                }
                try {
                    log.debug("获取到锁,执行后续代码。。。");
                }finally {
                    lock.unlock();
                }
            }, "t1");
    
            // 主线程先获取了锁
            lock.lock();
    
            t1.start();
        }
    }
    
    
  • 测试带参tryLock

    /**
     * 测试ReentrantLock 锁超时-tryLock带参
     */
    @Slf4j(topic = "c.TestTryLock02")
    public class TestTryLock02 {
        public static void main(String[] args) throws InterruptedException {
            ReentrantLock lock = new ReentrantLock();
            Thread t1 = new Thread(() -> {
                try {
                    if(!lock.tryLock(1, TimeUnit.SECONDS)) {
                        log.debug("没有获取锁,退出");
                        return;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    log.debug("获取锁失败,退出");
                    return;
                }
                try {
                    log.debug("获取到锁,执行后续代码。。。");
                }finally {
                    lock.unlock();
                }
            }, "t1");
    
            // 主线程先获取了锁
            lock.lock();
            Thread.sleep(100);
            t1.start();
        }
    }
    
    

4、锁超时解决哲学家进餐问题

  • 分析:

之前之所以会出现死锁现象,因为线程在获取另外一根筷子(锁)时,如果获取失败,就会无限等待;这里我们用tryLock超时等待方式,线程在获取另外一根筷子(锁)时,如果获取失败,不等待或者有时限的等待,同时放弃已经获取的筷子;

/**
 * 筷子类
 */
@Data
@AllArgsConstructor
public class Chopstick  extends ReentrantLock {
    /** 筷子名称 */
    private String name;
}

/**
 * 哲学家类
 */
@Slf4j(topic = "c.Philosopher")
@Data
public class Philosopher extends Thread{
    /** 左手边筷子名称 */
    private Chopstick left;
    /** 右手边筷子名称 */
    private Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            // 尝试获取左手边的筷子
            if (left.tryLock()) {
                try {
                    // 尝试获取右手边的筷子
                    if (right.tryLock()) {
                        try {
                            // 拿到了一双筷子,开始吃饭
                            eat();
                        } finally {
                            // 然后释放锁
                            right.unlock();
                        }
                    }
                }finally {
                    // 1、正常执行,吃饭后放下筷子
                    // 2、获取右手边筷子失败,那么就放下已经获取到的左筷子,防止死锁
                    left.unlock();
                }
            }
        }
    }

    /**
     * 吃饭
     */
    private void eat() {
        log.debug("eating ...");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 测试类
 */
@Slf4j(topic = "c.TestPhilosopherDinner")
public class TestPhilosopherDinner {
    public static void main(String[] args) {
        // 创建5根筷子
        Chopstick c1 = new Chopstick("筷子1");
        Chopstick c2 = new Chopstick("筷子2");
        Chopstick c3 = new Chopstick("筷子3");
        Chopstick c4 = new Chopstick("筷子4");
        Chopstick c5 = new Chopstick("筷子5");
        // 创建5个哲学家线程
        new Philosopher("苏格拉底", c1, c2).start();
        new Philosopher("柏拉图", c2, c3).start();
        new Philosopher("亚里士多德", c3, c4).start();
        new Philosopher("赫拉克利特", c4, c5).start();
        new Philosopher("阿基米德", c5, c1).start();
    }
}

5、公平锁与不公平锁

公平:在等待队列中等待的线程,重新竞争锁对象的时候,如果遵循先来来获取,就是公平的;否则就是不公平的。

  • 在Monitor EntrList中线程竞争锁对象非公平的;

  • ReentrantLock 锁默认也是不公平的;在构造函数中设置布尔参数值为true,为公平锁

    // 源码ReentrantLock 构造方法
    // 无参
    public ReentrantLock() {
            sync = new NonfairSync();
        }
        
     // 有参   
    public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    

    关于底层具体怎么实现公平与不公平,我们后续详解。

6、条件变量

ReentrantLock的条件变量比synchronized强大支出在于,它支持多个条件变量(对象)。

  • synchronized那些不满足条件的线程进入一个等待队列
  • 而ReentrantLock支持多个条件变量对象(队列);线程可根据情况选择进入那个队列等待;同样根据条件唤醒在不同等待队列中等待的线程。

过程:

  • 等待状态的线程获取锁对象。
  • 因为不满足某个条件,进入ConditionObject等待
  • ConditionObject等待的线程被唤醒(或打断或超时),重新竞争锁对象

相关对象和方法

  • 对象:Condition 条件变量,由ReentrantLock对象创建
  • 方法:condition.await()/condition.await(long tiomout) 进入条件变量等待
  • 方法:condition.signal()/condition.signalAll() 唤醒在条件变量上等等的线程

示例:以之前工地施工为例,李师傅等电接通;王师傅等柴油继续开卡车为例

@Slf4j(topic = "w.TestCondition")
public class TestCondition {

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock room = new ReentrantLock();
        // 是否又电
        AtomicBoolean hasElectric = new AtomicBoolean(false);
        // 是否有柴油
        boolean hasDiesel = false;
        // 等电的休息室
        Condition electric = room.newCondition();
        // 等柴油的休息室
        Condition diesel = room.newCondition();
        new Thread(() -> {

            room.lock();
            try {
                log.debug("电有了没?[{}]", hasElectric);
                while (!hasElectric.get()) {
                    log.debug("没电,先歇会!");
                    try {
                        electric.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("电接通了?[{}]", hasElectric);
                if (hasElectric.get()) {
                    log.debug("开始干活!");
                }
            } finally {
                room.unlock();
            }

        }, "李师傅").start();

        new Thread(() -> {

            room.lock();
            try {
                log.debug("柴油送到没?[{}]", hasElectric);
                while (!hasDiesel) {
                    log.debug("柴油没送到,先歇会!");
                    try {
                        diesel.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("柴油送到没?[{}]", hasElectric);
                if (hasDiesel) {
                    log.debug("开始干活!");
                }

            } finally {
                room.unlock();
            }
        }, "王师傅").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            try {
                room.lock();
                hasElectric.set(true);
                log.debug("电接通了!");
                electric.signal();
            } finally {
                room.unlock();
            }
        }, "工人").start();
    }
}

仓库地址:https://gitee.com/gaogzhen/concurrent

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值