欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 文化 > 关于java中的锁

关于java中的锁

2025/4/19 9:42:32 来源:https://blog.csdn.net/for_no_what/article/details/144331550  浏览:    关键词:关于java中的锁

        云盘的基本登录注册文件上传下载功能都已写完,下一步考虑并发情况。这里总结一下Java中的锁都有哪些

        锁大致可以分为乐观锁和悲观锁两类。

        java中大部分类都是由悲观锁实现的

        悲观锁认为并发任务中冲突情况发生较多,因此有必要显式地在操作数据时加锁;而乐观锁认为冲突情况较少,因此不去显式地加锁,而是在更新数据时进行验证,如果没有冲突就直接更新,有冲突的话根据一些准则来采取措施

        悲观锁:

        常用的为synchronized、reentrantLock和ReadWriteLock。

        synchronized

        synchronized是一个关键字,用来修饰一段代码块或一个方法。

        可以简单地理解Synchronized锁住的是类而不是这段代码或方法本身。当修饰静态方法时,锁住的是这个类的Class,修饰普通方法时,锁住的是这个类的实例。

        比如说,有一个类有方法A和方法B,他们都被Synchronized修饰了,那么当有两个线程a和线程b同时分别调用同一个实例对象的方法A和方法B的时候,由于Synchronized锁住的是实例本身,那么由更早调用这个类的方法的线程a来获取这个类的锁(把这个锁叫做监视器锁,这个监视器在JVM中),并且执行完方法A之后,才轮到线程b来执行方法B。

        Synchronized是可重入的。也就是说,一个线程如果获得了这个类的锁,那么他就可以执行全部被Synchronized修饰的方法。每次执行一个方法,就会在监视器计数器中锁的数量加一,执行完之后减一。这样执行完所有的方法就可以退出锁。如果不可重入的话,先执行方法A,如果A调用了同样被Synchronized修饰的方法B,那么此时线程无法重复获得锁,无法执行下去,就会造成死锁问题。

        Synchronized的问题在于锁住的内容太多了,造成性能浪费

        reentrantLock

        reentrantLock是一个类。在方法中创建一个对象,在需要被锁住的内容前后调用.lcok()和.unlock()方法。

        reentrantLock实现的是真正意义上的锁住一个代码块。在一个线程执行到.lock()之后,会尝试获取当前的锁,如果成功获取就向后执行。如果获取不成功,则会被加入到reentrantLock维护的一个队列中,等待被持有当前锁的线程唤醒。

        reentrantLock是由一个AQS类实现的(AbstractQueuedSynchronizer),通过这个类实现管理锁和一个线程的等待队列。支持公平锁和非公平锁,其内部实现其实就是两个继承AbstractQueuedSynchronizer的子类,分别实现公平锁和非公平锁。AbstractQueuedSynchronizer维护了一个队列。当一个线程尝试获取这个锁失败的时候,这个线程被放入到等待队(实际上是AQS维护的一个阻塞队列)然后这个线程将自己挂起,当前线程执行完任务之后会调用reentrantLock的.unlock()方法来唤醒队列中的下一个线程

        AbstractQueuedSynchronizer通过维护一个状态变量(state)来判断现在是否有线程正在执行锁住的内容

        可以通过构造函数指定是否是公平锁,所谓公平锁就是严格按照先进先出的顺序让线程来执行任务。非公平锁就是线程尝试抢占当前内容,如果成功就直接执行,不成功再进入队列等待(unlock的时候唤醒下一个线程需要时间,这就给了新线程抢占的可能)通俗地讲,公平锁和非公平锁的区别就是是否允许新线程在门口插队。

        需要注意的是Synchronized在线程执行完任务之后会直接自动释放锁,而reentrantLock需要手动加锁和释放锁(即需要线程调用.lcok和.unlcok方法)

        除此之外,reentrantLock还提供了可中断功能和定时功能。可中断指的是reentrantLock可以调用lockInterruptibly方法。这样在他等待的时候就可以被另一个线程中断并抛出一个InterruptedException异常。(一般情况下在持有锁的线程并不会响应这个中断,等待Synchronized锁的线程不会不会响应中断)。定时锁指的是reentrantLock调用trylock方法。尝试获取锁,如果成功就直接运行,不成功的话在接下来的一段时间内持续尝试获取锁,直到超时或获取锁成功

        reentrantLock提供了Condition支持,这个Condition是reentrantLock的变量,用来帮助正在持有锁的线程进行一些判断,比如是不是要把自己挂起或唤醒某些线程。Condition主要用于多线程合作的状态,当满足(或不满足)一些条件时执行一些任务。需要注意的是,不使用Condition的话unlock后当前线程会释放锁然后由JVM唤醒等待队列中的线程,使用Condition的话就调用unlock就只会释放锁,唤醒等待中的线程需要Condition来完成(Condition调用signal或signalAll方法)

        ReadWriteLock

        ReadWriteLock:读写锁。他是一个接口,主要实现是ReentrantReadWriteLock这个类实现的。他底层也是由AQS这个choui实现的AbstractQueuedSynchronizer

        这个类可以看做是reentrantLock的扩展。他可以拆成读锁:readLock和写锁:writeLock两个部分。ReentrantReadWriteLock维护了一个状态变量state来记录写锁的重入次数和读锁的线程数。高16位是读锁,低16位是写锁。多个线程可以同时持有读锁,但只有一个线程能持有写锁。并且当一个线程持有写锁的时候,不能再有新线程来获取读锁。

        如果现在有很多线程持有读锁,一个新线程希望获取写锁,那么他需要等待所有读线程读完之后才能获取写锁。

        ReentrantReadWriteLock也有Condition变量,并且他为读锁和写锁分别准备了一个Condition,其功能大致和reentrantLock类似。ReentrantReadWriteLock也有公平锁和非公平锁实现,并且也是可重入的。

乐观锁

java中常用的乐观锁机制是CAS和版本号控制。其中CAS更常用于日常开发,而版本号控制常用于数据库

CAS

        CAS:compare and swap,比较和交换。与其说这是一种锁,倒不如说这是一种操作的类型,java大部分支持并发的类,甚至reentrantLock的state更新策略,都是根据CAS操作实现的。

        CAS是修改值的一种操作,java调用底层的硬件语言(CPU指令集)来实现直接在内存中改变内存中的值。具体来说CAS操作需要三个值,一个期望值,一个新值,一个地址值。首先通过地址来查询当前值,如果当前值和期望值相等,那么将他修改为新值。修改失败的话由调用者(你)来决定执行什么操作

        比如说对于基于CAS操作实现的AtomicInteger来说,他内部维护了两个属性,一个Value一个valueOffset,Value就是当前值,对应期望值;valueOffset是内存中Value相对于对象的地址偏移量,对应地址值(他并不是直接的地址值,而是通过他找到地址值);他将要被修改成为的值为新值

        这里解释一下为什么这样是原子操作而普通变量的修改不是:普通变量的修改需要先讲值从内存中读取出来然后存在CPU中,在CPU中修改之后再放回到内存中,先拿再放的操作就有可能会出问题。而CAS直接通过CPU在内存中完成比较和交换,就不会有这种问题了。

版本号控制

        在每条字段中加一个版本号,在写之前读取这个版本号,写入时通过 SQL 语句同时更新数据和版本号,并校验版本号是否匹配。这些读写操作都是在数据库层面完成的(而不是内存)。他的原子性由数据库事务操作的原子性保证

        

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词