ReentrantLock 可重入锁

ReentrantLock 可重入锁

1.特点

相对于 synchronized 它具备如下特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁(先进先出)构造参数为true表示公平锁。
  • 支持多个条件变量(休息室)(条件不满足时进入waitSet等待)
    与 synchronized 一样,都支持可重入。

2.基本语法

// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
} 

3.可重入

可重入指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。

static ReentrantLock lock = new ReentrantLock();

@Test
public void testReentrantLock() {
    lock.lock();
    try {
        log.debug("进入main方法");
        m1();   //锁重入
    } finally {
        lock.unlock();
    }
}

public static void m1() {
    lock.lock();
    try {
        log.debug("进入m1方法");
        m2();  //锁重入
    } finally {
        lock.unlock();
    }
}

public static void m2() {
    lock.lock();
    try {
        log.debug("进入m2方法");
    } finally {
        lock.unlock();
    }
}

在这里插入图片描述

4.可打断

reentrantLock的lock(); 是不可以打断的。lockInterruptibly();是可以打断的。

/**
 * 可打断
 */
@Test
public void t2() {
    Thread t1 = new Thread(() -> {
        //如果没有竞争,则使用次方法获得lock对象锁
        //如果有竞争,则进入阻塞队列,可以被其他线程的interrupt方法打断
        try {
            log.debug("尝试获得锁");
            lock2.lockInterruptibly();
        } catch (InterruptedException e) {
            e.printStackTrace();
            log.debug("没有获得锁,返回");
            return; //表示被打断,没有获得锁
        }
        try {
            log.debug("获取到锁");
        } finally {
            lock2.unlock();
        }
    }, "t1");
    //主线程加锁
    lock2.lock();
    t1.start();
    //主线程谁3秒后打断
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    t1.interrupt();
    try {
        t1.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

在这里插入图片描述

5.锁超时

没有参数的tryLock()

/**
 * 锁超时
 * trylock()
 */
static ReentrantLock lock3 = new ReentrantLock();

@Test
public void t3() {
    Thread t1 = new Thread(() -> {
        //尝试获得锁
        if (!lock3.tryLock()) {  //如果没有获得锁
            log.debug("获取锁,立刻失败,返回");
            return;
        }
        try {
            log.debug("获得了锁");
        } finally {
            lock3.unlock();
        }
    }, "t1");
    //先让主线程获得锁
    lock3.lock();
    log.debug("获得了锁");
    //t1启动后不能获得锁
    t1.start();

    //主线程2秒后释放锁
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock3.unlock();
    }

    try {
        t1.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

在这里插入图片描述

带参数的tryLock(long n,TimeUnit.单位)

/**
 *  tryLock(2, TimeUnit.SECONDS)
 */
@Test
public void t4(){
    Thread t1 = new Thread(() -> {
        //尝试获得锁
        try {
            if (!lock3.tryLock(1, TimeUnit.SECONDS)) {  //如果没有获得锁
                log.debug("获取锁,立刻失败,返回");
                return;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            log.debug("获得了锁");
        } finally {
            lock3.unlock();
        }
    }, "t1");
    //先让主线程获得锁
    lock3.lock();
    log.debug("获得了锁");
    //t1启动后不能获得锁
    t1.start();

    //主线程2秒后释放锁
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock3.unlock();
    }

    try {
        t1.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

在这里插入图片描述

6.使用锁超时解决哲学家就餐问题

package com.concurrent.p4;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

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

@Slf4j(topic = "c.TestQuestionPhilosopher2")
public class TestQuestionPhilosopher2 {
    @Test
    public void test1() {
        Chopstick2 c1 = new Chopstick2("1");
        Chopstick2 c2 = new Chopstick2("2");
        Chopstick2 c3 = new Chopstick2("3");
        Chopstick2 c4 = new Chopstick2("4");
        Chopstick2 c5 = new Chopstick2("5");
        new Philosopher2("苏格拉底", c1, c2).start();
        new Philosopher2("柏拉图", c2, c3).start();
        new Philosopher2("亚里士多德", c3, c4).start();
        new Philosopher2("赫拉克利特", c4, c5).start();
        new Philosopher2("阿基米德", c5, c1).start();
        while (true) ;
    }
}

@Slf4j(topic = "c.Philosopher2")
class Philosopher2 extends Thread {
    private String name;
    private Chopstick2 left;
    private Chopstick2 right;

    public Philosopher2(String name, Chopstick2 left, Chopstick2 right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    public void eat() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("eat...");
    }

    @Override
    public void run() {
//        while (true) {
//            try {
//                left.tryLock(1, TimeUnit.SECONDS);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//                return;
//            }
//            try {
//                right.tryLock(1, TimeUnit.SECONDS);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//                return;
//            }
//            try {
//                eat();
//            } finally {
//                left.unlock();
//                right.unlock();
//            }
//        }
        while (true) {
            if (left.tryLock()) {      //尝试获取左筷子
                try {
                    if (right.tryLock()) {      //尝试获取右筷子
                        try {
                            eat();  //都获取到开始eat
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();  //***没有获取到right,则会释放left
                }
            }
        }
    }
}

class Chopstick2 extends ReentrantLock {
    private String name;

    public Chopstick2(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Chopstick2{" +
                "name='" + name + '\'' +
                '}';
    }
}

在这里插入图片描述

7.公平锁

ReentrantLock默认是不公平的。

public ReentrantLock(boolean fair) {  //如果fari=true,则为公平锁
    sync = fair ? new FairSync() : new NonfairSync();
}

公平锁:按照进入阻塞队列的顺序,按照先进先出的顺序执行。
公平锁用来解决饥饿问题,但是没必要。一般使用tryLock()。
公平锁会降低并发程度。

8.条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比

  • synchronized 是那些不满足条件的线程都在一间休息室等消息
  • ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒。

使用要点:

  • await 前需要获得锁。
  • await 执行后,会释放锁,进入 conditionObject 等待。
  • await 的线程被唤醒(或打断、或超时)会重新竞争 lock 锁。
  • 竞争 lock 锁成功后,从 await 后继续执行。

条件变量例子

static ReentrantLock lock = new ReentrantLock();

@Test
public void test1() {
    //创建新的条件变量(休息室)
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();

    Thread t1 = new Thread(() -> {
        try {
            lock.lock();
            try {
                log.debug("开始等待");
                //进入休息室等待
                condition1.await();
                log.debug("结束等待");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            lock.unlock();
        }

    }, "t1");
    t1.start();

    Thread t2 = new Thread(() -> {
        //t2线程5秒后唤醒休息室中的线程t1
        try {
            lock.lock();
            try {
                log.debug("休眠3秒...");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("唤醒休息室1中的线程");
            condition1.signal();
        } finally {
            lock.unlock();
        }
    }, "t2");
    t2.start();

    try {
        t1.join();
        t2.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

在这里插入图片描述

9.用ReentrantLock改写送烟送外卖代码

package com.concurrent.p4;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

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

/**
 * 用ReentrantLock改写送烟,送外卖的例子
 */

@Slf4j(topic = "c.TestReentrantLockExample")
public class TestReentrantLockExample {
    //static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    //定义ReentrantLock对象,true表示该锁为公平锁
    static ReentrantLock lock = new ReentrantLock(true);
    //定义两个条件变量
    //吸烟室
    static Condition smokingRoom = lock.newCondition();
    //用餐室
    static Condition launchRoom = lock.newCondition();

    @Test
    public void test1() throws InterruptedException {
        //小南线程
        Thread t1 = new Thread(() -> {
            try {
                lock.lock();
                log.debug("有烟没?[{}]", hasCigarette);
                //将if改为while多次判断
                while (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    //没有烟就在吸烟室等待,此时可以释放锁
                    try {
                        smokingRoom.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没烟干不了");
                }
            } finally {
                lock.unlock();
            }

        }, "小南");
        t1.start();

        //小女线程
        Thread t2 = new Thread(() -> {
            try {
                lock.lock();
                log.debug("有外卖没?[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("没外卖,先歇会!");
                    //没有外卖就在用餐室等待,此时可以释放锁
                    try {
                        launchRoom.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有外卖没?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以开始干活了");
                } else {
                    log.debug("没外卖干不了");
                }
            } finally {
                lock.unlock();
            }
        }, "小女");
        t2.start();


        //送外卖的
        Thread t3 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                lock.lock();
                hasTakeout = true;
                log.debug("外卖到了");
                launchRoom.signalAll();
            } finally {
                lock.unlock();
            }
        }, "送外卖的");
        t3.start();

        t1.join();
        t2.join();
        t3.join();
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值