多线程—ReentrantLock

你可能觉得很陌生,那我就把它和synchronized比较一下!

与synchronized区别:

不同点:

  • 可中断,运行代码块时,可以中断。

  • 可以设置超时时间,例如:等待一段时间之后,还没有获取到锁,那么就放弃等待,去执行其他的任务代码。

  • 可以设置为公平锁,可以防止线程饥饿。

  • 支持多个条件变量,相当于有多个WaitSet等待队列,根据等待条件的不同而区分。

相同点:

  • 都支持可重入

基本用法:

//创建一个ReentrantLock对象
ReentrantLock reentrantLock= new ReentrantLock();
//代码块
reentrantLock.lock();
try{
    //临界区
}finally{
 reentrantLock.unlock();   
}

1、ReentrantLock的锁重入

概念:如果一个线程首次获得了这把锁,那么它就是该锁的owner,因此可以再次获取这把锁。

(如果不允许锁重入,那么第二次获得锁时,自己也会被锁住。)

代码演示:

@Slf4j
public class ReentrantLockTest {
    private static ReentrantLock reentrantLock= new ReentrantLock();
    public static void main(String[] args) {
        reentrantLock.lock();
        try{
            //临界区
            log.debug("主线程lock临界区");
            //主线程获取锁之后,成为ReentrantLock锁的owner,同时调用方法1,再次获取ReentrantLock锁
            menthod1();
        }finally {
            reentrantLock.unlock();
        }
    }
    private static void menthod1() {
        reentrantLock.lock();
        try{
            log.debug("m1的lock临界区");
            //方法1中调用方法2,方法2再次获取ReentrantLock锁
            menthod2();
        }finally {
            reentrantLock.unlock();
        }
    }
    private static void menthod2() {
        reentrantLock.lock();
        try{
            log.debug("m2的lock临界区");
        }finally {
            reentrantLock.unlock();
        }
    }
}

测试结果:可以全部输出,因此ReentrantLock可以锁重入。

2、ReentrantLock的可打断

概念:当线程A获取锁时有竞争,线程A就进入阻塞队列,但是可以被其它线程用interrupt()方法打断。

看一个例子:

@Slf4j
public class ReentrantLockTest1 {
    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            //调用可打断的方法

            try {
                log.debug("尝试获取锁");
                reentrantLock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("没有获取到锁");
                return;
            }
            try{
                log.debug("获取到锁");
            }finally {
                reentrantLock.unlock();
            }
        });
        //主线程占用锁
        reentrantLock.lock();
        t.start();
        //打断t线程的等待
        t.interrupt();
    }
}

测试结果如下:

 在线程t调用start()方法之前,主线程已经获取了reentrantLock锁,那么线程t只能等待,但是主线程调用了t线程的interrupt()方法,打断了t线程的等待,此时t线程没有获取到锁,并且抛出了异常。这种方式不让线程无限制的等待下去,一定程度的避免了死锁。

3、锁超时

概念:线程获取锁时,设置一个时间限制,如果在时间限制之内获得了锁;则获取成功,如果获取超时,则获取失败。

基本用法:

//创建锁
ReentrantLock reentrantLock = new ReentrantLock();、
//获取锁
try{
    //方法一:立刻获取锁,获取不到直接返回
   if(!reentrantLock.tryLock()){} 
    //方法二:在等待的时间范围内获取锁,等待超时就返回
   if(!reentrantLock.tryLock(时间,时间单位)){}
}catch (InterruptedException e) {
     e.printStackTrace();
     return;//没有获取到锁就直接返回,不执行后面的代码
  }
try{
  	//获取到锁才会释放锁
}finally{
 	//释放锁
    reentrantLock.unlock();
}

实例演示:

@Slf4j
public class ReentrantLockTest2 {
    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
           log.debug("获取锁");
            try {
                //使用方法二:在设置的等待时间范围内获取锁
                if (!reentrantLock.tryLock(2, TimeUnit.SECONDS)){
                    log.debug("获取不到锁");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                //超过等待时限,也没有获取到锁,也需要返回
                return;
            }
            try{
               log.debug("获取到了锁");
           }finally {
               reentrantLock.unlock();
           }
        });
        //主线程获取锁
        reentrantLock.lock();
        t.start();
        sleep(1000);
        //主线程释放了锁
        reentrantLock.unlock();
    }
}

测试结果:

  可以看到,t线程获取锁等待时间是2秒,主线程在获取锁之后等待一秒就释放了,所以t线程可以获取到锁。

利用锁超时解决哲学家就餐问题

@Slf4j
public class Thread1 {
    public static void main(String[] args) throws InterruptedException {
        //筷子对象
        Chopsticks c1 = new Chopsticks("c1");
        Chopsticks c2 = new Chopsticks("c2");
        Chopsticks c3 = new Chopsticks("c3");
        Chopsticks c4 = new Chopsticks("c4");
        Chopsticks c5 = new Chopsticks("c5");
        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();
    }
}
//筷子
class  Chopsticks extends ReentrantLock {
    private String name;

    public Chopsticks(String name) {
        this.name = name;
    }
}
//哲学家
@Slf4j
class Philosopher extends Thread{
    //名字
    private String name;
    //筷子
    private Chopsticks left;
    private Chopsticks right;

    public Philosopher(String name, Chopsticks left, Chopsticks right) {
        super(name);
        this.name = name;
        this.left = left;
        this.right = right;
    }
    @SneakyThrows
    @Override
    public void run() {
        while(true){
            //获取左手的筷子
            if (left.tryLock()){
               try{
                   //获取右手的筷子
                   if (right.tryLock()){
                       try{
                           eat(name);
                       }finally {
                           right.unlock();
                       }
                   }
               }finally {
                   //吃饭饭放下筷子,或者只拿到左手的筷子而没拿到右手的筷子都要释放锁
                   left.unlock();
               }
            }
        }
    }
    private void eat(String name) throws InterruptedException {
        log.debug(name + "正在吃饭");
        sleep(1000);
    }
}

这样就不会造成死锁,同时也避免了线程饥饿问题。

测试结果:

                

4、条件变量

概念:使用ReentrantLock时,当线程获取到锁之后,执行同步代码块时有条件不成立,需要等待时,就会进入到像synchronized的WaitSet的地方等待,但是ReentrantLock会更加强大,因为它的条件变量会更多,让线程在多个地方等待,唤醒线程时,也是按照等待的地方来唤醒的。

基本使用:

//创建锁
private static ReentrantLock reentrantLock = new ReentrantLock();
//创建条件变量
Condition c = reentrantLock.newCondition();
//要先加锁
reentrantLock.lock();
//调用Condition的await()方法 等待
c.await();
//唤醒
c.signal();//唤醒一个
c.signalAll();//唤醒条件变量c中的全部

注意事项:

  • await()之前需要获得锁

  • await()执行之后会释放锁

  • await()等待的线程被唤醒、或者打断、或者超时,去重新竞争锁。

  • 竞争ReentrantLock成功之后,从await()方法后继续执行。

​​​​​​​

猜你喜欢

转载自blog.csdn.net/qq_42251944/article/details/120891452
今日推荐