zoukankan      html  css  js  c++  java
  • 关于ReentrantLock锁机制的那些事

    1. 背景引出

    背景:关于并发编程,多线程的业务,之前很想写一篇文章来的,因很多时候,忙于工作,而忽视了这些基础的知识点,项目中用的这些知识点也是很少。

    今天在在代码中,突然看到了一个前同事写的一个CopyToWriteArraySet这个变量,很好奇,为啥使用这个变量,而不去使用我们经常使用的HashSet相关集合呢,于是,我跟踪源码似乎明白了一些道理了。具体情况

    先了解下这个CopyToWriteArrayList源码

     可以看到其类中使用了一个ReentrantLock变量,和一个存储数据的数组(使用了volatile修饰,transient修饰表示这个类在序列化时,当前这个变量不会被序列化)

    而从源码中,我们可以很容易知道CopyToWriteArraySet内部采用的是CopyToWriteArrayList,只不过,CopyToWriteArraySet在进行调用CopyToWriteArrayList中的add操作前,检查了一下是否有重复的元素,看如下源码:

     再来看看这个add操作源码

     很明显,使用ReentrantLock锁机制,同时,使用Arrays.copyof进行创建副本操作,然后加setArray到数组中。

    通过上面讲解,得出关于CopyToWriteArrayList的结论:

    CopyToWriteArraySet内部采用的是CopyToWriteArrayList,存储的数据是一个数组,这个数组用volatile修饰的(保证变量可见性,修改后的值,直接存储到主内存中,达到一种共享变量),而这个CopyToWriteArrayList中在进行add时,进行了加锁操作,保证线程的安全。但不影响其他线程对其读操作(为什么呢,因为CopyToWriteArrayList对数据进行add/update/remove时,都是通过一个副本来操作,操作完成后,在赋值给这个被volatile修饰的数组)

    所以,如果我们想使用线程安全的集合,可以使用CopyToWriteArrayList来代替ArrayList集合来操作。

    1. 继续研究ReentrantLock机制

    ReentantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。

    Lock 接口的主要方法(选择了几个重要的方法)

    1. void lock(): 执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁. 相反, 如果锁已经被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁.

    2. boolean tryLock():如果锁可用, 则获取锁, 并立即返回 true, 否则返回 false. 该方法和lock()的区别在于, tryLock()只是"试图"获取锁, 如果锁不可用, 不会导致当前线程被禁用,当前线程仍然继续往下执行代码. 而 lock()方法则是一定要获取到锁, 如果锁不可用, 就一直等待, 在未获得锁之前,当前线程并不继续向下执行.

    3. void unlock():执行此方法时, 当前线程将释放持有的锁. 锁只能由持有者释放, 如果线程并不持有锁, 却执行该方法, 可能导致异常的发生.

    4. Condition newCondition():条件对象,获取等待通知组件。该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的 await()方法,而调用后,当前线程将缩放锁。

    5. isLock():此锁是否有任意线程占用

    6. lockInterruptibly():如果当前线程未被中断,获取锁

    7. tryLock():尝试获得锁,仅在调用时锁未被线程占用,获得锁

    8. tryLock(long timeout TimeUnit unit):如果锁在给定等待时间内没有被另一个线程保持,则获取该锁。

    非公平锁 JVM 按随机、就近原则分配锁的机制则称为不公平锁,ReentrantLock 在构造函数中提供了 是否公平锁的初始化方式,默认为非公平锁。非公平锁实际执行的效率要远远超出公平锁,除非 程序有特殊需要,否则最常用非公平锁的分配机制。

    公平锁 公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁, ReentrantLock 在构造函数中提供了是否公平锁的初始化方式来定义公平锁。

    ReentrantLock 与 synchronized

    1. ReentrantLock通过方法 lock()与 unlock()来进行加锁与解锁操作,与 synchronized会被JVM 自动解锁机制不同,ReentrantLock 加锁后需要手动进行解锁。为了避免程序出 现异常而无法正常解锁的情况,使用 ReentrantLock 必须在 finally 控制块中进行解锁操 作。

    2. ReentrantLock 相比 synchronized 的优势是可中断、公平锁、多个锁。这种情况下需要 使用 ReentrantLock。

    1. 写作最后:
    采用synchronized 实现ReentrantLock
    package com.quanroon.ysq;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    
    /**
     * @author quanroong.ysq
     * @version 1.0.0
     * @description 采用synchronized 实现ReentrantLock
     * 分析思路参考此地址:https://www.cnblogs.com/aspirant/p/8601937.html  分析的不错。从两者使用的不同点出发,然后,利用其相同点来进行构思
     * @createtime 2020/8/15 14:31
     */
    public class SynchronizedToReentrantLock {
        private static final long NONE=-1;
        private long owner =NONE;
    
        public synchronized void lock(){
            long currentThreadId=Thread.currentThread().getId();
            if(owner==currentThreadId){
                throw new IllegalStateException("lock has been acquired by current thread");
            }
    
            while(this.owner!=NONE){
    
                System.out.println(String.format("thread %s is waiting lock", currentThreadId));
                try {
                    wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            this.owner=currentThreadId;
            System.out.println(String.format("lock is acquired by thread %s", currentThreadId));
    
        }
    
        public synchronized void unlock(){
    
            long currentThreadId=Thread.currentThread().getId();
    
            if(this.owner!=currentThreadId){
                throw new IllegalStateException("Only lock owner can unlock the lock");
            }
    
            System.out.println(String.format("thread %s is unlocking", owner));
            owner=NONE;
            notify();
    
        }
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub 给定锁
            final SynchronizedToReentrantLock lock=new SynchronizedToReentrantLock();
    
            //产生20个线程
            ExecutorService executor= Executors.newFixedThreadPool(20, new ThreadFactory(){
                private ThreadGroup group =new ThreadGroup("test thread group");
                {
                    group.setDaemon(true);
                }
                @Override
                public Thread newThread(Runnable r) {
                    // TODO Auto-generated method stub
                    return new Thread(group,r);
                }});
    
    
            for(int i=0;i<20;i++){
                executor.submit(new Runnable(){
    
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        lock.lock();
                        System.out.println(String.format("thread %s is running...", Thread.currentThread().getId()));
    
    
                        try {
                            //执行业务逻辑
                            for (int i1 = 0; i1 < 10; i1++) {
                                System.out.println("===> i1=" + i1);
                                Thread.sleep(1000);
                            }
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        lock.unlock();
                    }});
            }
    
    
        }
    }
  • 相关阅读:
    java坏境内存不够用 大量占用swap 临时加swap
    磁盘分区
    简述raid0,raid1,raid5,raid10 的工作原理及特点
    给用户提权
    用户的环境变量被删除了
    定时任务
    linux权限
    kafka部署
    数据仓库
    kylin
  • 原文地址:https://www.cnblogs.com/ysq0908/p/13509419.html
Copyright © 2011-2022 走看看