关于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();
                }});
        }


    }
}
原文地址:https://www.cnblogs.com/ysq0908/p/13509419.html