自己手动写一个锁Lock

准备

自己实现一个锁我们要准备一个变量、一个队列、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)

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

posted @ 2021-06-16 15:30  gaowei656  阅读(164)  评论(0)    收藏  举报