avatar

一条在知识海洋的咸鱼

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

  • 首页
  • Linux
  • OCJP
  • Java核心技术卷
  • J2EE相关标准
  • 深入理解Java虚拟机
  • NIO与SOcket编程技术指南
  • Java多线程编程核心技术
  • Redis开发与运维
  • Spring Cloud Alibaba 微服务原理与实践
  • DevOps
  • Docker
  • MySQL必知必会
  • AI自学路线
  • Spring Boot 编程思想(核心篇)
主页 Lock对象的使用
文章

Lock对象的使用

发表于 2021-10-7 更新于 2026-06- 10
作者 Administrator
37~47 分钟 阅读

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类(互斥锁,拍他锁)
    • 基础使用
    • Condition接口
    • 使用Condition的wait/signal方法机制来实现Object的wait/notify方法
    • Unsafe类-awiat原理
    • 唤醒指定线程
    • 公平锁和非公平锁
    • ReentrantLock常用API
      • int getHoldCount()查看当前线程lock数目即持有此锁的次数
    • 监视方法
      • int getQueueLength()查看排队队列数目
      • 其他类似方法
      • 线程间通信交替执行
  • ReadWriterLock接口使用ReentrantReadWriteLock读写锁
    • ReentrantReadWriteLock锁的读共享
    • ReentrantReadWriteLock锁的写互斥

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

Java多线程编程核心技术
Java并发
许可协议: 
分享

相关文章

11月 13, 2021

线程其他

线程在不同的层次不同的说法与环境,就Java语言层次来说线程状态验证NEW、RUNNABLE、TERMINATED验证BLOCKED、WAITING、TIMED_WAITING线程状态就线程来说不同的层面有不同的说法。底层P(S),V(S)资源操作,编码层次对象锁操作,在Java层面对线程的状态定义

11月 12, 2021

单例模式与多线程

单利模式是经常使用到的设计模式,例如spring的默认bean为单例。同时众多框架体系使用的为多线程体系,所以在使用的时候单例和多线程结合起来可能会有一些没有注意到的事情,例如全局变量的控制等。单例模式的实现立即加载/饿汉模式单例模式的实现立即加载/饿汉模式

11月 12, 2021

定时器TImer

Java的定时/计划功能功能

下一篇

定时器TImer

上一篇

线程间通信

最近更新

  • 55 异常兜底:工具失败、非法输出、空检索与超时
  • 54 Agent Memory:短期记忆、用户上下文与摘要
  • 53 Human-in-the-loop:让关键动作暂停、确认与恢复
  • 52 工单 Tool:字段校验、创建结果与失败补偿
  • 51 订单 Tool:Mock、鉴权、状态解析与脱敏

热门标签

java基础 微服务 maven Spring Tomcat DDD Linux Linux基础 SQL基础 数据结构算法

目录

©2026 一条在知识海洋的咸鱼. 保留部分权利。

使用 Halo 主题 Chirpy