其他分享
首页 > 其他分享> > 并发学习记录07:ReentrantLock

并发学习记录07:ReentrantLock

作者:互联网

特点

相比于synchronized,ReentrantLock具有可中断,可以设置超时时间,可以设置为公平锁,支持多个条件变量的特点,它和synchronized一样,都支持可重入

基本语法

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

可重入

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

可重入验证代码

@Slf4j(topic = "ch.ReentrantLockTest01")
public class ReentrantLockTest01 {
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        method1();
    }

    public static void method1() {
        lock.lock();
        try {
            log.debug("method1");
            //验证可重入
            method2();
        } finally {
            lock.unlock();
        }
    }

    public static void method2() {
        lock.lock();
        try {
            log.debug("method2");
            method3();
        } finally {
            lock.unlock();
        }
    }

    public static void method3() {
        lock.lock();
        try {
            log.debug("method3");
        } finally {
            lock.unlock();
        }
    }
}

可打断

可中断锁是指抢占过程可以被中断的锁,如下:

@Slf4j(topic = "ch.ReentrantLockTest02")
public class ReentrantLockTest02 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("启动");
            try {
                //可打断的上锁
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("被打断");
                return;
            }
            log.debug("上锁成功");
            lock.unlock();
        }, "t1");

        lock.lock();
        log.debug("获得了锁");
        t1.start();
        Thread.sleep(1);
        log.debug("执行打断");
        t1.interrupt();
        lock.unlock();
    }
}

锁超时

其实这里用了tryLock方法实现了,tryLock有两种使用方法,一种是直接tryLock,返回值是Boolean对象,表示的是用来尝试获取锁,如果成功就返回true,如果失败就返回false,这个方法无论成功失败都会立刻返回。tryLock还有一个重载方法public boolean tryLock(long timeout, TimeUnit unit),表示在这个时间范围内成功获取了锁返回true,没获得就返回false。实例如下:

//测试锁超时
@Slf4j(topic = "ch.ReentrantLockTest03")
public class ReentrantLockTest03 {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("启动");
            if (!lock.tryLock()) {
                log.debug("获取锁失败,返回");
                return;
            }
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        log.debug("主线程获取了锁");
        lock.lock();
        t1.start();
        try {
            Thread.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

使用tryLock解决哲学家就餐问题

//使用tryLock解决哲学家就餐问题
@Slf4j(topic = "ch.ReentrantLockTest04")
public class ReentrantLockTest04 {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1", true);
        Chopstick c2 = new Chopstick("2", true);
        Chopstick c3 = new Chopstick("3", true);
        Chopstick c4 = new Chopstick("4", true);
        Chopstick c5 = new Chopstick("5", true);
        new Philosopher("哲学家1", c1, c2).start();
        new Philosopher("哲学家2", c2, c3).start();
        new Philosopher("哲学家3", c3, c4).start();
        new Philosopher("哲学家4", c4, c5).start();
        new Philosopher("哲学家5", c5, c1).start();
    }
}

@Slf4j(topic = "ch.Chopstick")
class Chopstick extends ReentrantLock {
    String name;

    public Chopstick(String name, boolean fair) {
        super(fair);
        this.name = name;
    }

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

@Slf4j(topic = "ch.Philosopher")
class Philosopher extends Thread {
    Chopstick left;
    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 {
                    left.unlock();
                }
            }
        }
    }

    private void eat() {
        log.debug("eating....");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

公平锁

ReentrantLock 默认是不公平的,公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

ReentrantLock的两个构造方法:

// 默认非公平
public ReentrantLock() {
  sync = new NonfairSync();
}

// 根据传参来实现公平或非公平锁
public ReentrantLock(boolean fair) {
	sync = fair ? new FairSync() : new NonfairSync();
}

条件变量

synchronized中也有条件变量,起的作用类似于synchronized中的那个waitSet休息室,当条件不满足时,进入waitSet等待

ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
*synchronized的那些不满足条件的线程都在一间休息室等消息
*而 ReentrantLock 支持多间休息室,可以按照不同的资源等待条件new不同的“休息室”,唤醒时也是按照不同的条件来唤醒的

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

标签:07,lock,ReentrantLock,Chopstick,并发,debug,new,public
来源: https://www.cnblogs.com/wbstudy/p/16609668.html