首页 > 其他 > 详细

自己手动写一个锁Lock

时间:2021-06-16 16:36:32      阅读:24      评论:0      收藏:0      [点我收藏+]

准备

自己实现一个锁我们要准备一个变量、一个队列、Unsafe类来执行CAS/park/unpark的Unsafe类。

一个变量

? 一个变量的值为1的时候就说明已加锁,变量值为0的时候就说明未加锁

一个队列

? 多个线程对同一个锁的争用肯定只有一个能成功,没有抢到锁的其它的线程就要排队,所以我们还需要一个队列

Unfafe类

? 多个线程只能有一个线程把变量的值修改为1,且当它的值为1的时候其它线程不能再修改它的值,我们使用Unsafe这个类来做CAS操作。而且排队的线程不能继续往下执行,只能阻塞。我们用Unsfae类来阻塞(park)和唤醒(unpark)线程。

大体的流程示意图如下:

技术分享图片

 

详细代码

主要代码

public class MyLock {

    //用来标记当前锁是否被线程占用,0:不占用 1:不占用
    private volatile int state;

    private Node empty = new Node();

    // 队列头
    private volatile Node head;
    // 队列尾
    private volatile Node tail;

    public MyLock() {
        head = tail = empty;
    }

    private static Unsafe unsafe;

    // state的偏移量
    private static long stateOffset;

    // tail的偏移量
    private static long tailOffset;

    static {
        try {
            // 获取Unsafe的实例
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
            // 获取state的偏移量
            stateOffset = unsafe.objectFieldOffset(MyLock.class.getDeclaredField("state"));
            // 获取tail的偏移量
            tailOffset = unsafe.objectFieldOffset(MyLock.class.getDeclaredField("tail"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    // 原子更新state字段
    private boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

    // 原子更新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 = enq();
        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;
    }

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

    /**
     * 没有抢到锁的线程入队列(入队过程是原子的)
     */
    private Node enq() {
        while (true) {
            Node t = tail;
            Node node = new Node(Thread.currentThread(), t);
            if (compareAndSetTail(t, node)) {
                node.prev = t;
                t.next = node;
                return node;
            }
        }
    }
}

 

测试类

public class TestMyLock {

    private static int count = 0;

    @Test
    public void testMyLock() 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();
        }, "tt" + i).start());

        countDownLatch.await();
        System.out.println(count);
    }
}

 

测试结果

未加锁打印:

技术分享图片

测试加锁后,是我们要的结果,锁有效

技术分享图片

 

详细调用过程

 

假设现在有3个线程进入lock方法

技术分享图片

 

? 线程1原子更新state成功然后去执行下面的逻辑。线程2和线程3没有抢到锁,都阻塞在unsafe.park(false, 0L)处。并且此时队列是这样的:Node(empty)—Node(线程2)—Node(线程3)

技术分享图片

 

? 线程1执行业务代码结束并执行unLock方法,执行unLock方法后,在unsafe.park(false, 0L)处阻塞的线程

技术分享图片

 

? 线程2在线程1执行unLock方法的unsafe.unpark(next.thread)被唤醒,再执行一次while (node.prev != head || !compareAndSetState(0, 1)),发现比对符合条件,执行下面的代码。并且队列现在变成Node(线程2)—Node(线程3)

技术分享图片

接下来就是重复上面的调用过程。

自己手动写一个锁Lock

原文:https://www.cnblogs.com/gaoweiBlog/p/14888295.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!