双非本科准备秋招(21.2)—— ReentrantLock

一、vs synchronized

  • 中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量

语法

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

二、可重入

连续三次上锁。

@Slf4j(topic = "c.test")
public class Reentrant {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        lock.lock();
        try {
            m1();
            log.debug("上锁");
        } finally {
            lock.unlock();
        }
    }
    
    public static void m1(){
        lock.lock();
        try {
            log.debug("上锁");
            m2();
        } finally {
            lock.unlock();
        }
    }
    
    public static void m2(){
        lock.lock();
        try {
            log.debug("上锁");
        } finally {
            lock.unlock();
        }
    }
}

可以发现,上锁都成功了。

双非本科准备秋招(21.2)—— ReentrantLock_第1张图片

三、可打断

        使用lockInterruptibly方法,如果无竞争那么就会获得lock锁;有有竞争会进入阻塞队列,可以被其他线程用interrupt方法打断。

@Slf4j(topic = "c.test")
public class Reentrant {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("启动");
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("我被打断了");
                return;
            }

            try {
                log.debug("获得了锁");
            } finally {
                //释放锁
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        t1.start();
        try {
            Thread.sleep(1000);
            t1.interrupt();
        }
        finally {
            lock.unlock();
        }
    }
}

双非本科准备秋招(21.2)—— ReentrantLock_第2张图片

        如果用lock方法那么是无法被打断的,会一直等待。

四、锁超时

        使用trylock方法,尝试获得锁,如果能获得锁就获得,返回true,否则返回false。

立即失败

@Slf4j(topic = "c.test")
public class Reentrant {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("启动");
            if(!lock.tryLock()){
                log.debug("没获得锁!");
                return;
            }

            try {
                log.debug("获得了锁");
            } finally {
                //释放锁
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        t1.start();
        try {
            Thread.sleep(1000);
        }
        finally {
            lock.unlock();
        }
    }
}

双非本科准备秋招(21.2)—— ReentrantLock_第3张图片

超时失败:

lock.tryLock(1, TimeUnit.SECONDS),第一个参数是数字,第二个参数是单位

@Slf4j(topic = "c.test")
public class Reentrant {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("启动");
            try {
                if(!lock.tryLock(1, TimeUnit.SECONDS)){
                    log.debug("没获得锁!");
                    return;
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }

            try {
                log.debug("获得了锁");
            } finally {
                //释放锁
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        t1.start();
        try {
            Thread.sleep(1000);
        }
        finally {
            lock.unlock();
        }
    }
}

锁超时可以用来优化哲学家问题:让Chopstick继承ReentrantLock,然后使用tryLock优化。

五、公平锁

可以加一个boolean参数,true代表公平锁,默认是不公平的。

ReentrantLock lock = new ReentrantLock(true);

但是一般不用,公平锁会降低并发度

六、条件变量

        ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的。

        wait和notify的模板语法中,每次都要notifyAll,然后所有的wait线程都被唤醒,如果条件没满足,就是虚假唤醒。

        但是使用了多个条件变量,我们就不需要盲目地唤醒所有线程了。

        使用newCondition()创建一个条件变量,这个案例中模拟两个线程,一个线程需要吃苹果才能工作,一个线程需要卫生纸才能工作。

    private static ReentrantLock lock = new ReentrantLock();
    static Condition hasApple = lock.newCondition();
    static Condition hasTissue = lock.newCondition();
    static boolean apple = false;
    static boolean tissue = false;

线程1:hasApple.await();方法,可以让当前线程进入等待。

        new Thread(()->{
            try {
                lock.lock();
                while(!apple){
                    try {
                        hasApple.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("苹果来了,开始干活");
            }
            finally {
                lock.unlock();
            }
        }, "APPLE").start();

还需要一个唤醒线程,这里就可以看出来条件变量的优势,只需要唤醒hasApple中的线程即可,不会导致hasTissue中的线程被虚假唤醒。

    private static void sendApple(){
        lock.lock();
        try {
            log.debug("送苹果来了");
            apple = true;
            hasApple.signal();
        }
        finally {
            lock.unlock();
        }
    }

完整代码:

@Slf4j(topic = "c.test")
public class Reentrant {
    private static ReentrantLock lock = new ReentrantLock();
    static Condition hasApple = lock.newCondition();
    static Condition hasTissue = lock.newCondition();
    static boolean apple = false;
    static boolean tissue = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            try {
                lock.lock();
                while(!apple){
                    try {
                        hasApple.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("苹果来了,开始干活");
            }
            finally {
                lock.unlock();
            }
        }, "APPLE").start();

        new Thread(()->{
            try {
                lock.lock();
                while(!tissue){
                    try {
                        hasTissue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("卫生纸来了,开始干活");
            }
            finally {
                lock.unlock();
            }
        }, "TISSUE").start();

        Thread.sleep(1000);
        sendApple();
        Thread.sleep(2000);
        sendTissue();
    }

    private static void sendApple(){
        lock.lock();
        try {
            log.debug("送苹果来了");
            apple = true;
            hasApple.signal();
        }
        finally {
            lock.unlock();
        }
    }

    private static void sendTissue(){
        lock.lock();
        try {
            log.debug("送卫生纸来了");
            tissue = true;
            hasTissue.signal();
        }
        finally {
            lock.unlock();
        }
    }
}

输出:

双非本科准备秋招(21.2)—— ReentrantLock_第4张图片

你可能感兴趣的:(ReentrantLock,并发编程,秋招,求职,锁,条件变量,可重入)