01 自旋锁(spinlock)
过程
获取锁
访问共享资源
释放锁
自旋锁
循环尝试获取锁,不会放弃CPU时间片,减伤cup上下文切换,缺点是循环会消耗cpu
非自旋锁
如果获取不到锁,让线程休眠,CPU就可以在这段时间去做很多其他的事情,直到之前持有这把锁的线程释放了锁,于是CPU再把之前的线程恢复回来,让这个线程再去尝试获取这把锁。若再次失败,就再次让线程休眠,若成功,一样可以成功获取到同步资源的锁
如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即获取锁。
如果在获取锁时已经有持有者,那么锁操作将自旋,直到自旋锁的保持者释放了锁。
缺点
自旋锁一直占用着CPU,他在未获得锁的情况下,一直运行(自旋),所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低
互斥锁与自旋锁区别
互斥锁 在未获得锁时,cup会进行切换,不会一直等待
自旋锁 在未获得锁时,一直占用着CPU,一直运行(自旋)抢占CUP
在多核机器中,如果锁住的“事务”很简单,占用很少时间,应该用自旋锁,自旋锁代价比互斥锁会小很多。”事务”很快执行完毕,自旋的消耗远远小于陷入sleep和wake的消耗。如果锁住“事务”粒度较大,就应该使用互斥锁,因为如果用自旋锁,那么在“事务”执行过程中自旋很长时间还不如使得线程sleep
在单核机器中。自旋锁没有任何意义的,自旋锁只会浪费唯一核心的cpu 时间片,这个时刻没有任何线程会运行的。所以单核机器中,不论锁住的”事务”的粒度大小都要使用。
手写自旋
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t"+"----come in");
// 因为atomicReference没有set,所以是null,compareAndSet方法比较如果是null,就修改为当前线程,如果不是null,就循环判断
while (!atomicReference.compareAndSet(null, thread)) {
}
}
public void unLock() {
Thread thread = Thread.currentThread();
// 如果atomicReference存储的是当前线程,就修改为null
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t"+"----task over,unLock...");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.lock();
// 业务逻辑
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
spinLockDemo.unLock();
},"A").start();
// 暂停500毫秒,线程A先于B启动
try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
spinLockDemo.lock();
spinLockDemo.unLock();
},"B").start();
}
}