Lock接口是JDK1.5新增的,其在JUC的locks包下,比synchronized关键字更强大

  • Lock接口 互斥锁,排他锁
    image.png
    的外部实现类只有ReentrantReadWirteLock,其他的实现类都是在class内部实现的,没有public,也就是说其无法在不同包之间访问!!!
    Segment是ConcurrentHashMap实现的主要原理
  • ReadWriteLock接口 读写锁
    image.png
    注意其实现的接口
    1.ReentrantReadWriteLock类实现了ReadWriteLock
    2.ReentrantReadWriteLock类的内部类WriteLock和ReadLock实现了Lock接口,没有实现ReadWriteLock接口
    提供2个接口方法
  • 读操作:共享锁,可以继续加共享锁
  • 写操作:排他锁,不可继续加任何锁

Lock接口使用ReentrantLock类(互斥锁,拍他锁)

该实现是由Lock接口实现的

基础使用

基础使用非常简单,使用lock和unlock,锁住之间的代码
image.png
image.png

class TestA {
    private Lock lock=new ReentrantLock();
    public void testLock(){
        lock.lock();
        IntStream.iterate(1,vo->vo+=1).limit(4).forEach(vo-> System.out.println(Thread.currentThread().getName()));
        lock.unlock();
    }

    public static void main(String[] args) {
        TestA testA = new TestA();
        new Thread(()->testA.testLock()).start();
        new Thread(()->testA.testLock()).start();
        new Thread(()->testA.testLock()).start();
        new Thread(()->testA.testLock()).start();
    }
}

因为lock在外面,锁住了里面的代码,所以foreach方法不会被分开执行
image.png

Condition接口

synchronized和wait/notify等方法使用可以唤醒线程,但无法唤醒指定的线程,因为其是由JVM执行的。
而Condition接口的对象的存在可以实现多路通知的功能,在一个lock对象中创建多个condition对象实例,线程与condition绑定,可以唤醒指定线程,更加灵活
image.png
image.png

void await() throws InterruptedException;
void signal();

注意使用condition的wait方法前必须使用lock锁,不然会抛InterruptedException

使用Condition的wait/signal方法机制来实现Object的wait/notify方法

image.png
使用lock的newCondition方法返回一个新的condition实例
image.png
注意其Condition必须要作用在一个lock上,因为只signal一次,所以在此wait之后不被唤醒

Unsafe类-awiat原理

通过源码可以看到使用的是Unsafe类的park方法
image.png
image.png
因为unsafe的不安全性,权限过大,所以无法通过AppClassLoader加载得到
image.png
image.png
只能通过反射得到
image.png
与park对应的还有unpark

唤醒指定线程

作用于同一个lock上,但是注册绑定的condition不同,唤醒不同的condition信号即可唤醒指定的线程

//唤醒指定线程
class D {
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();

    public void await1() {
        while (true) {
            lock.lock();
            System.out.println("await1时间" + System.currentTimeMillis());
            try {
                Thread.sleep(1000);
                condition1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
        }
    }

    public void signal1() {
        lock.lock();
        System.out.println("signal1时间" + System.currentTimeMillis());
        condition1.signal();
        lock.unlock();
    }

    public void await2() {
        while (true) {
            lock.lock();
            System.out.println("await2时间" + System.currentTimeMillis());
            try {
                Thread.sleep(1000);
                condition2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
        }
    }

    public void signal2() {
        lock.lock();
        System.out.println("signal2时间" + System.currentTimeMillis());
        condition2.signal();
        lock.unlock();
    }

    public static void main(String[] args) {
        D d = new D();
        Thread thread = new Thread(() -> d.await1());
        Thread thread1 = new Thread(() -> d.await2());
//        Thread thread2 = new Thread(() -> d.signal1());
        Thread thread3 = new Thread(() -> d.signal2());
        thread.start();
        thread1.start();
//        thread2.start();
        thread3.start();
    }
}

image.png

公平锁和非公平锁

  • 公平锁
    先到先得,每次获取锁之前都会检查队列里面有没有排队等待锁释放的线程,如果没有才会尝试获取锁,如果有则排队等待
  • 非公平锁
    在线程获取锁之前不管有没有排队等待的线程,都会尝试先获取锁,只有失败了才会排队等待
    设置方法在实例化lock的时候使用有参构造传入boolean类型
    image.png

ReentrantLock常用API

int getHoldCount()查看当前线程lock数目即持有此锁的次数

public class CC {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        System.out.println("1  " + lock.getHoldCount());
        lock.lock();
        System.out.println("2  " + lock.getHoldCount());
        lock.lock();
        System.out.println("3  " + lock.getHoldCount());
        lock.lock();
        System.out.println("4  " + lock.getHoldCount());
        lock.unlock();
        System.out.println("5  " + lock.getHoldCount());
        lock.unlock();
        System.out.println("6  " + lock.getHoldCount());
        lock.unlock();
        System.out.println("7  " + lock.getHoldCount());
        lock.unlock();
        System.out.println("8  " + lock.getHoldCount());
    }
}

最后因为没有持有锁进行unlock抛出异常
image.png

监视方法

这些方法返回的结果都是具有时效性的,不是一成不变,所以不要用于结构控制

int getQueueLength()查看排队队列数目

查看有多少个线程在排队等待
image.png
值得注意的是JDK源码里面也写清楚了,即时返回数目也可能会因为进程的动态变化导致不准确,所以最好不要用于流程控制

class CA {
    private static ReentrantLock lock = new ReentrantLock();

    private void test() {
        lock.lock();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        CA ca = new CA();
        Thread thread1 = new Thread(() -> ca.test());
        Thread thread2 = new Thread(() -> ca.test());
        Thread thread3 = new Thread(() -> ca.test());
        Thread thread4 = new Thread(() -> ca.test());
        System.out.println("0 "+lock.getQueueLength());
        thread1.start();
        Thread.sleep(100);
        System.out.println("1 "+lock.getQueueLength());
        thread2.start();
        Thread.sleep(100);
        System.out.println("2 "+lock.getQueueLength());
        thread3.start();
        Thread.sleep(100);
        System.out.println("3 "+lock.getQueueLength());
        thread4.start();
        Thread.sleep(100);
        System.out.println("4 "+lock.getQueueLength());
        Thread.sleep(4000);
        System.out.println("5 "+lock.getQueueLength());
    }
}

结果如下图,在第四个线程启动之后主线程休眠4秒后等待数为0,说明都执行完毕,线程也结束了
image.png

其他类似方法

  • int getWaitingThreads(Condition condition)
    查看wait等待此condition的线程数目
  • boolean hasWaiters(Condition condition)
    查看lock给定的condition中是否有等待队列
  • boolean hasQueuedThread(Thread thread)
    查看该thread是否在此lock的等待队列中
  • boolean hasQueuedThreads()
    查看此lock是否有等待队列
  • boolean hasWaiters(Condition condition)
  • boolean isFair()
    查看此lock锁是否为公平锁
  • boolean isHeldByCurrentThread()
    查询当前thread线程是否持有此lock锁
  • boolean isLocked()
    查询该lock锁是否被任何线程持有,没有释放
  • lockInterruptibly()、trylock()、lock()
    详细的不说了,知乎上面的一句话简单明了
    https://www.zhihu.com/question/36771163
    image.png

线程间通信交替执行

通过统一的condition的设置,再根据条件判断,如下代码可以实现交替打印ABC

//按顺序执行线程
class CD {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    //标识位,volatile强制线程从公共堆栈中取值
    private volatile int flag = 1;

    private void A() {
        lock.lock();
        while (flag != 1) {
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("A");
        flag = 2;
        condition.signalAll();
        lock.unlock();
    }


    private void B() {
        lock.lock();
        while (flag != 2) {
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("B");
        flag = 3;
        condition.signalAll();
        lock.unlock();
    }


    private void C() {
        lock.lock();
        while (flag != 3) {
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("C");
        flag = 1;
        condition.signalAll();
        lock.unlock();
    }

    public static void main(String[] args) {
        CD cd = new CD();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> cd.A()).start();
            new Thread(() -> cd.B()).start();
            new Thread(() -> cd.C()).start();
        }
    }
}

ReadWriterLock接口使用ReentrantReadWriteLock读写锁

lock接口的ReentrantLock锁排他性效率并发如果不修改的话只读区的化,则执行效率不高
image.png
其使用的writeLock()和readLock()返回的还是Lock接口实现

ReentrantReadWriteLock锁的读共享

使用lock.readLock()返回Lock锁之后再使用lock()方法

public class DD {
    private ReadWriteLock lock=new ReentrantReadWriteLock();
    private String useName="superMan";

    public void testRead(){
        lock.readLock().lock();
        System.out.println("start"+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
        try {
            Thread.sleep(3000);
            System.out.println("start"+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }
    public static void main(String[] args) {
        DD dd = new DD();
        new Thread(()->dd.testRead()).start();
        new Thread(()->dd.testRead()).start();
        new Thread(()->dd.testRead()).start();
    }
}

结果如下,读之间并不影响
image.png

ReentrantReadWriteLock锁的写互斥

使用lock.wirteLock()返回Lock锁之后再使用lock()方法
验证:

  • 只要有写锁就不能再加任何锁
  • 只要有读锁只能加读锁
//测试读写、写写
 class DA {
    private ReadWriteLock lock=new ReentrantReadWriteLock();
    private String useName="superMan";

    public void testRead(){
        lock.readLock().lock();
        System.out.println("startRead"+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
        try {
            Thread.sleep(3000);
            System.out.println("endRead"+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }
    public void testWirte(){
        lock.writeLock().lock();
        System.out.println("startWirte"+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
        try {
            Thread.sleep(3000);
            System.out.println("endWirte"+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
    public static void main(String[] args) {
        DA da = new DA();
        new Thread(()->da.testWirte()).start();
        new Thread(()->da.testRead()).start();
        new Thread(()->da.testWirte()).start();
    }
}

结果如下
image.png


这个家伙很懒,啥也没有留下😋