自旋锁:spinLock,指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的开销,确点是循环会消耗CPU资源。
public class SpinLockDemo {
private AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock() {
System.out.println(Thread.currentThread().getName() + " come in");
while (!atomicReference.compareAndSet(null, Thread.currentThread())) {}
System.out.println(Thread.currentThread().getName() + " 持有锁成功");
}
public void myUnLock() {
atomicReference.compareAndSet(Thread.currentThread(), null);
System.out.println(Thread.currentThread().getName() + " 释放锁成功");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); };
spinLockDemo.myUnLock();
}, "t1").start();
new Thread(()->{
spinLockDemo.myLock();
spinLockDemo.myUnLock();
}, "t2").start();
}
}
CAS的缺点:
1、如果CAS失败,会一致进行尝试。如果长时间不成功,CPU空转带来很大开销。(长时间抢不到锁 -- 锁饥饿)
2、会导致ABA问题
CAS算法实现的一个重要前提是需要取出内存中某时刻的数据,并在当下时刻比较并替换。那么在这个时间差中会导致数据的变化。比如说一个线程a,从内存位置V中取出数据x,这时候另一个线程b也从内存取出数据x,然后线程b进行了一些操作,将内存位置V的值变成了y,然后线程b又将内存位置V的数据由y变成x。这时候线程a进行CAS操作,发现内存中数据仍然是x,然后线程a操作成功。尽管线程a的CAS操作成功,但是不代表这个过程就是没有问题的。
如何解决ABA问题?
使用带版本号时间戳原子引用:AtomicStampedReference
ABA示例
public class ABADemo {
private static AtomicInteger a = new AtomicInteger(100);
public static void main(String[] args) {
new Thread(()->{
a.compareAndSet(100,101);
a.compareAndSet(101,100);
}).start();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); };
a.compareAndSet(100,200);
}
}
result = true, 值 = 200
AtomicStampedReference解决ABA实例
public class ABADemo {
private static AtomicStampedReference<Integer> a = new AtomicStampedReference<>(100, 1); // 初始值,版本号
public static void main(String[] args) {
new Thread(()->{
int stamp = a.getStamp();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); };
a.compareAndSet(100,101, stamp ,stamp + 1);
a.compareAndSet(101,100, a.getStamp() ,a.getStamp() + 1);
}).start();
int stamp = a.getStamp();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); };
boolean result = a.compareAndSet(100, 200, stamp, stamp + 1);
System.out.println("result = " + result + " , 值 = " + a.getReference() + " , 版本号 = " + a.getStamp());
}
}
result = false , 值 = 100 , 版本号 = 3