显示锁Lock

Lock简单介绍

public class LockDemo {

    // 声明一个lock锁
    private Lock lock = new ReentrantLock();

    private int count;

    // 用lock来加锁
    public void increament01() {
        // 加锁
        lock.lock();
        try {
            count++;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    // syn关键字加锁
    public synchronized void increament02() {
        count++;
    }

}

释放锁的代码应当放在finally关键字中,以保证代码出现异常后,锁能够及时的释放掉。

syn和lock的比较

  1. syn称之为内置锁,因为它是一个关键字,在jdk中没有源码,经过多年优化,性能很优秀,并且代码较为简洁,但较lock有局限性。
  2. lock称之为显示锁,在获取锁的过程中可以被中断,并且还可以超时获取锁,和尝试加锁,syn则没有这三个特点。
  3. syn和lock都是可重入锁。
  4. ReentrantLock和syn关键字都是排他锁,即在同一个时刻只允许一个线程访问。

总结:如果没有第二条中的业务场景的时候,应该用syn关键字。

公平锁和非公平锁

如果在时间上,先对锁进行获取的请求,一定先被满足,这个锁就是公平的,如果不满足,这个锁就不非公平的,一般来说,非公平锁效率相对较高。

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

源码看出,lock默认是非公平的锁。

Lock和Condition

举例说明:

下面是一个快递类:一个lock下面可以有多个condition,因为需要监控两个条件的变化,所以定义了两个condition来锁这两个变量。lock锁不会自动释放,需要手动释放。使线程进入等待状态的方法不是object的wait方法,而是condition接口中的await方法,唤醒线程的方法也是condition接口中的signal和signalAll,这点很重要。

public class Express {
    // 声明锁
    private Lock lock = new ReentrantLock();
    // 公里变化锁
    private Condition kmCondition = lock.newCondition();
    // 地点变化锁
    private Condition siteCondition = lock.newCondition();
    // 始发地
    private final static String CITY = "ShangHai";
    // 里程变化
    private int km;
    // 地点变化
    private String site;

    Express() {

    }

    Express(int km, String site) {
        this.km = km;
        this.site = site;
    }

    // 里程数变化,会唤起线程
    public void changeKm() {
        lock.lock();
        try {
            this.km = 101;
            // 唤醒
            kmCondition.signal();
        } finally {
            lock.unlock();
        }
    }

    // 地点变化会唤起线程
    public void changeSite() {
        lock.lock();
        try {
            this.site = "BeiJing";
            // 唤醒
            siteCondition.signal();
        } finally {
            lock.unlock();
        }
    }

    // 用来监听里程数的变化
    public void waitKm() {
        lock.lock();
        try {
            while (this.km <= 100) {
                System.out.println(Thread.currentThread().getId() + "监控【公里数】变化的线程即将进入等待状态。。。");
                kmCondition.await();
                System.out.println(Thread.currentThread().getId() + "-号监听===里程变化===的线程被唤醒了。。。");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println(Thread.currentThread().getId() + "释放了锁。");
        }
        System.out.println(Thread.currentThread().getId() + "-号监听===里程变化===的线程去做相应的事了");
    }

    // 用来监听地点的变化
    public void waitSite() {
        lock.lock();
        try {
            while (CITY.equals(this.site)) {
                System.out.println(Thread.currentThread().getId() + "监控【地点】变化的线程即将进入等待状态。。。");
                siteCondition.await();
                System.out.println(Thread.currentThread().getId() + "-号监听===地点变化===的线程被唤醒了。。。");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println(Thread.currentThread().getId() + "释放了锁。");
        }
        System.out.println(Thread.currentThread().getId() + "-号监听===地点变化===的线程去做相应的事了");
    }

}

main方法测试类:

分别启动三个监控地点变化的线程和三个监控公里数变化的线程,并使它们都进入等待状态,然后改变地点变化。调用await方法后,每个被阻塞的线程都会加到队列末尾排队。

public class Test {
    // 初始化快递
    private static Express express = new Express(0, "ShangHai");
    // 用来监听里程数变化的线程
    static class CheckKm implements Runnable {
        public void run() {
            express.waitKm();
        }
    }

    // 用来监听地点变化的线程
    static class CheckSite implements Runnable {
        public void run() {
            express.waitSite();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 启动三个线程去监听里程数的变化
        for (int i = 0; i <= 2; i++) {
            new Thread(new CheckKm()).start();
        }

        // 启动三个线程去监听地点的变化
        for (int i = 0; i <= 2; i++) {
            new Thread(new CheckSite()).start();
        }

        // 主线程睡眠一秒,异常信息抛出去
        Thread.sleep(1000);

        // 让快递的地点发生变化
        express.changeSite();
    }
}
  1. 首先使用的是用signal来唤醒线程:唤醒在此Lock对象上等待的单个线程。
11监控【公里数】变化的线程即将进入等待状态。。。
13监控【公里数】变化的线程即将进入等待状态。。。
14监控【地点】变化的线程即将进入等待状态。。。
15监控【地点】变化的线程即将进入等待状态。。。
12监控【公里数】变化的线程即将进入等待状态。。。
16监控【地点】变化的线程即将进入等待状态。。。
14-号监听===地点变化===的线程被唤醒了。。。
14释放了锁。
14-号监听===地点变化===的线程去做相应的事了

signal是唤醒当前阻塞队列中的第一个,而不是随机唤醒。

  1. 使用signalAll来唤醒线程:唤醒在此Lock对象上等待的所有线程。
11监控【公里数】变化的线程即将进入等待状态。。。
15监控【地点】变化的线程即将进入等待状态。。。
13监控【公里数】变化的线程即将进入等待状态。。。
12监控【公里数】变化的线程即将进入等待状态。。。
14监控【地点】变化的线程即将进入等待状态。。。
16监控【地点】变化的线程即将进入等待状态。。。
15-号监听===地点变化===的线程被唤醒了。。。
15释放了锁。
15-号监听===地点变化===的线程去做相应的事了
14-号监听===地点变化===的线程被唤醒了。。。
14释放了锁。
14-号监听===地点变化===的线程去做相应的事了
16-号监听===地点变化===的线程被唤醒了。。。
16释放了锁。
16-号监听===地点变化===的线程去做相应的事了

signalAll唤醒所有当前队列中的等待线程。

原文地址:https://www.cnblogs.com/zhangjianbing/p/13574812.html