从 0 到 1 实现 ReentrantLock

news2024/11/24 8:04:36

虽然本文的标题是从 0 到 1 实现 ReentrantLock ,但是为了方便理解,我们先从一个问题出发:既然系统已经有 synchronized 关键字了,那么为什么还会出现 ReentrantLock 这种代码层面的锁?

这就要先回顾一下历史了:在 JDK 1.5 之前,synchronized 还没有经过优化,是一个重量级锁,会切换线程状态,比较耗时。那有没有其它的方式来保证线程同步呢?从上帝视角来说,java 中的 compareAndSet (即:CAS) 方法可以保证高并发下多线程安全地操作共享变量,那么利用 compareAndSet 方法实现一个工具类来模拟 synchronized 的效果也是有可能的。所以从 JDK 1.5 就添加了 ReentrantLock 及其相关类。

由于 ReentrantLock 是通过内部类实现 AbstractQueuedSynchronizer 来保证线程同步的,而 AbstractQueuedSynchronizer 又是个 模板类 ,为了适应不同的场景添加了许多和 没有直接关系的概念,理解起来比较抽象,所以换个角度:如果让我们自己实现一个线程同步工具,应该怎么做?

一、线程同步工具类雏形

首先,先想象一下这个锁工具应该怎么使用:

方式一

MyLock myLock = new MyLock();
// 被操作的共享变量
int count = 0;
new Thread(new Runnable() {
	// MyLock 保证只有在一个线程执行完方法 exc(Lockable lockable) 中的 Lockable 后,才能有另一个线程执行 Lockable
	myLock.exc(new Lockable() {
		@Override
		public boolean run(){
			count++;
			return true;
		}
	});
}).start();
new Thread(new Runnable() {
	myLock.exc(new Lockable() {
		@Override
		public boolean run(){
			count++;
			return true;
		}
	});
}).start();

先不管 MyLock 怎么实现上面的要求,那这种方式有什么缺点吗?

  • 高并发下会创建很多 Lockable 对象,占用内存比较多

方式二

MyLock myLock = new MyLock();
// 被操作的共享变量
int count = 0;
new Thread(new Runnable() {
	// MyLock.lock() 保证一个线程拿到锁后,其它线程都会堵塞在 lock() 方法,待此线程执行 myLock.release() 后,其它线程再争夺锁
	myLock.lock();
	try{
		count++;
	} catch(Exception e) {}
	finally{
		myLock.release();
	}
}).start();
new Thread(new Runnable() {
	myLock.lock();
	try{
		count++;
	} catch(Exception e) {}
	finally{
		myLock.release();
	}
}).start();

这种方式显然比方式一看起来优雅多了,那么如何实现此方式呢?

二、利用 CAS 实现线程同步

2.1 线程同步的简单实现

因为 CAS 是借助于 Unsafe 执行的,如果在高版本使用 Unsafe ,IDEA 会报下面的错误:

cannot access class jdk.internal.misc.Unsafe xxx

解决方法如下:

点击 “Edit Configurations…” 进入参数配置界面

在这里插入图片描述

点击 “Modify options” 会弹出 “Add Run Options” 弹窗,然后选则 Java 模块的 “Add VM options”,配置界面就会出现一个新的参数输入框(绿色框)

在这里插入图片描述在这里插入图片描述

绿色框中添加

--add-opens java.base/jdk.internal.misc=ALL-UNNAMED --illegal-access=warn

即可,下面是具体的实现方法:

public class MyLock {

    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(MyLock.class, "isLock");

    public final boolean compareAndSet(boolean expectedValue, boolean newValue) {
        return U.compareAndSetBoolean(this, VALUE, expectedValue, newValue);
    }
    // 为 true 时表示有线程获得锁
    private volatile boolean isLock;

    public void lock() {
        for (; ; ) {
            if (compareAndSet(false, true)) {
                // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                break;
            }
        }
    }

    public void unlock() {
        // 由于之前 lock 执行成功,所以 isLock 一定为 true,所以这里可以直接置为 false
        compareAndSet(true, false);
    }
}

public class MyLockTest {
    public static int count;

    public static void main(String[] args) throws InterruptedException {
        MyLock myLock = new MyLock();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
//                myLock.lock();
                try {
                    for (int i = 0; i < 100; i++) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count++;
                        System.out.println("Thread 1 and count:" + count);
                    }
                }catch (Exception e){

                }finally {
                    myLock.unlock();
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
//                myLock.lock();
                try {
                    for (int i = 0; i < 100; i++) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count++;
                        System.out.println("Thread 2 and count:" + count);
                    }
                }catch (Exception e){

                }finally {
                    myLock.unlock();
                }
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(count);
    }
}

运行上面代码,执行结果为:

...
Thread 1 and count:169
Thread 2 and count:169
Thread 1 and count:171
Thread 2 and count:170
Thread 2 and count:172
Thread 1 and count:172
172

可以看到在把 myLock.lock() 注释掉的情形下,Thread1 和 Thread2 交替执行,待都执行完毕后,最终结果是 172 和预期结果 200 不等。

将代码中 2 处 myLock.lock() 放开后,执行结果为:

Thread 1 and count:1
...// 这里都是 Thread 1
Thread 1 and count:100
Thread 2 and count:101
...// 这里都是 Thread 2	
Thread 2 and count:200
200

多次测试后结果均为 200 与预期结果一致,且是待一个线程处理完 共享资源 后另一个线程才会处理,所以我们写的同步工具 MyLock 实现了 synchronized 的功能。

2.2 解决锁被异常解除的问题

但是上面的写法没问题吗?

假设 Thread1 先执行,Thead2 被阻塞,如果此时另一个线程 Thread3 或者 main 线程,直接执行了 myLock.unlock() 方法,那么Thread2 就跳出了 myLock.lock() 中的 for 循环,和 Thread1 并行,这样就又出现线程同步问题。所以应该在 myLock.lock() 中记录获取到锁的线程,在 myLock.unlock() 中判断调用 unlock 的线程是否是获得锁的线程。修改后代码如下:

public class MyLock {

    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(MyLock.class, "isLock");
    // 持有锁的线程
    private Thread exclusiveOwnerThread;

    public final boolean compareAndSet(boolean expectedValue, boolean newValue) {
        return U.compareAndSetBoolean(this, VALUE, expectedValue, newValue);
    }

    private volatile boolean isLock;

    public void lock() {
        for (; ; ) {
            if (compareAndSet(false, true)) {
                Thread thread = Thread.currentThread();
                setExclusiveOwnerThread(thread);
                // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                break;
            }
        }
    }

    public void unlock() {
        Thread thread = Thread.currentThread();
        if (thread != exclusiveOwnerThread) {
            // 只有获得锁的线程才能释放锁,否则抛出异常
            throw new IllegalMonitorStateException();
        }
        // 由于之前 lock 执行成功,所以 isLock 一定为 true,所以这里可以直接置为 false
        compareAndSet(true, false);
        // 释放锁后,将持有锁的线程置为 null
        setExclusiveOwnerThread(null);
    }

    public Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }

    public void setExclusiveOwnerThread(Thread exclusiveOwnerThread) {
        this.exclusiveOwnerThread = exclusiveOwnerThread;
    }
}

2.3 解决锁重入的问题

锁被异常解除通过引入 ExclusiveOwnerThread 解决了,但又发现一个问题:如果在 Thread1 中调用了 2 次 myLock.lock() 会怎么样?并不是像下面这样直接调用 2 次

Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                myLock.lock();
                myLock.lock();
                ...

而是下面这种情况,在工作中很容易碰到:

public class MyLockTest {
    private int count;
    private MyLock myLock = new MyLock();
    // 模拟在 thread 执行过程中需要再执行的一个同步方法
    private void reentrantLock() {
        myLock.lock();
        System.out.println(Thread.currentThread().getName() + " reentrant lock");
        myLock.unlock();
    }
    public static void main(String[] args) throws InterruptedException {
        MyLockTest myLockTest = new MyLockTest();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                myLockTest.myLock.lock();
                try {
                    for (int i = 0; i < 100; i++) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        myLockTest.count++;
                        System.out.println("Thread 1 and count:" + myLockTest.count);
                        if (myLockTest.count == 50) {
                            myLockTest.reentrantLock();
                        }
                    }
                }catch (Exception e){

                }finally {
                    myLockTest.myLock.unlock();
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                myLockTest.myLock.lock();
                try {
                    for (int i = 0; i < 100; i++) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        myLockTest.count++;
                        System.out.println("Thread 2 and count:" + myLockTest.count);
                        if (myLockTest.count == 150) {
                            myLockTest.reentrantLock();
                        }
                    }
                }catch (Exception e){

                }finally {
                    myLockTest.myLock.unlock();
                }
            }
        });
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("thread3");
                myLockTest.myLock.unlock();
            }
        });
        thread1.start();
        thread2.start();
        thread3.start();
        thread1.join();
        thread2.join();
        thread3.join();
        System.out.println(myLockTest.count);
    }
}

假设 thread1 先获得了锁,然后执行 reentrantLock() 方法,由于 thread1 之前已经将 isLock 置为 true 了,所以 reentrantLock() 中的 myLock.lock() 会一直卡在 for 循环中。这样 thread1 就无法往下执行,也就不会释放锁,那么 thread2 也永远无法执行。既然 MyLock 中已存在 ExclusiveOwnerThread 变量,这就好办了,只需要在 lock() 方法中判断一下,让 reentrantLock() 不卡在 for 循环,代码如下:

    public void lock() {
        for (; ; ) {
            Thread thread = Thread.currentThread();
            if (compareAndSet(false, true)) {
                setExclusiveOwnerThread(thread);
                // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                break;
            } else if (thread == getExclusiveOwnerThread()) {
                break;
            }
        }
    }

运行代码发现的确是不会卡在 for 循环了,但是打印结果变成了下面这样:

Thread 1 and count:1
...// 都是 Thread 1
Thread 1 and count:50
// 这个 Thread-0 就是第一个启动的线程 Thead1
// reentrantLock() 方法执行后打印的
Thread-0 reentrant lock
// reentrantLock() 方法执行完毕后,线程乱序
Thread 1 and count:51
Thread 2 and count:52
Thread 2 and count:53
Thread 1 and count:54
...
Thread 2 and count:180
180

这是由于 thread1 执行了 reentrantLock() 中的 myLock.unlock() 后将 isLock 置为 false,释放了锁导致的;而调用 unlock() 又不能不释放锁且 unlock() 方法并不知道当前线程调用了多少次 lock() 。所以这里提供两种解决方案:

  • 为 reentrantLock() 添加一把新的锁
  • 对 lock() 成功的次数进行计数

2.3.1 添加新锁的方案为什么不可行

因为给 reentrantLock() 添加一把新锁 myLock2,会出现死锁的情形。

例如:当 thread1 在执行 reentrantLock() 前,thread4 执行 reentrantLock() 获得了 myLock2 的锁,然后去获得 thread1 的锁 myLock,此时 myLock 被 thread1 占用,thread4 阻塞;待 thread1 执行到 reentrantLock() 时,myLock2 又被 thread4 占有。这样就造成了 thread1 和 thread4 彼此等待对方释放锁的场景,产生了死锁问题。总结:多线程中共同操作多把锁有几率产生死锁问题。

2.3.2 对 lock() 成功的次数进行计数

注意新加的变量 lockCount

public class MyLock {

    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(MyLock.class, "isLock");

    private Thread exclusiveOwnerThread;
    // 记录 lock() 执行成功的次数
    private int lockCount;

    public final boolean compareAndSet(boolean expectedValue, boolean newValue) {
        return U.compareAndSetBoolean(this, VALUE, expectedValue, newValue);
    }

    private volatile boolean isLock;

    public void lock() {
        for (; ; ) {
            Thread thread = Thread.currentThread();
            if (compareAndSet(false, true)) {
                setExclusiveOwnerThread(thread);
                lockCount++;
                // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                break;
            } else if (thread == getExclusiveOwnerThread()) {
                // 因为只有先得到锁的线程才能执行到这,所以对于 lockCount 的操作,可以保证线程安全
                lockCount++;
                break;
            }
        }
    }

    public void unlock() {
        Thread thread = Thread.currentThread();
        if (thread != exclusiveOwnerThread) {
            // 只有获得锁的线程才能释放锁,否则抛出异常
            throw new IllegalMonitorStateException();
        }
        // 因为 unlock 只有在先得到锁的线程中才能正确执行,所以对于 lockCount 的操作,可以保证线程安全
        lockCount--;
        if (lockCount == 0) {
            // 由于之前 lock 执行成功,所以 isLock 一定为 true,所以这里可以直接置为 false
            compareAndSet(true, false);
            setExclusiveOwnerThread(null);
        }
    }

    public Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }

    public void setExclusiveOwnerThread(Thread exclusiveOwnerThread) {
        this.exclusiveOwnerThread = exclusiveOwnerThread;
    }
}

三、公平锁与非公平锁

经过上面的一系列调整 MyLock 已经实现了一个锁的基本功能了。那么当第一个获得锁的线程释放锁后,其它线程该怎么获得锁呢?例如下面的情形,开始的时候创建了 5 个线程:

public class MyLockTest {
    private int count;
    private MyLock myLock = new MyLock();
    private void reentrantLock() {
        myLock.lock();
        System.out.println(Thread.currentThread().getName() 
            + " reentrant lock");
        myLock.unlock();
    }
    public static void main(String[] args) throws InterruptedException {
        MyLockTest myLockTest = new MyLockTest();
        Runnable runnable = () -> {
            myLockTest.myLock.lock();
            try {
                for (int i = 0; i < 100; i++) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    myLockTest.count++;
                    System.out.println(Thread.currentThread().getName() 
                        + " and count:" + myLockTest.count);
                    if (i == 50) {
                        myLockTest.reentrantLock();
                    }
                }
            }catch (Exception e){

            }finally {
                myLockTest.myLock.unlock();
            }
        };
        Thread thread;
        for (int i = 0; i < 5; i++) {
            thread = new Thread(runnable, "Thread " + (i + 1));
            thread.start();
            thread.join();
        }
        System.out.println(myLockTest.count);
    }
}

当第一个线程释放锁时,其它四个线程都卡在 myLock.lock() 的 for 循环中,至于谁能第二个抢到锁就凭运气了,不会按照调用 myLock.lock() 的先后顺序执行。这种不以申请锁的先后顺序执行,而是通过抢占锁执行的方式叫作 非公平锁;于此相反按照申请锁的顺序执行的方式叫作 公平锁。总结一下就是:

公平锁:每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁。

非公平锁:每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,所有线程会竞争获取锁。

既然现在的 MyLock 是非公平锁,那么公平锁应该怎么实现?

3.1 实现公平锁

线程的公平锁是按照申请锁的先后顺序获取的,那么就需要在 lock() 方法中记录下线程的顺序。代码如下:

public class MyLock {

    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(MyLock.class, "isLock");

    private boolean fair;

    public MyLock() {}

    public MyLock(boolean fair) {
        this.fair = fair;
    }

    private Thread exclusiveOwnerThread;
    // 记录 lock() 执行成功的次数
    private int lockCount;
    private List<Node> threadOrder = new ArrayList<>();

    private static final class Node {

        public Node(Thread thread) {
            this.thread = thread;
        }
        // 记录申请锁的线程
        Thread thread;
    }

    public final boolean compareAndSet(boolean expectedValue, boolean newValue) {
        return U.compareAndSetBoolean(this, VALUE, expectedValue, newValue);
    }

    private volatile boolean isLock;

    public void lock() {
        if (fair) {
            tryAcquireFair();
        } else {
            tryAcquireUnfair();
        }
    }

    public void tryAcquireUnfair() {
        for (; ; ) {
            Thread thread = Thread.currentThread();
            if (compareAndSet(false, true)) {
                setExclusiveOwnerThread(thread);
                lockCount++;
                // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                break;
            } else if (thread == getExclusiveOwnerThread()) {
                lockCount++;
                break;
            }
        }
    }

    public void tryAcquireFair() {
        threadOrder.add(new Node(Thread.currentThread()));
        for (; ; ) {
            Thread thread = Thread.currentThread();
            Node node = null;
            try {
                // 由于 threadOrder 不是线程安全的,且在 unlock 方法中会移除一个 node 节点
                // 这里有可能会数组越界
                node = threadOrder.get(0);
            }catch (Exception e){
                // 当数组越界时,说明还未执行的 node 节点被删除,会导致下面的匹配一直不成功,
                // 卡在 for 循环中
            }
            if (node != null && thread == node.thread) {
                if (compareAndSet(false, true)) {
                    setExclusiveOwnerThread(thread);
                    lockCount++;
                    // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                    return;
                } else if (thread == getExclusiveOwnerThread()) {
                    lockCount++;
                    return;
                }
            }
        }
    }

    public void unlock() {
        Thread thread = Thread.currentThread();
        if (thread != exclusiveOwnerThread) {
            System.out.println("before crash exclusiveOwnerThread is "
                    + exclusiveOwnerThread);
            // 只有获得锁的线程才能释放锁,否则抛出异常
            throw new IllegalMonitorStateException();
        }
        // 因为 unlock 只有在先得到锁的线程中才能正确执行,所以对于 lockCount 的操作,可以保证线程安全
        lockCount--;
        if (lockCount == 0) {
            int deletePosition = -1;
            for (int i = 0; i < threadOrder.size(); i++) {
                if (thread == threadOrder.get(i).thread) {
                    deletePosition = i;
                } else {
                    if (deletePosition > -1) {
                        threadOrder.set(i - 1, threadOrder.get(i));
                    }
                }
            }
            threadOrder.remove(threadOrder.size() - 1);
            setExclusiveOwnerThread(null);
            // 由于之前 lock 执行成功,所以 isLock 一定为 true,所以这里可以直接置为 false
            compareAndSet(true, false);
        }
    }

    public Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }

    public void setExclusiveOwnerThread(Thread exclusiveOwnerThread) {
        this.exclusiveOwnerThread = exclusiveOwnerThread;
    }
}
public class MyLockTest {
    private int count;
    private MyLock myLock = new MyLock(true);
    public static void main(String[] args) throws InterruptedException {
        MyLockTest myLockTest = new MyLockTest();
        Runnable runnable = () -> {
            myLockTest.myLock.lock();
            // 打印执行的顺序
            System.out.println(Thread.currentThread().getName());
            myLockTest.count++;
            myLockTest.myLock.unlock();
        };

        Thread thread;
        for (int i = 0; i < 5; i++) {
            thread = new Thread(runnable, "Thread " + (i + 1));
            thread.start();
        }
        Thread.sleep(5000);
        System.out.println(myLockTest.count);
    }
}

执行上面代码后,电脑风扇疯狂转,且输出结果为:

// 这个顺序是调用 myLock.lock() 的顺序,不是 new Thread 的顺序
Thread 1
Thread 5
Thread 3
Thread 2
4

Process finished with exit code 130

明显少执行了 Thread4,这是因为对 threadOrder 的操作是非线程安全的,在 unlock 中整理数组长度(不整理数据长度会造成数组中有很多空数据,随着程序运行时间的增加,会导致内存浪费和遍历耗时增加)时,将 thread4 对应的 Node 节点删除了,thread4 一直匹配不到卡在了 for 循环中。

既然数组存储不能保证线程安全,那么使用链表呢?

链表的实现方式:

public class MyLock {

    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(MyLock.class, "isLock");

    // 是否使用公平锁
    private boolean fair;

    public MyLock() {}

    public MyLock(boolean fair) {
        this.fair = fair;
    }

    private Thread exclusiveOwnerThread;
    // 记录单个线程 lock() 执行成功的次数
    private int lockCount;
    private volatile Node head;
    private static final long HEAD = U.objectFieldOffset(MyLock.class, "head");
    public final boolean headCompareAndSet(Node oldHead, Node newHead) {
        return U.compareAndSetObject(this, HEAD, oldHead, newHead);
    }

    private volatile Node tail;
    private static final long TAIL = U.objectFieldOffset(MyLock.class, "tail");

    public final boolean tailInitCompareAndSet() {
        return U.compareAndSetObject(this, TAIL, null, head);
    }

    public final boolean tailToNextCompareAndSet() {
        return U.compareAndSetObject(this, TAIL, tail, tail.next);
    }
    private static final long TAIL_NEXT = U.objectFieldOffset(Node.class, "next");
    public final boolean tailInitNextCompareAndSet(Node tailNext) {
        return U.compareAndSetObject(tail, TAIL_NEXT, null, tailNext);
    }

    private static final class Node {
        static final int FINISH = 1;
        volatile int waitStatus;
        volatile Node next;
        public Node(Thread thread) {
            this.thread = thread;
        }
        // 记录申请锁的线程
        volatile Thread thread;

        @Override
        public String toString() {
            return thread.getName() + " next:" + (next == null ? next : next.thread.getName());
        }
    }

    public final boolean compareAndSet(boolean expectedValue, boolean newValue) {
        return U.compareAndSetBoolean(this, VALUE, expectedValue, newValue);
    }

    private volatile boolean isLock;

    public void lock() {
        if (fair) {
            tryAcquireFair();
        } else {
            tryAcquireUnfair();
        }
    }

    public void tryAcquireUnfair() {
        for (; ; ) {
            Thread thread = Thread.currentThread();
            if (compareAndSet(false, true)) {
                setExclusiveOwnerThread(thread);
                lockCount++;
                // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                break;
            } else if (thread == getExclusiveOwnerThread()) {
                lockCount++;
                break;
            }
        }
    }

    /**
     * 用于对比线程的执行顺序和调用 lock() 的顺序是否一致,后面可以删掉
     */
    private List<String> threadNames = new ArrayList<>();
    public void tryAcquireFair() {
        Thread thread = Thread.currentThread();
        Node node = new Node(thread);
        if (headCompareAndSet(null, node)){
            threadNames.add(node.thread.getName());
            tailInitCompareAndSet();
        } else {
            // 其它没成功设置 head 的线程都进入这个死循环,直到 tail 也设置完成
            for (;;) {
                if (tail != null) {
                    for (;;) {
                        // 一直循环直到给 tail.next 设置成功
                        // 之所以用循环,是因为可能多个线程在竞争执行 tailInitNextCompareAndSet
                        // 当一个线程成功给 tail.next 设置好 node 值后,
                        // tailToNextCompareAndSet 重置 tail
                        if (tailInitNextCompareAndSet(node)){
                            // 这里必须使用 oldHead 往 headCompareAndSet 传值
                            // 因为这里的 headCompareAndSet 和 unlock 里的 headCompareAndSet 有可能都在等待执行,
                            // 必须保证有且只有一个执行成功。
                            Node oldHead = head;
                            if (oldHead.waitStatus == Node.FINISH && oldHead.next != null) {
                                // 1.如果 head 线程已经结束,且 unlock 中的 head.next == null,
                                // 那么在加入一个新的尾节点后,head.next 一定不为 null,节点应往下走一步
                                // 2.如果 head 线程结束后,unlock 中的 headCompareAndSet 执行成功,
                                // head 会往前移动一步,那么当这里的 oldHead 是 head 移动前的 Node 时,
                                // 下面执行不成功;当 oldHead 是新节点时 waitStatus != Node.FINISH,
                                // 若新的 head 又走到了 unlock 的 headCompareAndSet 时,这就和第一种情况一样了。
                                if (headCompareAndSet(oldHead, oldHead.next)) {
                                    // 已经执行过的 Node 断开链,在 GC 时进行回收
                                    oldHead.next = null;
                                }
                            }
                            threadNames.add(node.thread.getName());
                            tailToNextCompareAndSet();
                            break;
                        }
                    }
                    // 成功追加到 tail,跳出循环
                    break;
                }
                // tail 为 null 时说明非 head 线程还没有加入到 Node 节点中,需要一直循环直到加入
            }
        }
        for (;;) {
            if (head != null && thread == head.thread) {
                if (compareAndSet(false, true)) {
                    setExclusiveOwnerThread(thread);
                    lockCount++;
                    // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                    break;
                } else if (thread == getExclusiveOwnerThread()) {
                    lockCount++;
                    break;
                }
            }
        }
    }

    public void unlock() {
        Thread thread = Thread.currentThread();
        if (thread != exclusiveOwnerThread) {
            System.out.println("before crash exclusiveOwnerThread is "
                    + exclusiveOwnerThread);
            // 只有获得锁的线程才能释放锁,否则抛出异常
            throw new IllegalMonitorStateException();
        }
        // 因为 unlock 只有在先得到锁的线程中才能正确执行,所以对于 lockCount 的操作,可以保证线程安全
        lockCount--;
        if (lockCount == 0) {
            if (head != null) {
                // 这里必须使用 oldHead 往 headCompareAndSet 传值
                // 因为这里的 headCompareAndSet 和 tryAcquireFair 里的 headCompareAndSet 有可能都在等待执行,
                // 必须保证有且只有一个执行成功。
                Node oldHead = head;
                oldHead.waitStatus = Node.FINISH;
                if (oldHead.next != null) {
                    // head.next == null 时,并不能说明所有线程已执行完毕,
                    // 有可能还有线程未加入 Node 节点中
                    if (headCompareAndSet(oldHead, oldHead.next)) {
                        // 已经执行过的 Node 断开链,在 GC 时进行回收
                        oldHead.next = null;
                    }
                }
            }
            setExclusiveOwnerThread(null);
            // 由于之前 lock 执行成功,所以 isLock 一定为 true,所以这里可以直接置为 false
            compareAndSet(true, false);
        }
    }

    public Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }

    public void setExclusiveOwnerThread(Thread exclusiveOwnerThread) {
        this.exclusiveOwnerThread = exclusiveOwnerThread;
    }
}

public class MyLockTest {

    private List<String> threadNames = new ArrayList<>();
    private int count;
    private int threadCount = 338;
    private long startTime;
    private MyLock myLock = new MyLock(true);
    public static void main(String[] args) throws InterruptedException {
        MyLockTest myLockTest = new MyLockTest();
        Runnable runnable = () -> {
            myLockTest.myLock.lock();
            myLockTest.threadNames.add(Thread.currentThread().getName());
            myLockTest.count++;
            if (myLockTest.count == 1) {
                myLockTest.startTime = System.currentTimeMillis();
            } else if (myLockTest.count == myLockTest.threadCount) {
                System.out.println("total time:" + (System.currentTimeMillis() - myLockTest.startTime));
            }
            // 打印执行的顺序
            System.out.println(myLockTest.count + "  ---  " + Thread.currentThread().getName());
            myLockTest.myLock.unlock();
        };

        Thread thread;
        for (int i = 0; i < myLockTest.threadCount; i++) {
            thread = new Thread(runnable, "Thread " + (i + 1));
            thread.start();
        }
        Thread.sleep(10000);
        // 打印一下各个线程的实际运行次序
        System.out.println("thread opera orders:" + myLockTest.threadNames);
        System.out.println(myLockTest.count);
    }
}

经过测试使用链表的方式是可以的,但要注意里面的逻辑比较绕。这样我们就得到了一个公平锁的写法,虽然相对于非公平锁麻烦多了。

四、优化

4.1 自旋锁与阻塞锁

4.1.1 自旋锁

执行上面的代码后,打印结果如下:

...
336  ---  Thread 67
337  ---  Thread 62
total time:84260
338  ---  Thread 15

平均每个线程耗时 249ms ,而线程中又没有耗时任务,理论上不可能耗时这么久。显然是因为线程争抢锁资源时,for 循环中的 CAS 总是返回 false,然后一直执行 for 循环导致的。

像这种在解决多线程问题时,获取锁失败后,通过死循环的方式重新获取锁,直到成功为止。这种方案叫 自旋锁

4.1.1.1 乐观锁

另外,CAS 即 Compare And Swap(比较与交换),是一种 无锁算法,即在不使用锁的情况下实现多线程之间变量的同步,所以也叫 非阻塞同步(Non-blocking Synchronization)。

像 CAS 这样,假设多个线程在操作共享数据时,不会发生冲突,因此不需要加锁,而是在更新数据时,通过比较当前状态和上一次的状态,来判断是否有其他线程修改了数据。如果没有冲突,就执行更新操作,否则就重试或者放弃。这种方式叫作 乐观锁

乐观锁的优点

  • 减少了锁的开销,提高了并发性能;

乐观锁的缺点

  1. ABA 问题。 即如果一个变量 V 初次读取时值是A,并且在准备赋值时检查它的值仍然是 A,那能说明它的值没有被其他线程修改过吗?显然不能。因为可能在准备赋值前,V 的值被其他线程修改成了其他值 B,然后又修改成了 A,那么 CAS 操作就会误认为它从来没有被修改过,这个就是 CAS 操作的 ABA 问题

  2. 循环时间长开销大。 自旋 CAS,不成功就会一直循环执行直到成功,如果长时间不成功,会给CPU带来很大的开销。

  3. 只能保证一个共享变量的原子操作。 CAS 只对单个共享变量有效,当操作涉及到多个共享变量时CAS 无效。但在 JDK1.5 开始,提供了AtomicReference 类来保证引用对象之间的原子性,可以把多个变量放在一个对象里进行 CAS 操作。所以可以使用锁或者是利用 AtomicReference 类把多个共享变量合并成一个共享变量来操作

乐观锁使用场景

适用于线程冲突较少的情景,一般在写操作比较少,读操作比较多的情况下推荐使用

4.1.2 阻塞锁

对于自旋锁在大量线程并发下,缺点很明显,CPU资源耗尽、应用卡死、手机发烫等(例如上面单线程的平局耗时为 249ms)。那么怎么解决呢?

我们可以在第一次获锁失败后,将当前线程阻塞,有锁资源时再唤醒。这种方案叫 阻塞锁

由于 JDK 中已提供了线程的阻塞方法 LockSupport.park() 所以我们就不需要自己实现了。LockSupport 类内容很少,其本质还是使用 Unsafe 实现的一系列静态方法。

4.1.2.1 公平锁中实现阻塞锁

下面将代码改成阻塞锁的模式:

public class MyLock {

    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(MyLock.class, "isLock");

    // 是否使用公平锁
    private boolean fair;

    public MyLock() {}

    public MyLock(boolean fair) {
        this.fair = fair;
    }

    private Thread exclusiveOwnerThread;
    // 记录 lock() 执行成功的次数
    private int lockCount;
    private volatile Node head;
    private static final long HEAD = U.objectFieldOffset(MyLock.class, "head");
    public final boolean headCompareAndSet(Node oldHead, Node newHead) {
        return U.compareAndSetObject(this, HEAD, oldHead, newHead);
    }

    private volatile Node tail;
    private static final long TAIL = U.objectFieldOffset(MyLock.class, "tail");

    public final boolean tailInitCompareAndSet() {
        return U.compareAndSetObject(this, TAIL, null, head);
    }

    public final boolean tailToNextCompareAndSet() {
        return U.compareAndSetObject(this, TAIL, tail, tail.next);
    }
    private static final long TAIL_NEXT = U.objectFieldOffset(Node.class, "next");
    public final boolean tailInitNextCompareAndSet(Node tailNext) {
        return U.compareAndSetObject(tail, TAIL_NEXT, null, tailNext);
    }

    private static final class Node {
        static final int FINISH = 1;
        volatile int waitStatus;
        volatile Node next;
        public Node(Thread thread) {
            this.thread = thread;
        }
        // 记录申请锁的线程
        volatile Thread thread;

        @Override
        public String toString() {
            return thread.getName() + " next:" + (next == null ? next : next.thread.getName());
        }
    }

    public final boolean compareAndSet(boolean expectedValue, boolean newValue) {
        return U.compareAndSetBoolean(this, VALUE, expectedValue, newValue);
    }

    private volatile boolean isLock;

    public void lock() {
        if (fair) {
            tryAcquireFair();
        } else {
            tryAcquireUnfair();
        }
    }

    public void tryAcquireUnfair() {
        for (; ; ) {
            Thread thread = Thread.currentThread();
            if (compareAndSet(false, true)) {
                setExclusiveOwnerThread(thread);
                lockCount++;
                // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                break;
            } else if (thread == getExclusiveOwnerThread()) {
                lockCount++;
                break;
            }
        }
    }

    /**
     * 用于对比线程的执行顺序和调用 lock() 的顺序是否一致,后面可以删掉
     */
    private List<String> threadNames = new ArrayList<>();
    public void tryAcquireFair() {
        Thread thread = Thread.currentThread();
        Node node = new Node(thread);
        if (headCompareAndSet(null, node)){
            threadNames.add(node.thread.getName());
            tailInitCompareAndSet();
        } else {
            // 其它没成功设置 head 的线程都进入这个死循环,直到 tail 也设置完成
            for (;;) {
                if (tail != null) {
                    for (;;) {
                        // 一直循环直到给 tail.next 设置成功
                        // 之所以用循环,是因为可能多个线程在竞争执行 tailInitNextCompareAndSet
                        // 当一个线程成功给 tail.next 设置好 node 值后,
                        // tailToNextCompareAndSet 重置 tail
                        if (tailInitNextCompareAndSet(node)){
                            // 这里必须使用 oldHead 往 headCompareAndSet 传值
                            // 因为这里的 headCompareAndSet 和 unlock 里的 headCompareAndSet 有可能都在等待执行,
                            // 必须保证有且只有一个执行成功。
                            Node oldHead = head;
                            if (oldHead.waitStatus == Node.FINISH && oldHead.next != null) {
                                // 1.如果 head 线程已经结束,且 unlock 中的 head.next == null,
                                // 那么在加入一个新的尾节点后,head.next 一定不为 null,节点应往下走一步
                                // 2.如果 head 线程结束后,unlock 中的 headCompareAndSet 执行成功,
                                // head 会往前移动一步,那么当这里的 oldHead 是 head 移动前的 Node 时,
                                // 下面执行不成功;当 oldHead 是新节点时 waitStatus != Node.FINISH,
                                // 若新的 head 又走到了 unlock 的 headCompareAndSet 时,这就和第一种情况一样了。
                                if (headCompareAndSet(oldHead, oldHead.next)) {
                                    // 已经执行过的 Node 断开链,在 GC 时进行回收
                                    oldHead.next = null;
                                    LockSupport.unpark(head.thread);
                                }
                            }
                            threadNames.add(node.thread.getName());
                            tailToNextCompareAndSet();
                            break;
                        }
                    }
                    // 成功追加到 tail,跳出循环
                    break;
                }
                // tail 为 null 时说明非 head 线程还没有加入到 Node 节点中,需要一直循环直到加入
            }
        }
        for (;;) {
            if (head != null && thread == head.thread) {
                if (compareAndSet(false, true)) {
                    setExclusiveOwnerThread(thread);
                    lockCount++;
                    // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                    break;
                } else if (thread == getExclusiveOwnerThread()) {
                    lockCount++;
                    break;
                }
            } else {
                // 当当前线程不是 head.thread 线程时,在公平锁的机制下不应该获取到锁,所以应该阻塞一下
                LockSupport.park();
            }
        }
    }

    public void unlock() {
        Thread thread = Thread.currentThread();
        if (thread != exclusiveOwnerThread) {
            System.out.println("before crash exclusiveOwnerThread is "
                    + exclusiveOwnerThread);
            // 只有获得锁的线程才能释放锁,否则抛出异常
            throw new IllegalMonitorStateException();
        }
        // 因为 unlock 只有在先得到锁的线程中才能正确执行,所以对于 lockCount 的操作,可以保证线程安全
        lockCount--;
        if (lockCount == 0) {
            if (head != null) {
                // 这里必须使用 oldHead 往 headCompareAndSet 传值
                // 因为这里的 headCompareAndSet 和 tryAcquireFair 里的 headCompareAndSet 有可能都在等待执行,
                // 必须保证有且只有一个执行成功。
                Node oldHead = head;
                oldHead.waitStatus = Node.FINISH;
                if (oldHead.next != null) {
                    // head.next == null 时,并不能说明所有线程已执行完毕,
                    // 有可能还有线程未加入 Node 节点中
                    if (headCompareAndSet(oldHead, oldHead.next)) {
                        // 已经执行过的 Node 断开链,在 GC 时进行回收
                        oldHead.next = null;
                        LockSupport.unpark(head.thread);
                    }
                }
            }
            setExclusiveOwnerThread(null);
            // 由于之前 lock 执行成功,所以 isLock 一定为 true,所以这里可以直接置为 false
            compareAndSet(true, false);
        }
    }

    public Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }

    public void setExclusiveOwnerThread(Thread exclusiveOwnerThread) {
        this.exclusiveOwnerThread = exclusiveOwnerThread;
    }
}

运行上面代码,打印输出结果:

...
total time:66
338  ---  Thread 338

之前的总耗时为 84260ms,现在为 66ms,性能提高了约 1277 倍。

4.1.2.2 非公平锁中实现阻塞锁

在非公平锁模式中线程只抢占 CAS,而不需要判断是否符合执行顺序,理论上耗时应该远小于公平锁。

下面看一下实现阻塞前非公平锁的耗时情况:

...
337  ---  Thread 272
total time:14341
338  ---  Thread 307

执行结果与预期一致。下面是设置阻塞后的代码:

public class MyLock {

    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(MyLock.class, "isLock");

    // 是否使用公平锁
    private boolean fair;

    public MyLock() {
        this(false);
    }

    public MyLock(boolean fair) {
        this.fair = fair;
        if (!this.fair) {
            head = Node.HEAD;
            tail = head;
        }
    }

    private Thread exclusiveOwnerThread;
    // 记录 lock() 执行成功的次数
    private int lockCount;
    private volatile Node head;
    private static final long HEAD = U.objectFieldOffset(MyLock.class, "head");
    public final boolean headCompareAndSet(Node oldHead, Node newHead) {
        return U.compareAndSetObject(this, HEAD, oldHead, newHead);
    }

    private volatile Node tail;
    private static final long TAIL = U.objectFieldOffset(MyLock.class, "tail");

    public final boolean tailInitCompareAndSet() {
        return U.compareAndSetObject(this, TAIL, null, head);
    }

    public final boolean tailToNextCompareAndSet() {
        return U.compareAndSetObject(this, TAIL, tail, tail.next);
    }

    private static final long NODE_NEXT = U.objectFieldOffset(Node.class, "next");
    public final boolean tailInitNextCompareAndSet(Node tailNext) {
        return U.compareAndSetObject(tail, NODE_NEXT, null, tailNext);
    }

    public final boolean nodeSetNextCompareAndSet(Node node, Node nextExpected, Node nextNode) {
        return U.compareAndSetObject(node, NODE_NEXT, nextExpected, nextNode);
    }

    private static final class Node {
        static final Node HEAD = new Node(null);
        static final int FINISH = 1;
        volatile int waitStatus;
        volatile Node next;
        public Node(Thread thread) {
            this.thread = thread;
        }
        // 记录申请锁的线程
        volatile Thread thread;

        @Override
        public String toString() {
            return (thread == null ? thread : thread.getName()) 
                + " next:" 
                + (next != null && next.thread != null ? next.thread.getName() : "");
        }
    }

    public final boolean compareAndSet(boolean expectedValue, boolean newValue) {
        return U.compareAndSetBoolean(this, VALUE, expectedValue, newValue);
    }

    private volatile boolean isLock;

    public void lock() {
        if (fair) {
            tryAcquireFair();
        } else {
            tryAcquireUnfair();
        }
    }

    public void tryAcquireUnfair() {
        Thread thread = Thread.currentThread();
        Node node = new Node(thread);
        for (;;) {
            if (tailInitNextCompareAndSet(node)){
                threadNames.add(node.thread.getName());
                tailToNextCompareAndSet();
                break;
            }
        }
        for (;;) {
            if (compareAndSet(false, true)) {
                setExclusiveOwnerThread(thread);
                lockCount++;
                // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                break;
            } else if (thread == getExclusiveOwnerThread()) {
                lockCount++;
                break;
            }
            LockSupport.park();
        }
    }

    /**
     * 用于对比线程的执行顺序和调用 lock() 的顺序是否一致,后面可以删掉
     */
    private List<String> threadNames = new ArrayList<>();
    public void tryAcquireFair() {
        Thread thread = Thread.currentThread();
        Node node = new Node(thread);
        if (headCompareAndSet(null, node)){
            threadNames.add(node.thread.getName());
            tailInitCompareAndSet();
        } else {
            // 其它没成功设置 head 的线程都进入这个死循环,直到 tail 也设置完成
            for (;;) {
                if (tail != null) {
                    for (;;) {
                        // 一直循环直到给 tail.next 设置成功
                        // 之所以用循环,是因为可能多个线程在竞争执行 tailInitNextCompareAndSet
                        // 当一个线程成功给 tail.next 设置好 node 值后,
                        // tailToNextCompareAndSet 重置 tail
                        if (tailInitNextCompareAndSet(node)){
                            // 这里必须使用 oldHead 往 headCompareAndSet 传值
                            // 因为这里的 headCompareAndSet 和 unlock 里的 headCompareAndSet 有可能都在等待执行,
                            // 必须保证有且只有一个执行成功。
                            Node oldHead = head;
                            if (oldHead.waitStatus == Node.FINISH && oldHead.next != null) {
                                // 1.如果 head 线程已经结束,且 unlock 中的 head.next == null,
                                // 那么在加入一个新的尾节点后,head.next 一定不为 null,节点应往下走一步
                                // 2.如果 head 线程结束后,unlock 中的 headCompareAndSet 执行成功,
                                // head 会往前移动一步,那么当这里的 oldHead 是 head 移动前的 Node 时,
                                // 下面执行不成功;当 oldHead 是新节点时 waitStatus != Node.FINISH,
                                // 若新的 head 又走到了 unlock 的 headCompareAndSet 时,这就和第一种情况一样了。
                                if (headCompareAndSet(oldHead, oldHead.next)) {
                                    // 已经执行过的 Node 断开链,在 GC 时进行回收
                                    oldHead.next = null;
                                    LockSupport.unpark(head.thread);
                                }
                            }
                            threadNames.add(node.thread.getName());
                            tailToNextCompareAndSet();
                            break;
                        }
                    }
                    // 成功追加到 tail,跳出循环
                    break;
                }
                // tail 为 null 时说明非 head 线程还没有加入到 Node 节点中,需要一直循环直到加入
            }
        }
        for (;;) {
            if (head != null && thread == head.thread) {
                if (compareAndSet(false, true)) {
                    setExclusiveOwnerThread(thread);
                    lockCount++;
                    // CAS 返回 true 则另一个线程调用 lock() 肯定会返回 false,那么就会一直卡在 for 循环中
                    break;
                } else if (thread == getExclusiveOwnerThread()) {
                    lockCount++;
                    break;
                }
            } else {
                // 当当前线程不是 head.thread 线程时,在公平锁的机制下不应该获取到锁,所以应该阻塞一下
                LockSupport.park();
            }
        }
    }

    public void unlock() {
        Thread thread = Thread.currentThread();
        if (thread != exclusiveOwnerThread) {
            System.out.println("before crash exclusiveOwnerThread is "
                    + exclusiveOwnerThread);
            // 只有获得锁的线程才能释放锁,否则抛出异常
            throw new IllegalMonitorStateException();
        }
        // 因为 unlock 只有在先得到锁的线程中才能正确执行,所以对于 lockCount 的操作,可以保证线程安全
        lockCount--;
        if (lockCount == 0) {
            if (fair) {
                Node oldHead = head;
                oldHead.waitStatus = Node.FINISH;
                oldHead.thread = null;
                // 这里必须使用 oldHead 往 headCompareAndSet 传值
                // 因为这里的 headCompareAndSet 和 tryAcquireFair 里的 headCompareAndSet 有可能都在等待执行,
                // 必须保证有且只有一个执行成功。
                if (oldHead.next != null) {
                    // head.next == null 时,并不能说明所有线程已执行完毕,
                    // 有可能还有线程未加入 Node 节点中
                    if (headCompareAndSet(oldHead, oldHead.next)) {
                        // 已经执行过的 Node 断开链,在 GC 时进行回收
                        oldHead.next = null;
                        LockSupport.unpark(head.thread);
                    }
                }
            } else {
                Node nextNode;
                Node currentNode = Node.HEAD.next;
                Node prevNode = Node.HEAD;
                while (currentNode != null) {
                    if (currentNode.thread == thread) {
                        currentNode.waitStatus = Node.FINISH;
                        currentNode.thread = null;
                    }
                    nextNode = currentNode.next;
                    if (currentNode.thread == null && nextNode != null) {
                        // 删除已执行的节点,prevNode 不变
                        prevNode.next = nextNode;
                        // 将 currentNode.next 置为 null,会报
                        // Could not load hsdis-amd64.dll; library not loadable; PrintAssembly is disabled
                        // 错误,所以这里注释掉,因为没有引用 currentNode 的节点了,所以不影响 GC
                        // currentNode.next = null;
                        currentNode = nextNode;
                        continue;
                    }
                    prevNode = currentNode;
                    currentNode = currentNode.next;
                }
            }
            setExclusiveOwnerThread(null);
            // 由于之前 lock 执行成功,所以 isLock 一定为 true,所以这里可以直接置为 false
            compareAndSet(true, false);
            if (fair) {
                LockSupport.unpark(head.thread);
            } else {
                Node currentNode = Node.HEAD.next;
                while (currentNode != null) {
                    // 唤醒所有线程重新竞争锁
                    LockSupport.unpark(currentNode.thread);
                    currentNode = currentNode.next;
                }
            }
        }
    }

    public void printAllNodes() {
        Node node;
        String str = null;
        if (fair) {
            node = head;
            str = "all fair nodes:";
        } else {
            node = Node.HEAD.next;
            str = "all unfair nodes:";
        }
        while (node != null) {
            System.out.println(str + node.thread + " next:" + node.next);
            node = node.next;
        }
    }

    public Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }

    public void setExclusiveOwnerThread(Thread exclusiveOwnerThread) {
        this.exclusiveOwnerThread = exclusiveOwnerThread;
    }
}

public class MyLockTest {

    private List<String> threadNames = new ArrayList<>();
    private int count;
    private int threadCount = 338;
    private long startTime;
    private MyLock myLock = new MyLock(false);
    public static void main(String[] args) throws InterruptedException {
        MyLockTest myLockTest = new MyLockTest();
        Runnable runnable = () -> {
            myLockTest.myLock.lock();
            myLockTest.threadNames.add(Thread.currentThread().getName());
            myLockTest.count++;
            // 打印执行的顺序
            System.out.println(myLockTest.count + "  ---  " + Thread.currentThread().getName());
            if (myLockTest.count == 1) {
                myLockTest.startTime = System.currentTimeMillis();
            } else if (myLockTest.count == myLockTest.threadCount) {
                System.out.println("total time:" + (System.currentTimeMillis() - myLockTest.startTime));
            }
            myLockTest.myLock.unlock();
        };
        Thread thread;
        for (int i = 0; i < myLockTest.threadCount; i++) {
            thread = new Thread(runnable, "Thread " + (i + 1));
            thread.start();
        }
        Thread.sleep(5000);
        // 打印一下各个线程的实际运行次序
        System.out.println("thread opera orders:" + myLockTest.threadNames);
        System.out.println(myLockTest.count);
        myLockTest.myLock.printAllNodes();
    }
}

执行代码,打印输出内容:

1  ---  Thread 1
2  ---  Thread 2
3  ---  Thread 40
4  ---  Thread 65
...
338  ---  Thread 338
total time:33
thread opera orders:[Thread 1 ....
338
all unfair nodes:null next:null

之前的总耗时为 14341ms,现在为 33ms,性能提高了约 435 倍。

五、总结

经过上面一系列的调整,我们终于实现了自己的可重入锁。虽然上面的代码还有一些待优化的点,例如:

  • isLock 可以用 int 代替,然后和 lockCount 的计数功能整合在一起。
  • Unsafe 不能直接使用,我们可以使用 VarHandle 或 AtomicReference 替代。
  • MyLock 只实现了公平锁与非公平锁,没有进一步抽象封装,提供出一个像 AbstractQueuedSynchronizer 那样的模板类。

但是到此篇幅太长了,就不往下写了。

最后,欢迎扫码关注公众号,将不定时分享干货内容。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1350518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务整合:构建高效灵活的分布式系统

随着软件开发的不断演进和业务的复杂性增加&#xff0c;微服务架构已经成为一种流行的解决方案。然而&#xff0c;当涉及到多个微服务之间的整合时&#xff0c;我们需要谨慎考虑如何实现高效、灵活的分布式系统。 微服务架构的流行使得软件开发变得更加灵活和可扩展。然而&…

layuiadmin新建tabs标签页,点击保存,打开新的标签页并刷新

用的layuiamin前端框架 需求&#xff1a;新增的页面为一个标签页&#xff0c;保存后&#xff0c;需要刷新列表 1、新建customMethod.js文件&#xff0c;自定义自己的方法 layui.define(function (exports) {var $ layui.$var customMethod {// 表单点击保存后&#xff0c;…

【ROS2】MOMO的鱼香ROS2(四)ROS2入门篇——ROS2节点通信之话题与服务

ROS2节点通信之话题与服务点 引言1 理解从通信开始1.1 TCP&#xff08;传输控制协议&#xff09;1.2 UDP&#xff08;用户数据报协议&#xff09;1.3 基于共享内存的IPC方式 2 ROS2话题2.1 ROS2话题指令2.2 话题之RCLPY实现2.2.1 编写发布者2.2 2 编写订阅者2.2.3 运行测试 3 R…

【Unity美术】Unity工程师对3D模型需要达到的了解【二】

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

基于ThinkPHP的云盘系统Cloudreve本地搭建并实现远程访问

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&#…

扫码看图时,多图如何用轮播排列展示?

在扫描二维码看图时&#xff0c;一般图片大多会通过上下排列的方式来展示&#xff0c;如果图片的数量太多&#xff0c;就需要在手机上不断地下滑才能看到所有内容&#xff0c;这种方式会导致在查看图片时感觉疲劳或者眼花的情况。那么想要解决这个问题&#xff0c;我们可以在生…

flutter接入扫码枪的扫描结果,其实就是监听键盘输入,从测试到页面显示出来

检测设备是否正常 首先一定要测试一下你的硬件设备是否正常&#xff0c;虽然有的设备看着插入usb后指示灯什么都亮了&#xff0c;但是不一定就说明设备没问题&#xff0c;这就需要先验证一下&#xff0c;比如打开记事本或者doc文档&#xff0c;然后扫描一下条形码&#xff0c;…

electron 主进程对预加载脚本和渲染进程通信

知识整理 主进程main.js node环境可以使用node的方法预加载脚本可以使用部分node方法,可以理解为是主进程和渲染进程之间的一个桥梁渲染进程属于浏览器环境,不可以使用node方法,可以操作dom等js方法 主进程对渲染进程通信 上一篇文章实现了自定义菜单栏功能,上上篇实现了预加…

程序员30而立的北京之路

作为一名程序员&#xff0c;职业规划和心灵成长是我工作和生活中不可或缺的部分。30岁是一个人生中的重要节点&#xff0c;也是所谓的“而立之年”&#xff0c;在这个阶段&#xff0c;我开始更加关注自己的职业发展和内心成长。在这篇文章中&#xff0c;我将分享我在北京这座城…

YOLOv8改进 | 细节创新篇 | iAFF迭代注意力特征融合助力多目标细节涨点

一、本文介绍 本文给大家带来的改进机制是iAFF&#xff08;迭代注意力特征融合&#xff09;&#xff0c;其主要思想是通过改善特征融合过程来提高检测精度。传统的特征融合方法如加法或串联简单&#xff0c;未考虑到特定对象的融合适用性。iAFF通过引入多尺度通道注意力模块(我…

花几分钟整点jmeter花活,轻松超越90%软件测试

jmeter 可以做性能测试&#xff0c;这个很多人都知道&#xff0c;那你知道&#xff0c;jmeter 可以在启动运行时&#xff0c;指定线程数和运行时间&#xff0c;自定义性能场景吗&#xff1f; 前言 jmeter 性能测试&#xff0c;动态设定性能场景 平时&#xff0c;我们使用 jmet…

使用.Net nanoFramework 驱动ESP32的OLED显示屏

本文介绍如何使用.Net nanoFramework 驱动ESP32的OLED显示屏。我们将会从最基础的部分开始&#xff0c;逐步深入&#xff0c;让你能够理解并实现整个过程。无论你是初学者还是有一定经验的开发者&#xff0c;这篇文章都会对你有所帮助。 1. 硬件准备 1.1 ESP32开发板 这里我们…

安装中望CAD2023 SP2

1.下载中望CAD2023 SP2&#xff0c;并安装&#xff1b; 2.把“flxNetCommon.dll”拷贝到安装目录&#xff08;与“ZWCAD.exe”同一个目录&#xff09;&#xff1b; 3.运行“ZwLicenseManager.exe” 4.点击“激活许可证”&#xff1b; 5.点击“浮动许可” ->“仅配置不查询…

Hotspot源码解析-第十一章

第十一章 11.1 线程 11.1.1 线程的概念 说起线程&#xff0c;首先得提起进程&#xff0c;相信很面试者在回答进程与线程的区别时都会用一句话&#xff1a;“进程是操作系统资源分配的基本单位&#xff0c;而线程是任务调度和执行的基本单位”&#xff0c;只能说这句话部分正…

十大排序总结之——冒泡排序、插入排序

同样&#xff0c;这两几乎也是被淘汰了的算法&#xff0c;尽管它们是稳定的&#xff0c;但是时间复杂度没人喜欢&#xff0c;了解一下就好&#xff0c;没啥好说的&#xff0c;注意最后一句话就行了 一&#xff0c;冒泡排序 1. 算法步骤 共n-1趟&#xff0c;谁两敢冒泡就换了…

SpringBoot多环境配置,让你部署无忧

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: 循序渐进学SpringBoot ✨特色专栏: MySQL学习 🥭本文内容:SpringBoot多环境配置,让你部署无忧 📚个人知识库: Leo知识库,欢迎大家访…

vite项目中动态引入src失败的问题解决:require is not defined

问题复现 静态引入路径(无问题) <el-menu-item v-for"(item,index) in menuList" :index"item.name" :key"index"><img class"menuItemImg" src"../svg/router/homePage.svg" alt"">{{ item.meta.c…

vue中使用echarts实现省市地图绘制,根据数据显示省市天气图标及温度信息

一、实现效果 使用echarts实现省市地图绘制根据数据显示省下市的天气图标根据数据显示省下市的温度信息 二、实现方法 1、安装echarts插件 npm install echarts --save2、获取省市json数据 https://datav.aliyun.com/portal/school/atlas/area_selector 通过 阿里旗下的高…

hubSpot有哪些功能?

HubSpot是一款综合性的市场营销、销售和服务软件平台&#xff0c;旨在帮助企业实现更有效的客户关系管理和增长。以下是HubSpot的一些主要功能&#xff1a; 市场营销自动化&#xff1a; 制定和执行多渠道的市场营销活动。 创建和管理电子邮件营销、社交媒体发布和广告活动。 …