自定义锁之一

本文探讨了如何用Java实现一个简单的互斥锁Lock类,通过CAS操作保证线程安全,并与synchronized关键字进行了性能对比。通过实例展示了Lock类的API设计与实现,以及与标准synchronized在速度上的差距。
摘要由CSDN通过智能技术生成

1. 概述

锁(狭义指互斥锁),只是一个逻辑概念,起到线程间同步互斥的功能。同步,是指线程间的通信,通俗的讲就是线程t1释放锁后,其他线程要收到通知,可以争取锁;互斥,是指线程t1获得锁后,其他线程无法在获得锁。

就作者目前知识储备,所有锁的实现都离不开CAS,只有CPU提供了原子性操作,才有各种各样的锁的实现,例如synchronized、ReentrantLock等。因为本人不太懂C++,还没有阅读理解synchronized的源码部分,现在如果让我说出synchronized和ReentrantLock的区别,答案大概是synchonized是C++基于CAS实现的,而ReentrantLock是Java基于CAS实现的。看过几次AbstractQueuedSynchronizer的源码,但是感觉很难掌握其中之精髓,索性慢下来,从需求出发,一点点理解其设计思想。

2. 思路

设计一个Lock类,仅提供互斥锁的功能,不考虑可重入。那么API可以设计成

void lock();
void unlock();

从最朴素的念头出发,Lock类,持有一个变量state,可以是int,也可以是boolean。

  1. 当state==1/true时,代表锁已经被某线程持有了,其他线程调用lock()应该被阻塞
  2. 当state==0/false时,代表锁处于释放的状态,任意线程调用lock()将有机会获得锁

这里我们将state设计成int类型。

因为int变量无法提供CAS操作,所以Lock类要已持有一个VarHandle类型的STATE变量,提供对state的CAS操作。

假设当线程t1获得锁后,为了仅使t1.unlock()操作有效,应该在Lock类中提供一个currentThread变量,记录持有锁的当前线程。在unlock()中进行判断,忽略非持有锁线程的调用。

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
/**
 * Created by ligc on 2022/5/6 22:16
 */
public class Lock {
    private volatile int state;
    private static final VarHandle STATE;
    private Thread currentThread;
    
    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            STATE = l.findVarHandle(Lock.class, "state", int.class);
        } catch (NoSuchFieldException e) {
            throw new ExceptionInInitializerError(e);
        } catch (IllegalAccessException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    private boolean compareAndSetState(int expect, int newVal){
        return STATE.compareAndSet(this, expect, newVal);
    }

    //在竞争锁时,lock使用死循环的方式进行CAS操作,也就是所谓的自旋,简单而粗暴。
    public void lock() {
        while (!compareAndSetState(0, 1));
        currentThread = Thread.currentThread();
    }

    //在释放锁时,进行线程验证。无需自旋
    public void unlock(){
        if(Thread.currentThread() != currentThread){
            return;
        }

        currentThread = null;
        setState(); //这里没有必要进行CAS,因为执行到这里,意味着线程已经获得了锁
    }

    private void setState(){
        state = 0;
    }
}

3. 对比

对比一下synchronized关键字,和我们自定义的Lock的速度。5个线程 * 每个线程自增1000 万次,代码如下:

/**
 * Created by ligc on 2022/5/6 22:28
 */
public class LockTest {
    static final int MAX = 1000 * 10000;
    static volatile int x = 0;
    static Lock lock = new Lock();
    
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Worker());
        Thread t2 = new Thread(new Worker());
        Thread t3 = new Thread(new Worker());
        Thread t4 = new Thread(new Worker());
        Thread t5 = new Thread(new Worker());
        long start = System.currentTimeMillis();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t1.join();
        t2.join();
        t3.join();
        t4.join();
        t5.join();
        long end = System.currentTimeMillis();
        System.out.println((end - start) + " ms --> " + x);
    }

    static synchronized void increase(){
        x ++;
    }
    
//    static void increase(){
//        lock.lock();
//        x ++;
//        lock.unlock();
//    }

    static class Worker implements Runnable{

        @Override
        public void run() {
            for(int i = 0; i < MAX; i ++){
                increase();
            }
        }
    }
}

结果如下:

synchronized

3026 ms --> 50000000

Lock

12860 ms --> 50000000

结果倒是正确,仅仅差了4倍的性能而已,完败~待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值