自旋锁(spinlock):是指当一个线程在获取锁的时候,若是锁已经被其它线程获取,那么该线程将循环等待,而后不断的判断锁是否可以被成功获取,直到获取到锁才会退出循环。
获取锁的线程一直处于活跃状态,可是并无执行任何有效的任务,使用这种锁会形成busy-waiting。java
先看一个实现自旋锁的例子,java.util.concurrent包里提供了不少面向并发编程的类. 使用这些类在多核CPU的机器上会有比较好的性能.主要缘由是这些类里面大多使用(失败-重试方式的)乐观锁而不是synchronized方式的悲观锁.编程
class spinlock { private AtomicReference<Thread> cas; spinlock(AtomicReference<Thread> cas){ this.cas = cas; } public void lock() { Thread current = Thread.currentThread(); // 利用CAS while (!cas.compareAndSet(null, current)) { //为何预期是null?? // DO nothing System.out.println("I am spinning"); } } public void unlock() { Thread current = Thread.currentThread(); cas.compareAndSet(current, null); } }
lock()方法利用的CAS,当第一个线程A获取锁的时候,可以成功获取到,不会进入while循环,若是此时线程A没有释放锁,另外一个线程B又来获取锁,此时因为不知足CAS,因此就会进入while循环,不断判断是否知足CAS,直到A线程调用unlock方法释放了该锁。多线程
package ddx.多线程; import java.util.concurrent.atomic.AtomicReference; public class 自旋锁 { public static void main(String[] args) { AtomicReference<Thread> cas = new AtomicReference<Thread>(); Thread thread1 = new Thread(new Task(cas)); Thread thread2 = new Thread(new Task(cas)); thread1.start(); thread2.start(); } } //自旋锁验证 class Task implements Runnable { private AtomicReference<Thread> cas; private spinlock slock ; public Task(AtomicReference<Thread> cas) { this.cas = cas; this.slock = new spinlock(cas); } @Override public void run() { slock.lock(); //上锁 for (int i = 0; i < 10; i++) { //Thread.yield(); System.out.println(i); } slock.unlock(); } }
经过以前的AtomicReference类建立了一个自旋锁cas,而后建立两个线程,分别执行,结果以下:并发
0 I am spin I am spin I am spin I am spin I am spin I am spin I am spin I am spin I am spin I am spin I am spin I am spin I am spin I am spin I am spin I am spin 1 I am spin I am spin I am spin I am spin I am spin 2 3 4 5 6 7 8 9 I am spin 0 1 2 3 4 5 6 7 8 9
经过对输出结果的分析咱们能够得知,首先假定线程一在执行lock方法的时候得到了锁,经过方法ide
cas.compareAndSet(null, current)函数
将引用改成线程一的引用,跳过while循环,执行打印函数性能
而线程二此时也进入lock方法,在执行比较操做的时候发现,expect value != update value,因而进入while循环,打印this
i am spinning。由如下红字能够得出结论,Java中的一个线程并非老是占着cpu时间片不放,一直执行完的,而是采用抢占式调度,因此出现了上面两个线程交替执行的现象atom
Java线程的实现是经过映射到系统的轻量级线程上,轻量级线程有对应系统的内核线程,内核线程的调度由系统调度器来调度的,因此Java的线程调度方式取决于系统内核调度器,只不过恰好目前主流操做系统的线程实现都是抢占式的。操作系统
使用自旋锁会有如下一个问题:
1. 若是某个线程持有锁的时间过长,就会致使其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会形成CPU使用率极高。
2. 上面Java实现的自旋锁不是公平的,即没法知足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。
文章开始的时候的那段代码,仔细分析一下就能够看出,它是不支持重入的,即当一个线程第一次已经获取到了该锁,在锁释放以前又一次从新获取该锁,第二次就不能成功获取到。因为不知足CAS,因此第二次获取会进入while循环等待,而若是是可重入锁,第二次也是应该可以成功获取到的。
并且,即便第二次可以成功获取,那么当第一次释放锁的时候,第二次获取到的锁也会被释放,而这是不合理的。
例如将代码改为以下:
@Override public void run() { slock.lock(); //上锁 slock.lock(); //再次获取本身的锁!因为不可重入,则会陷入循环 for (int i = 0; i < 10; i++) { //Thread.yield(); System.out.println(i); } slock.unlock(); }
则运行结果将会无限打印,陷入无终止的循环!
为了实现可重入锁,咱们须要引入一个计数器,用来记录获取锁的线程数。
public class ReentrantSpinLock { private AtomicReference<Thread> cas = new AtomicReference<Thread>(); private int count; public void lock() { Thread current = Thread.currentThread(); if (current == cas.get()) { // 若是当前线程已经获取到了锁,线程数增长一,而后返回 count++; return; } // 若是没获取到锁,则经过CAS自旋 while (!cas.compareAndSet(null, current)) { // DO nothing } } public void unlock() { Thread cur = Thread.currentThread(); if (cur == cas.get()) { if (count > 0) {// 若是大于0,表示当前线程屡次获取了该锁,释放锁经过count减一来模拟 count--; } else {// 若是count==0,能够将锁释放,这样就能保证获取锁的次数与释放锁的次数是一致的了。 cas.compareAndSet(cur, null); } } } }
一样lock方法会先判断是否当前线程已经拿到了锁,拿到了就让count加一,可重入,而后直接返回!而unlock方法则会首先判断当前线程是否拿到了锁,若是拿到了,就会先判断计数器,不断减一,不断解锁!
//可重入自旋锁验证 class Task1 implements Runnable{ private AtomicReference<Thread> cas; private ReentrantSpinLock slock ; public Task1(AtomicReference<Thread> cas) { this.cas = cas; this.slock = new ReentrantSpinLock(cas); } @Override public void run() { slock.lock(); //上锁 slock.lock(); //再次获取本身的锁!没问题! for (int i = 0; i < 10; i++) { //Thread.yield(); System.out.println(i); } slock.unlock(); //释放一层,但此时count为1,不为零,致使另外一个线程依然处于忙循环状态,因此加锁和解锁必定要对应上,避免出现另外一个线程永远拿不到锁的状况 slock.unlock(); } }
本文到这里就结束了,感谢看到最后的朋友,都看到最后了,点个赞再走啊,若有不对之处还请多多指正。