自旋锁/Spin lock

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  
public class SpinLock {

private AtomicReference<Thread> sign =new AtomicReference<>();

public void lock(){
      
Thread current = Thread.currentThread();
      
while(!sign.compareAndSet(null, current)){
      
}
    
}

public void unlock (){
      
Thread current = Thread.currentThread();
      
sign.compareAndSet(current, null);
    
}
  
}

// another spin lock
  
for (;;) {
      
if (condition == true)
          
break;
  
}
  

自旋锁是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋"一词就是因此而得名。

自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。

自旋锁一般原理

跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。由此我们可以看出,自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题: 死锁和过多占用cpu资源

自旋锁适用情况

自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问 (包括底半部即中断处理句柄和顶半部即软中断) ,就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP (多处理器) 的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。另外格外注意一点: 自旋锁不能递归使用

缺点

CAS操作需要硬件的配合;

保证各个CPU的缓存 (L1、L2、L3、跨CPU Socket、主存) 的数据一致性,通讯开销很大,在多处理器系统上更严重;

没法保证公平性,不保证等待进程/线程按照FIFO顺序获得锁。

自旋锁是最简单的一种阻塞线程的办法,就是在一个死循环里不断检查等待的条件是否满足,直至满足之后才跳出循环。很显然,自旋锁的最大缺点是白白消耗计算资源,并且把调度的责任完全交给了线程调度器。但是自旋锁最大的优点就是减少线程切换的次数。因为线程切换需要CPU从用户态进入核心态,是非常昂贵的操作。特别是在等待时间较短时,频繁地切换线程的运行状态可能得不偿失。

http://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html

http://ifeve.com/java_lock_see1

http://ifeve.com/java_lock_see1/embed/#?secret=0zgZ1kp03E

https://coderbee.net/index.php/concurrent/20131115/577

https://coderbee.net/index.php/concurrent/20131115/577/embed#?secret=DLJab8XJP7