目录
乐观锁
每次获取数据的时候都认为别人不会修改,所以不会上锁。但是在更新的时候会判断在此器间别人是否有更新该数据。如果数据有被修改,则不进行更新;如果没有被修改则进行更新。
一般使用version和CAS方式。
version
一般在数据库表中加上一个版本号version字段,表示数据被修改的次数,当数据被修改时,version加1.
当线程A要更新数据时,在读取数据的同时获取version值,在提交更新时,若刚才读取到的version值与当前数据库中的version相等才会执行更新,否则重试更新操作,直到成功。
CAS
即compare and swap或者compare and set。涉及到数据所在的内存值、预期值、新值。当需要更新时判断当前内存值与读取到的是否相等,若相等则用新值更新,若失败则重试,一般情况下是自旋操作,即不断的重试。
可以用for于CAS联用实现简易自旋锁。
for {
if atomic.CompareAndSwapInt32(&num2, 10, 0) {
fmt.Println("The second number has gone to zero.")
break
}
time.Sleep(time.Millisecond * 500)
}
使用场景
适合读取比较频繁的场景,如果出现大量的写入则数据可能会出现冲突。为了保证数据一致性,应用层则需要不断的重新获取数据,这样会增大查询操作,降低了吞吐量。
悲观锁
每次获取数据都担心数据被修改,所以每次获取数据都会加锁,确保在使用过程中数据不会被修改,使用完后解锁。
使用场景
适合写入操作频繁的场景。如果大量的读操作,每次读取的时候都加锁增大了开销,降低了吞吐量。
自旋锁
使线程在没有取得锁的情况下不被挂起,转去执行空循环(自己执行空循环),若在若干个空循回后,线程如果可以获得锁,则继续执行,若依然不能获得锁才被挂起。
可能引发的问题
1.过多占据CPU时间:如果锁的当前持有者长时间不释放该锁,那么等待者将长时间占据CPU,导致CPU浪费,因此可以设定一个时间,当锁的持有者超过这个时间不释放锁时,等待者就放弃cpu时间片。
2.死锁问题:有一个线程连续两次试图获得自旋锁,第一次这个线程获得了该锁,当第二次试图加锁时检测到锁已被占用(其实被自己占用),那么这时线程会一直等待自己释放该苏哦,而不能继续执行,这样就引起了死锁。
因此递归程序不能在持有自旋锁的时候调用自己。
阻塞锁
让线程进入阻塞状态进行等待,当获得相应的信号(唤醒、时间)时才可以进入线程的准备就绪状态,准备就绪状态的所有线程通过竞争进入运行态。