深读源码-java同步系列之自己手写一个锁Lock

问题

(1)自己动手写一个锁需要哪些知识?

(2)自己动手写一个锁到底有多简单?

(3)自己能不能写出来一个完美的锁?

简介

本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁、解锁操作。

本篇文章的目标二是通过自己动手写一个锁,能更好地理解后面章节将要学习的AQS及各种同步器实现的原理。

分析

自己动手写一个锁需要准备些什么呢?

首先,在上一章学习synchronized的时候我们说过它的实现原理是更改对象头中的MarkWord,标记为已加锁或未加锁。

但是,我们自己是无法修改对象头信息的,那么我们可不可以用一个变量来代替呢?

比如,这个变量的值为1的时候就说明已加锁,变量值为0的时候就说明未加锁,我觉得可行。

其次,我们要保证多个线程对上面我们定义的变量的争用是可控的,所谓可控即同时只能有一个线程把它的值修改为1,且当它的值为1的时候其它线程不能再修改它的值,这种是不是就是典型的CAS操作,所以我们需要使用Unsafe这个类来做CAS操作。

然后,我们知道在多线程的环境下,多个线程对同一个锁的争用肯定只有一个能成功,那么,其它的线程就要排队,所以我们还需要一个队列。

最后,这些线程排队的时候干嘛呢?它们不能再继续执行自己的程序,那就只能阻塞了,阻塞完了当轮到这个线程的时候还要唤醒,所以我们还需要Unsfae这个类来阻塞(park)和唤醒(unpark)线程。

基于以上四点,我们需要的神器大致有:一个变量、一个队列、执行CAS/park/unpark的Unsafe类

大概的流程图如下图所示:

mylock

关于Unsafe类的相关讲解请参《深读源码-java魔法类之Unsafe解析》

解决

一个变量

这个变量只支持同时只有一个线程能把它修改为1,所以它修改完了一定要让其它线程可见,因此,这个变量需要使用volatile来修饰。

private volatile int state;

CAS

这个变量的修改必须是原子操作,所以我们需要CAS更新它,我们这里使用Unsafe来直接CAS更新int类型的state。

当然,这个变量如果直接使用AtomicInteger也是可以的,不过,既然我们学习了更底层的Unsafe类那就应该用(浪)起来。

private boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

一个队列

队列的实现有很多,数组、链表都可以,我们这里采用链表,毕竟链表实现队列相对简单一些,不用考虑扩容等问题。

这个队列的操作很有特点:

放元素的时候都是放到尾部,且可能是多个线程一起放,所以对尾部的操作要CAS更新;

唤醒一个元素的时候从头部开始,但同时只有一个线程在操作,即获得了锁的那个线程,所以对头部的操作不需要CAS去更新。

private static class Node {
    // 存储的元素为线程
    Thread thread;
    // 前一个节点(可以没有,但实现起来很困难)
    Node prev;
    // 后一个节点
    Node next;

    public Node() {
    }

    public Node(Thread thread, Node prev) {
        this.thread = thread;
        this.prev = prev;
    }
}
// 链表头
private volatile Node head;
// 链表尾
private volatile Node tail;
// 原子更新tail字段
private boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

这个队列很简单,存储的元素是线程,需要有指向下一个待唤醒的节点,前一个节点可有可无,但是没有实现起来很困难,不信学完这篇文章你试试。

加锁

public void lock() {
    // 尝试更新state字段,更新成功说明占有了锁
    if (compareAndSetState(0, 1)) {
        return;
    }
    // 未更新成功则入队
    Node node = enqueue();
    Node prev = node.prev;
    // 再次尝试获取锁,需要检测上一个节点是不是head,按入队顺序加锁
    while (node.prev != head || !compareAndSetState(0, 1)) {
        // 未获取到锁,阻塞
        unsafe.park(false, 0L);
    }
    // 下面不需要原子更新,因为同时只有一个线程访问到这里
    // 获取到锁了且上一个节点是head
    // head后移一位
    head = node;
    // 清空当前节点的内容,协助GC
    node.thread = null;
    // 将上一个节点从链表中剔除,协助GC
    node.prev = null;
    prev.next = null;
}
// 入队
private Node enqueue() {
    while (true) {
        // 获取尾节点
        Node t = tail;
        // 构造新节点
        Node node = new Node(Thread.currentThread(), t);
        // 不断尝试原子更新尾节点
        if (compareAndSetTail(t, node)) {
            // 更新尾节点成功了,让原尾节点的next指针指向当前节点
            t.next = node;
            return node;
        }
    }
}

(1)尝试获取锁,成功了就直接返回;

(2)未获取到锁,就进入队列排队;

(3)入队之后,再次尝试获取锁;

(4)如果不成功,就阻塞;

(5)如果成功了,就把头节点后移一位,并清空当前节点的内容,且与上一个节点断绝关系;

(6)加锁结束;

解锁

// 解锁
public void unlock() {
    // 把state更新成0,这里不需要原子更新,因为同时只有一个线程访问到这里
    state = 0;
    // 下一个待唤醒的节点
    Node next = head.next;
    // 下一个节点不为空,就唤醒它
    if (next != null) {
        unsafe.unpark(next.thread);
    }
}

(1)把state改成0,这里不需要CAS更新,因为现在还在加锁中,只有一个线程去更新,在这句之后就释放了锁;

(2)如果有下一个节点就唤醒它;

(3)唤醒之后就会接着走上面lock()方法的while循环再去尝试获取锁;

(4)唤醒的线程不是百分之百能获取到锁的,因为这里state更新成0的时候就解锁了,之后可能就有线程去尝试加锁了。

测试

上面完整的锁的实现就完了,是不是很简单,但是它是不是真的可靠呢,敢不敢来试试?!

直接上测试代码:

private static int count = 0;

public static void main(String[] args) throws InterruptedException {
    MyLock lock = new MyLock();

    CountDownLatch countDownLatch = new CountDownLatch(1000);

    IntStream.range(0, 1000).forEach(i -> new Thread(() -> {
        lock.lock();

        try {
            IntStream.range(0, 10000).forEach(j -> {
                count++;
            });
        } finally {
            lock.unlock();
        }
//            System.out.println(Thread.currentThread().getName());
        countDownLatch.countDown();
    }, "tt-" + i).start());

    countDownLatch.await();

    System.out.println(count);
}

运行这段代码的结果是总是打印出10000000(一千万),说明我们的锁是正确的、可靠的、完美的。

总结

(1)自己动手写一个锁需要做准备:一个变量、一个队列、Unsafe类。

(2)原子更新变量为1说明获得锁成功;

(3)原子更新变量为1失败说明获得锁失败,进入队列排队;

(4)更新队列尾节点的时候是多线程竞争的,所以要使用原子更新;

(5)更新队列头节点的时候只有一个线程,不存在竞争,所以不需要使用原子更新;

(6)队列节点中的前一个节点prev的使用很巧妙,没有它将很难实现一个锁,只有写过的人才明白,不信你试试^^

彩蛋

(1)我们实现的锁支持可重入吗?

答:不可重入,因为我们每次只把state更新为1。如果要支持可重入也很简单,获取锁时检测锁是不是被当前线程占有着,如果是就把state的值加1,释放锁时每次减1即可,减为0时表示锁已释放。

(2)我们实现的锁是公平锁还是非公平锁?

答:非公平锁,因为获取锁的时候我们先尝试了一次,这里并不是严格的排队,所以是非公平锁。

(3)完整源码

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch;
import java.util.stream.IntStream;

/**
 * @ClassName MyLock
 * @Author suidd
 * @Description 自己手写一个锁Lock
 * @Date 11:06 2020/5/23
 * @Version 1.0
 **/
public class MyLock {
    //判断的当前是否加锁 0:未加锁 1:已加锁
    private volatile int state;
    // 链表头
    private volatile Node head;
    // 链表尾
    private volatile Node tail;
    // Unsafe实例
    private static Unsafe unsafe;
    // state偏移量
    private static long stateOffset;
    // 链表尾偏移量
    private static long tailOffset;
    // 空对象
    static final Node EMPTY = new Node();

    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
            stateOffset = unsafe.objectFieldOffset(MyLock.class.getDeclaredField("state"));
            tailOffset = unsafe.objectFieldOffset(MyLock.class.getDeclaredField("tail"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private static class Node {
        // 存储的元素为线程
        Thread thread;
        // 前一个节点(可以没有,但实现起来很困难)
        Node prev;
        // 后一个节点
        Node next;

        public Node() {
        }

        public Node(Thread thread, Node prev) {
            this.thread = thread;
            this.prev = prev;
        }
    }

    /**
     * @return
     * @Author suidd
     * @Description CAS原子更新state
     * @Date 11:15 2020/5/23
     * @Param
     * @Version 1.0
     **/
    private boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

    /**
     * @return
     * @Author suidd
     * @Description CAS原子更新链表尾
     * @Date 11:25 2020/5/23
     * @Param
     * @Version 1.0
     **/
    private boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);

    }

    /**
     * @return
     * @Author suidd
     * @Description 构造方法
     * @Date 18:52 2020/5/23
     * @Param
     * @Version 1.0
     **/
    public MyLock() {
        // 初始化链表头和链表尾为空节点
        head = tail = EMPTY;
    }

    /**
     * @return
     * @Author suidd
     * @Description 加锁
     * @Date 11:26 2020/5/23
     * @Param
     * @Version 1.0
     **/
    public void lock() {
        // 尝试更新state字段,更新成功说明占有了锁
        if (compareAndSetState(0, 1)) {
            return;
        }

        //当前线程未取得锁,入队排队
        Node node = enqueue();
        Node prev = node.prev;
        // 再次尝试获取锁,需要检测上一个节点是不是head,按入队顺序加锁
        while (prev != head || !compareAndSetState(0, 1)) {
            // 未获取到锁,阻塞
            unsafe.park(false, 0L);
        }
        // 下面不需要原子更新,因为同时只有一个线程访问到这里
        // 获取到锁了且上一个节点是head
        // head后移一位
        head = node;
        // 清空当前节点的内容,协助GC
        node.thread = null;
        // 将上一个节点从链表中剔除,协助GC
        node.prev = null;
        prev.next = null;
    }

    /**
     * @return
     * @Author suidd
     * @Description 入队
     * @Date 11:28 2020/5/23
     * @Param
     * @Version 1.0
     **/
    private Node enqueue() {
        while (true) {
            // 获取尾节点
            Node t = tail;
            // 构造新节点
            Node node = new Node(Thread.currentThread(), t);
            // 不断尝试原子更新尾节点
            if (compareAndSetTail(t, node)) {
                // 更新尾节点成功了,让原尾节点的next指针指向当前节点
                t.next = node;
                return node;
            }
        }
    }

    /**
     * @return
     * @Author suidd
     * @Description 解锁
     * @Date 11:26 2020/5/23
     * @Param
     * @Version 1.0
     **/
    public void unlock() {
        // 把state更新成0,这里不需要原子更新,因为同时只有一个线程访问到这里
        state = 0;
        // 下一个待唤醒的节点
        Node next = head.next;
        // 下一个节点不为空,就唤醒它
        if (next != null) {
            unsafe.unpark(next.thread);
        }
    }

    private static int count = 0;

    //测试自己手写的锁
    public static void main(String[] args) throws InterruptedException {
        MyLock lock = new MyLock();
        CountDownLatch countDownLatch = new CountDownLatch(1000);

        IntStream.range(0, 1000).forEach(i -> new Thread(() -> {
            lock.lock();

            try {
                IntStream.range(0, 10000).forEach(j -> count++);
            } finally {
                lock.unlock();
            }

            countDownLatch.countDown();
        }, "thread-" + i).start());

        countDownLatch.await();

        System.out.println(count);
    }
}

推荐阅读

  1. 《深读源码-java魔法类之Unsafe解析》

  2. 《深读源码-java同步系列之JMM(Java Memory Model)》

  3. 《深读源码-java同步系列之volatile解析》

  4. 《深读源码-java同步系列之synchronized解析》


参考链接:https://www.cnblogs.com/tong-yuan/p/mylock.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一个Spring的码实现是一个非常复杂的任务,需要对Spring的核心原理和设计思想有入的理解。以下是一个简单的Spring码实现的示例,仅供参考。 首先,我们需要创建一个核心的容器类来管理Bean的生命周期和依赖注入。这个容器类需要实现BeanFactory接口,提供getBean()方法来获取Bean对象。 ```java public interface BeanFactory { Object getBean(String name); } ``` 接下来,我们需要实现一个简单的Bean定义类,用于描述Bean的属性和依赖关系。 ```java public class BeanDefinition { private Class<?> beanClass; private Map<String, Object> properties = new HashMap<>(); private List<PropertyValue> propertyValues = new ArrayList<>(); public void setBeanClass(Class<?> beanClass) { this.beanClass = beanClass; } public Class<?> getBeanClass() { return beanClass; } public void addProperty(String name, Object value) { properties.put(name, value); } public Object getProperty(String name) { return properties.get(name); } public List<PropertyValue> getPropertyValues() { return propertyValues; } public void addPropertyValue(PropertyValue propertyValue) { propertyValues.add(propertyValue); } } public class PropertyValue { private final String name; private final Object value; public PropertyValue(String name, Object value) { this.name = name; this.value = value; } public String getName() { return name; } public Object getValue() { return value; } } ``` 然后,我们需要实现一个简单的BeanFactory实现类,用于创建和管理Bean对象。这个实现类需要取Bean定义信息,创建Bean实例,并将其保存在一个Map中。 ```java public class SimpleBeanFactory implements BeanFactory { private final Map<String, BeanDefinition> beanDefinitions = new HashMap<>(); private final Map<String, Object> beans = new HashMap<>(); public SimpleBeanFactory(String configLocation) { loadBeanDefinitions(configLocation); createBeans(); } private void loadBeanDefinitions(String configLocation) { // 从配置文件中取Bean定义信息 } private void createBeans() { for (String beanName : beanDefinitions.keySet()) { createBean(beanName); } } private Object createBean(String beanName) { BeanDefinition beanDefinition = beanDefinitions.get(beanName); Class<?> beanClass = beanDefinition.getBeanClass(); Object bean = createInstance(beanClass); applyPropertyValues(bean, beanDefinition.getPropertyValues()); beans.put(beanName, bean); return bean; } private Object createInstance(Class<?> beanClass) { try { return beanClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } private void applyPropertyValues(Object bean, List<PropertyValue> propertyValues) { for (PropertyValue propertyValue : propertyValues) { String propertyName = propertyValue.getName(); Object value = propertyValue.getValue(); setPropertyValue(bean, propertyName, value); } } private void setPropertyValue(Object bean, String propertyName, Object value) { try { BeanUtils.setProperty(bean, propertyName, value); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } @Override public Object getBean(String name) { return beans.get(name); } } ``` 最后,我们需要实现一个简单的测试类来验证我们的实现是否正确。 ```java public class SimpleBeanFactoryTest { @Test public void testGetBean() { BeanFactory beanFactory = new SimpleBeanFactory("classpath:beans.xml"); TestBean testBean = (TestBean) beanFactory.getBean("testBean"); assertNotNull(testBean); assertNotNull(testBean.getDependency()); } } ``` 这只是一个简单的Spring码实现示例,实际上Spring的码实现要复杂得多,涉及到很多高级特性和设计模式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值