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,然后创建两个线程,分别实行,结果如下:
0I am spinI am spinI am spinI am spinI am spinI am spinI am spinI am spinI am spinI am spinI am spinI am spinI am spinI am spinI am spinI am spin1I am spinI am spinI am spinI am spinI am spin23456789I am spin0123456789

通过对输出结果的剖析我们可以得知,首先假定线程一在实行lock方法的时候得到了锁,通过方法
cas.compareAndSet(null, current)
将引用改为线程一的引用,跳过while循环,实行打印函数
而线程二此时也进入lock方法,在实行比较操作的时候创造,expect value !
= update value,于是进入while循环,打印
i am spinning。由以下红字可以得出结论,Java中的一个线程并不是总是占着cpu韶光片不放,一贯实行完的,而是采取抢占式调度,以是涌现了上面两个线程交替实行的征象
Java线程的实现是通过映射到系统的轻量级线程上,轻量级线程有对应系统的内核线程,内核线程的调度由系统调度器来调度的,以是Java的线程调度办法取决于系统内核调度器,只不过刚好目前主流操作系统的线程实现都是抢占式的。
3.自旋锁存在的问题利用自旋锁会有以下一个问题:1. 如果某个线程持有锁的韶光过长,就会导致其它等待获取锁的线程进入循环等待,花费CPU。利用不当会造成CPU利用率极高。2. 上面Java实现的自旋锁不是公正的,即无法知足等待韶光最长的线程优先获取锁。不公正的锁就会存在“线程饥饿”问题。
4.自旋锁的优点自旋锁不会使线程状态发生切换,一贯处于用户态,即线程一贯都是active的;不会使线程进入壅塞状态,减少了不必要的高下文切换,实行速率快非自旋锁在获取不到锁的时候会进入壅塞状态,从而进入内核态,当获取到锁的时候须要从内核态规复,须要线程高下文切换。 (线程被壅塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)5.可重入的自旋锁和不可重入的自旋锁文章开始的时候的那段代码,仔细剖析一下就可以看出,它是不支持重入的,即当一个线程第一次已经获取到了该锁,在锁开释之前又一次重新获取该锁,第二次就不能成功获取到。由于不知足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(); //再次获取自己的锁!
6.自旋锁与互斥锁异同点自旋锁与互斥锁都是为了实现保护资源共享的机制。无论是自旋锁还是互斥锁,在任意时候,都最多只能有一个保持者。获取互斥锁的线程,如果锁已经被占用,则该线程将进入就寝状态;获取自旋锁的线程则不会就寝,而是一贯循环等待锁开释。7.总结自旋锁:线程获取锁的时候,如果锁被其他线程持有,则当前哨程将循环等待,直到获取到锁。自旋锁等待期间,线程的状态不会改变,线程一贯是用户态并且是活动的(active)。自旋锁如果持有锁的韶光太长,则会导致其它等待获取锁的线程耗尽CPU。自旋锁本身无法担保公正性,同时也无法担保可重入性。基于自旋锁,可以实现具备公正性和可重入性子的锁结尾
没问题!
for (int i = 0; i < 10; i++) { //Thread.yield(); System.out.println(i); } slock.unlock(); //开释一层,但此时count为1,不为零,导致另一个线程依然处于忙循环状态,以是加锁和解锁一定要对应上,避免涌现另一个线程永久拿不到锁的情形 slock.unlock(); }}
本文到这里就结束了,感谢看到末了的朋友,都看到末了了,点个赞再走啊,如有不对之处还请多多示正。