锁机制
Posted qsc-acstu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了锁机制相关的知识,希望对你有一定的参考价值。
悲观锁:
悲观锁悲观的认为每一次操作都会造成更新丢失问题,在每次查询时加上排他锁
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
Select * from xxx for update;
缺点:因为只能保证一个连接进行操作,所以效率低
乐观锁:
乐观锁会乐观的认为每次查询都不会造成更新丢失,利用版本字段控制
重入锁:
重入锁也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但外层函数不受内层函数影响,例如当内部释放锁(unlock)后,外部不会释放。在JAVA环境下 ReentrantLock 和synchronized 都是可重入锁。
synchronized :
ReentrantLock :
读写锁:
两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(读-读能共存,读-写不能共存,写-写不能共存)。这就需要一个读/写锁来解决这个问题。Java5在java.util.concurrent包中已经包含了读写锁。
CAS无锁机制:
原子类底层实现保证线程安全就是通过CAS实现。
CAS算法的过程是这样:它包含三个参数CAS(V,E,N): V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。
对应java内存模型,V相当于是主内存,E相当于本地内存,如果(V=E),本地内存与主内存一致,说明变量没有被修改过那么就将V要更新的变量的值设置成N新值,如果不相等,说明本地内存被修改,需要将主内存的值刷新到本地内存中去,再进行V和E进行比较,然后再设置成新值N。
一个来自码农翻身的例子:
(1)从内存中读取value值,假设为10,称之为A
(2)B=A+1,得到B=11
(3)用A的值和内存的值相比,如果相等(过去的一段时间内,没人修改过A),就把B写入内存,如果不相等的,说明A在这段时间内被修改了,就放弃这次修改,返回第一步
CAS存在一个很明显的问题,即ABA问题。
问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?
如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。
自旋锁:
自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。
当一个线程 调用这个不可重入的自旋锁去加锁的时候没问题,当再次调用lock()的时候,因为自旋锁的持有引用已经不为空了,该线程对象会误认为是别人的线程持有了自旋锁
使用了CAS原子操作,lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。
当有第二个线程调用lock操作时由于owner值不为空,导致循环一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。
由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。
分布式锁:
如果想在不同的jvm中保证数据同步,使用分布式锁技术。
有数据库实现、缓存实现、Zookeeper分布式锁
以上是关于锁机制的主要内容,如果未能解决你的问题,请参考以下文章