自定义一把lock锁
一、概述
学习完lock锁之后,想要来写一把自己的lock锁。
但是我感觉比较初级,应该在学习Lock锁之前来写的。反正也想着总结一下。
二、lock锁的前提
1、锁的标识,当前锁是用state来进行标识的;
2、加锁、解锁方法;
3、如果锁是自由状态,加锁成功的话需要让其返回加锁成功状态;
4、如果锁不是自由状态,则让其加锁失败。所谓的加锁失败,是让其线程陷入到阻塞中或者是自旋;
5、解锁方法原则:释放锁的同时还需要唤醒其他线程(当前锁中使用的是自旋,所以不需要唤醒)
对于第三点要来做个详细说明:
存在一个现象,对于外部线程来说,不知道锁是否是自由状态。 那么就意味着,外部线程来访问锁的时候,就分为了两种情况:被其他线程持有锁 或者 是当前锁是自由状态, 那么对于多线程来说:t1,t2,t3,t4,如果当前锁被t1持有,t2,t3,t4线程因为持有不到锁,而去自旋,但是此时,如果t5线程进入,而t1线程又刚刚好将锁释放掉,而锁又被刚刚加入被t5持有了,那么对于t2,t3,t4线程来说,是不公平的,因为已经自旋了这么久了。因为外部的线程到来而导致当前线程无法使用。所以需要考虑到公平和非公平的条件。
如果是公平锁,那么线程也应该等着前面的t2,t3,t4线程执行完之后,然后轮到t5;如果是不公平的,那么就无所谓
但是如果是多线程条件下,自旋是很消耗CPU资源的。所以为了避免CPU资源的消耗,应该考虑让线程阻塞。那么阻塞应该要被唤醒, 那么唤醒,那么就应该考虑到应该使用容器来盛放多线程,在锁释放之后,将线程再唤醒
三、代码实现
public class LigLock {
private final static Logger logger = LoggerFactory.getLogger(LigLock.class);
//标识---加锁成功=1 如果自由状态=0
volatile int state=0;
//unsafe 对象,因为cas操作需要这个对象
static Unsafe unsafe;
//state这个字段在ShadowLock对象当中的内存偏移量
private static long stateOffset;
/**
* 1、获取unsafe对象,这个对象的获取稍微有点复杂,目前不需要关心
* 2、通过unsafe对象得到state属性在当前对象当的偏移地址
*/
static {
try {
unsafe = getUnSafe();
stateOffset = unsafe.objectFieldOffset(LigLock.class.getDeclaredField("state"));
} catch (Exception e) {
e.printStackTrace();
}
}
//加锁方法
@SneakyThrows
public void lock(){
//compareAndSet(0,1) 当某个属性(state)他的值如果为0 则修改1
//效率高 cas 无锁 不会发生内核态切换
//t1 执行 compareAndSet(0,1) before state =0;after true ---->state=1
//t1 执行 compareAndSet(0,1) before state =1
// 这里保证了原子性操作
while(!compareAndSet(0,1)){
//加锁失败
logger.info("当前线程{}在等待获取得到锁",Thread.currentThread().getName());
Thread.sleep(10L);
}
// 如果成功,则进行赋值
Thread currentThread = Thread.currentThread();
logger.info("当前线程获取得到了锁,当前线程的名称是:{}",currentThread.getName());
}
public void unlock(){
//思考这里为什么不要cas? 因为这里不需要来进行只有已经持有锁了的线程才可以操作
state=0;
logger.info("当前线程释放了锁,当前线程的名称是:{}",Thread.currentThread().getName());
}
//获取unsafe对象
private static Unsafe getUnSafe() throws NoSuchFieldException, IllegalAccessException {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe) theUnsafe.get(null);
}
//cas 修改state的值
//expect预期值,如果相同则把state的值改成x 原子操作
private boolean compareAndSet(int expect,int x){
//stateOffset 一个地址 【state属性的地址】
return unsafe.compareAndSwapInt(this, stateOffset, expect, x);
};
}
测试代码:
public class LigLockTest {
static LigLock lock = new LigLock();
private final static Logger logger = LoggerFactory.getLogger(LigLockTest.class);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
lock.lock();
logger.info("当前是线程{}在执行",Thread.currentThread().getName());
try {
TimeUnit.MICROSECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
logger.info("-------------------------");
}, "t1");
Thread t2 = new Thread(() -> {
lock.lock();
logger.info("当前是线程{}在执行",Thread.currentThread().getName());
lock.unlock();
logger.info("-------------------------");
}, "t2");
t1.start();
TimeUnit.MICROSECONDS.sleep(10);
t2.start();
}
}
四、缺点总结
1、不能够支持重入;
2、不支持公平锁;
3、性能较低。因为自旋比较消耗CPU资源;
4、没有对排队线程记录以及当前线程记录;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?