• JUC(java.util.concurrency)包中常用锁相关类


    java.util.concurrency.locks

    java.util.concurrency.locksjava.util.concurrency(JUC)包下,它是一个提供了锁机制相关的类包,比如:Lock, Condition, ReadWriteLock等。

    Lock接口

    在JDK5之前,Java应用程序对于多线程的并发安全处理只能基于synchronized关键字解决,但是synchronized在有些场景中会存在一些短板。比如:它并不适合于所有的并发场景。Lock的出现可以解决synchronized在某些场景中的短板,它比synchronized更加灵活。

    synchronized、wait

    synchronized可以加锁,wait、notify可以看做加锁和解锁。但是synchronized方式加锁存在一定问题,因此出现了显式锁Lock。

    synchronized的问题:

    1、同步块的阻塞无法中断,不能Interruptibly
    
    2、同步块的阻塞无法控制超时,无法自动解锁
    
    3、同步块无法异步处理锁,即不能立即知道是否可以拿到锁
    
    4、同步块无法根据条件灵活的加锁解锁,即只能跟同步块范围一致
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Lock

    Lock锁采用接口设计,使用方式灵活可控,性能开销小,属于java.util.concurrent.locks锁工具包
    在这里插入图片描述

    接口方法

    在这里插入图片描述

    方法描述
    void lock()获取锁,类似synchronized (lock)
    void lockInterruptibly()获取锁,允许打断
    Condition newCondition()新增一个绑定到当前Lock的条件
    final Lock lock = new ReentrantLock()
    final Condition notEmpty = lock.newCondition()
    boolean tryLock()尝试无等待获取锁,成功则返回true
    boolean tryLock(long time, TimeUnit unit)尝试获取锁,成功则返回true,超时则退出
    void unlock()解锁,要求当前线程已获得锁,类似同步块结束

    使用Lock

    public class LockCounter {
        private int sum = 0;
        // 可重入锁+公平锁
        private Lock lock = new ReentrantLock(true);
    
        public int incrAndGet() {
            try {
                lock.lock();
                return ++sum;
            } finally {
                lock.unlock();
            }
        }
    
        public int getSum() {
            return sum;
        }
    
        public static void main(String[] args) {
            int loopNum = 10_0000;
            LockCounter counter = new LockCounter();
            // 从0到loopNum产生递增值序列,转成一个流,然后循环遍历0到loopNum数,进行求和
            IntStream.range(0, loopNum).parallel()
                    .forEach(i -> counter.incrAndGet());
            System.out.println("counter.getSum() = " + counter.getSum());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    counter.getSum() = 100000
    
    • 1

    ReentrantLock重入锁

    ReentrantLock是可以重入的锁,当一个线程获取锁时,还可以接着重复获取多次。
    
    ReentrantLock具有与synchronized相同功能,但是会比synchronized更加灵活,具有更多的方法。
    
    ReentrantLock底层基于AbstractQueuedSynchronizer实现。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ReentrantLock与Synchronized的异同

    ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。

    实现上两者不同:

    synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但不够灵活。一般并发场景使用synchronized

    ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中使用。

    都是可重入:

    synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁

    ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。

    ReentrantLock实现公平、非公平锁

    ReentrantLock可以实现公平、非公平锁。

    公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权。非公平锁则随机分配这种使用权。

    ReentrantLock和synchronized一样,默认ReentrantLock实现是非公平锁,因为相比公平锁,非公平锁性能更好。

    创建公平锁

    ReentrantLock lock = new ReentrantLock(true);
    
    • 1

    创建非公平锁

    ReentrantLock lock = new ReentrantLock(false);
    
    • 1

    使用示例

    public class ReentrantLockDemo {
    
        private static int count = 0;
    
        // 公平重入锁
        static Lock lock = new ReentrantLock(true);
    
        public static void inc() {
            // 线程获得锁(互斥锁)
            lock.lock();
            try {
                Thread.sleep(1);
                count++;
                getLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //释放锁 
                lock.unlock();
            }
        }
    
        public static void getLock() {
            // 同一个线程再次来抢占锁 : 不需要再次抢占锁,而是只增加重入的次数
            lock.lock();
            try {
                count--;
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> AtomicDemo.inc());
            t1.start();
    
            System.out.println("result:" + count);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    result:0
    
    • 1

    ReadWriteLock重入读写锁

    ReadWriteLock管理一组锁:一个读锁,一个写锁。它适用于读多写少的并发情况。

    读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。因此,在读写锁的实现时必须确保写操作对读操作的影响。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。

    在这里插入图片描述

    接口方法

    Lock readLock(); 获取读锁,共享锁
    
    Lock writeLock(); 获取写锁,独占锁,排斥读锁
    
    • 1
    • 2
    • 3

    使用ReadWriteLock

    public class ReadWriteLockCounter {
        private int sum = 0;
        /**
         * 可重入、读写锁、公平/非公平锁
         * fair:true:公平锁 false:非公平锁
         * 公平锁意味排队靠前的优先
         * 非公平锁则都是同样机会
         */
        private ReadWriteLock lock = new ReentrantReadWriteLock(true);
    
        public int incrAndGet() {
            try {
                // 写锁 独占锁 被读锁排斥
                lock.writeLock().lock();
                return ++sum;
            } finally {
                lock.writeLock().unlock();
            }
        }
    
        public int getSum() {
            try {
                // 读锁 共享锁 保证可见性
                lock.readLock().lock();
                return sum;
            } finally {
                lock.readLock().unlock();
            }
        }
    
        public static void main(String[] args) {
            int loopNum = 10_0000;
            ReadWriteLockCounter readWriteLockCounter = new ReadWriteLockCounter();
            // 从0到loopNum产生递增值序列,转成一个流,然后循环遍历0到loopNum数,进行求和
            IntStream.range(0, loopNum).parallel()
                    .forEach(i -> readWriteLockCounter.incrAndGet());
            System.out.println("counter.getSum() = " + readWriteLockCounter.getSum());
            System.out.println("counter.getSum() = " + readWriteLockCounter.getSum());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    counter.getSum() = 100000
    counter.getSum() = 100000
    
    • 1
    • 2

    Condition信号量

    Condition是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件( condition ),只有满足条件时,线程才会被唤醒

    Condition具有线程的wait和notify的功能,有2个核心方法:

    await:把当前线程阻塞挂起
    
    signal:唤醒阻塞的线程
    
    • 1
    • 2
    • 3

    当调用await方法后,当前线程会释放锁并等待,而其他线程调用condition对象的signal或者signalall方法通知并被阻塞的线程,然后自己执行unlock释放锁,被唤醒的线程获得之前的锁继续执行,最后释放锁。

    接口方法

    在这里插入图片描述

    方法描述
    void await()等待信号,相当于Object的wait()
    boolean await(long time, TimeUnit unit)等待信号,超时则返回false
    long awaitNanos(long nanosTimeout)等待信号,直到被信号发送或中断, 或指定等待时间过去
    void awaitUninterruptibly()等待信号
    boolean awaitUntil(Date deadline)等待信号, 超时则返回false
    void signal()给一个等待线程发送唤醒信号, 相当于Object的notify ()
    void signalAll()给所有等待线程发送唤醒信号,相当于Object的notifyAll()

    使用Condition

        public static void main(String[] args) {
            Queue<String> queue = new LinkedList<>();
            // 重入锁
            Lock lock = new ReentrantLock();
            // 通过Lock.newCondition()创建,可以看做是Lock对象上的信号,类似于wait/notify
            Condition condition = lock.newCondition();
            int maxSize = 5;
    
            Producer producer = new Producer(queue, maxSize, lock, condition);
            Consumer consumer = new Consumer(queue, maxSize, lock, condition);
    
            Thread t1 = new Thread(producer);
            Thread t2 = new Thread(consumer);
            t1.start();
            t2.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    public class Producer implements Runnable {
    
        private Queue<String> msg;
    
        private int maxSize;
    
        Lock lock;
        Condition condition;
    
        public Producer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
            this.msg = msg;
            this.maxSize = maxSize;
            this.lock = lock;
            this.condition = condition;
        }
    
        @Override
        public void run() {
            int i = 0;
            while (true) {
                i++;
                lock.lock();
                while (msg.size() == maxSize) {
                    System.out.println("生产者队列满了,先等待");
                    try {
                        condition.await(); // 阻塞线程并释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("生产消息:" + i);
                msg.add("生产者的消息内容" + i);
                condition.signal(); // 唤醒阻塞状态下的线程
                lock.unlock();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    public class Consumer implements Runnable {
        private Queue<String> msg;
    
        private int maxSize;
    
        Lock lock;
        Condition condition;
    
        public Consumer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
            this.msg = msg;
            this.maxSize = maxSize;
            this.lock = lock;
            this.condition = condition;
        }
    
        @Override
        public void run() {
            int i = 0;
            while (true) {
                i++;
                lock.lock(); //synchronized
                while (msg.isEmpty()) {
                    System.out.println("消费者队列空了,先等待");
                    try {
                        condition.await(); //阻塞线程并释放锁   wait
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费消息:" + msg.remove());
                condition.signal(); //唤醒阻塞状态下的线程
                lock.unlock();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    生产消息:1
    生产消息:2
    生产消息:3
    生产消息:4
    生产消息:5
    生产者队列满了,先等待
    消费消息:生产者的消息内容1
    消费消息:生产者的消息内容2
    消费消息:生产者的消息内容3
    消费消息:生产者的消息内容4
    消费消息:生产者的消息内容5
    消费者队列空了,先等待
    生产消息:6
    生产消息:7
    生产消息:8
    生产消息:9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    LockSupport锁当前线程

    LockSupport类似于Thread 类的静态方法,专门处理执行代码的本线程。

    接口方法

    在这里插入图片描述

    方法描述
    void park(Object blocker)暂停当前线程
    void parkNanos(Object blocker,long nanos)暂停当前线程,不过有超时时间的限制
    void parkUntil(Object blocker,long deadline)暂停当前线程,直到某个时间
    void park()无期限暂停当前线程
    void parkNanos(long nanos)暂停当前线程,不过有超时时间的限制
    void parkUntil(long deadline)暂停当前线程,直到某个时间
    void unpark(Thread thread)恢复当前线程。传入Thread参数,是因为一个park线程,无法自己唤醒自己,所以需要其他线程来唤醒
    Object getBlocker(Thread t)

    使用LockSupport

    
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " 开始执行");
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() + " 被唤醒");
            }, "t1");
            t1.start();
    
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " 开始执行");
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + " 开始唤醒");
                LockSupport.unpark(t1);
            }, "t2").start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    t1 开始执行
    t2 开始执行
    t2 开始唤醒
    t1 被唤醒
    
    • 1
    • 2
    • 3
    • 4

    用锁原则

    1. 只在更新对象的成员变量时加锁
    
    2. 只在访问可变的成员变量时加锁
    
    3. 不在调用其他对象的方法时加锁
    
    4. 降低锁范围:锁定代码的范围、作用域
    
    5. 细分锁粒度:一个大锁,拆分成多个小锁
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    大厂真题:【位运算】米哈游2023秋招-相加异或
    微服务框架 SpringCloud微服务架构 20 RestClient 操作索引库 20.1 导入demo
    二进制搭建及高可用 Kubernetes v1.20
    java毕业设计藏宝阁游戏交易系统Mybatis+系统+数据库+调试部署
    子网掩码与VLAN有何区别?
    Python语法基础(条件语句 循环语句 函数 切片及索引)
    Makefile相关操作
    c++ - 第11节 - stack和queue类
    适合女生佩戴的蓝牙耳机有什么推荐?五款高性价比蓝牙耳机
    【最小生成树的用法总结】
  • 原文地址:https://blog.csdn.net/qq_38628046/article/details/127579534