zoukankan      html  css  js  c++  java
  • 多线程系列之自己实现一个 lock 锁

    我们面试中经常会被问到多线程相关知识,这一块内容往浅了说大家都会,但是一问到底层实现原理,我们往往就一脸懵逼。

    这段时间准备好好学习多线程,接下来会写一系列关于多线程的知识。

    我们首先要了解线程,百度百科这么介绍:线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程每条线程并行执行不同的任务

    这句话很好理解。一般我们不创建 thread 类的话,程序都是单线程在执行。

    线程是有多种状态的,Thread 类里有个枚举变量 State 列举了线程的几种状态

    下面这张图说明了线程状态之间的关系。

    程序里有多个线程时,会出现多个线程对同一个对象操作的情况,为了保证同一时间只能有一个线程操作该对象,就引入了锁机制。

    锁有分布式锁,java锁 ,java 锁有 synchronized 关键字,Lock 锁。今天我们主要讲 java 中的 Lock 锁。

    常用的可重入锁(ReentrantLock)就是实现了 Lock 接口,这次我们就尝试自己制作一把锁,当然我们要实现 Lock 接口。

    首先来分析如何实现它?

    1.需要一个线程的等待集合,所以我们要定义一个list,考虑到先进先出机制,我们用 LinkedBlockingQueue 来存放线程集合

    2.需要一个锁标记,来记录当前持有锁的进程,考虑到它的原子性,我们用 AtomicReference 类来存放

    3.线程执行完,会释放锁,此时要通知其他线程争抢锁,这里又涉及到线程通信。

    下图也是我的分析过程,我们接下来就用代码来实现。

     

    实现之前,我们先了解一些线程通信的知识,我们最常用的方法是 wait/notify,但是 wait/notify 有一个很明显的缺点,正常顺序是 先 wait,再 notify,如果程序先 notify,再 wait,就会进入死锁状态,所以它是有顺序限制的,但是 park/unpark 方法就不会有这些问题,所以我们用 park/unpark 实现线程通信。

    下面是实现代码,注释里写的很清楚了。

    public class MyLock implements Lock {
    
        //判断是否有人拿到锁
        AtomicReference<Thread> owner = new AtomicReference<>();
        //等待线程队列
        LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
    
        @Override
        public boolean tryLock() {
            //如果 owner 为空,就把当前线程赋给 owner
            return owner.compareAndSet(null,Thread.currentThread());
        }
    
        @Override
        public void lock() {
            boolean park = false;
            while(!tryLock()){
                if(!park){
                    //加入等待集合
                    waiters.offer(Thread.currentThread());
                    park = true;
                }else{
                    LockSupport.park();
                }
            }
            waiters.remove(Thread.currentThread());
    
        }
    
        @Override
        public void unlock() {
            //释放锁,要通知等待者
            if(owner.compareAndSet(Thread.currentThread(),null)){
                //遍历等待者 ,通知继续执行
                Thread next = null;
                while ((next = waiters.peek() )!= null){
                    LockSupport.unpark(next);
                }
    
            }
        }
    
        @Override
        public void lockInterruptibly() throws InterruptedException {
        }
        
        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            return false;
        }
        
        @Override
        public Condition newCondition() {
            return null;
        }
    }

     Lock 接口里,还有一些方法没有实现,当然这都不重要,主要是为了实现 lock 和 unlock 方法。

    研究多线程,要多看看 java.util.concurrent 包,把这个包研究透了,多线程也就学的差不多了。毕竟面试中多线程用的还是挺多的。

  • 相关阅读:
    《Redis 设计与实现》读书笔记(四)
    《Redis 设计与实现》读书笔记(三)
    《Redis 设计与实现》读书笔记(二)
    《Redis 设计与实现》读书笔记(一)
    《Mysql技术内幕-InnoDB存储引擎》读书笔记 (一)
    Python调试工具
    记一次偶发的bug排查——redis-py-cluster库的bug
    苹果支付的这些漏洞,你都堵上了吗?
    微软消息队列-MicroSoft Message Queue(MSMQ)队列的C#使用
    Quartz.net设置任务中同时最多运行一个实例 [DisallowConcurrentExecution]
  • 原文地址:https://www.cnblogs.com/fightingting/p/10680608.html
Copyright © 2011-2022 走看看