1.自旋锁实现与测试:
package juc.lock;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
class SpinLock {
//设置一个原子引用,引用的初始值时null,
// 当引用值为null时代表没有线程获取锁
// 当引用值为某个线程时,代表锁被该线程占有
private final AtomicReference<Thread> reference = new AtomicReference<>();
public void lock(){
Thread thread = Thread.currentThread();
System.out.println("线程:"+Thread.currentThread().getName()+"准备获取锁,时间:"+new Date().toString());
int count = 1;
//当引用的期望值时null(没有线程占用锁)时,才能结束循环(*****获取锁的自旋逻辑*****)
while(!reference.compareAndSet(null,thread)){
count++;
}
System.out.println("线程:"+Thread.currentThread().getName()+"获取到了锁,尝试获取次数:"+count+",时间:"+new Date().toString());
}
public void unlock(){
Thread thread = Thread.currentThread();
System.out.println("线程:"+Thread.currentThread().getName()+"准备释放锁,时间:"+new Date().toString());
//志用当引用为当前线程时,才能释放锁
if(reference.compareAndSet(thread,null)){
System.out.println("线程:"+Thread.currentThread().getName()+"释放锁成功,时间:"+new Date().toString());
}else{
System.out.println("线程:"+Thread.currentThread().getName()+"释放锁失败,时间:"+new Date().toString());
}
}
}
public class SpinLockDemo {
//检测
public static void main(String[] args) {
SpinLock lock = new SpinLock();
Thread t1 = new Thread(()->{
//t1尝试获取锁
lock.lock();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("线程:"+Thread.currentThread().getName()+"做了一些事情,时间:"+new Date().toString());
lock.unlock();
},"t1");
Thread t2 = new Thread(()->{
//t2未获取到锁,尝试先释放锁,看看会发生什么
lock.unlock();
//t2尝试获取锁
lock.lock();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("线程:"+Thread.currentThread().getName()+"做了一些事情,时间:"+new Date().toString());
lock.unlock();
},"t2");
t1.start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
t2.start();
}
}
执行结果:
分析:
t1线程先执行,成功获取到锁,并开始执行自己的逻辑(耗时3S),同时在t1线程开始执行1s后,t2线程尝试获取锁,但是此时锁已经被t1占用,t2进入自旋,不断尝试获取锁,可以看到,一共尝试了260571185次,这个过程中t2线程是活动状态.
2.非自旋锁的实现与测试:
package juc.lock;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
class UsualLock {
//设置一个原子引用,引用的初始值时null,
// 当引用值为null时代表没有线程获取锁
// 当引用值为某个线程时,代表锁被该线程占有
private final AtomicReference<Thread> reference = new AtomicReference<>();
public synchronized void lock() {
Thread thread = Thread.currentThread();
System.out.println("线程:"+Thread.currentThread().getName()+"准备获取锁,时间:"+new Date().toString());
int count = 1;
//当引用的期望值时null(没有线程占用锁)时,才能结束循环(*****获取锁的自旋逻辑*****)
//当引用不是null时,线程休眠
while(!reference.compareAndSet(null,thread)){
count++;
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程:"+Thread.currentThread().getName()+"获取到了锁,尝试获取次数:"+count+",时间:"+new Date().toString());
}
public synchronized void unlock(){
Thread thread = Thread.currentThread();
System.out.println("线程:"+Thread.currentThread().getName()+"准备释放锁,时间:"+new Date().toString());
//志用当引用为当前线程时,才能释放锁
if(reference.compareAndSet(thread,null)){
//释放锁成功,通知其他线程
this.notifyAll();
System.out.println("线程:"+Thread.currentThread().getName()+"释放锁成功,时间:"+new Date().toString());
}else{
System.out.println("线程:"+Thread.currentThread().getName()+"释放锁失败,时间:"+new Date().toString());
}
}
}
public class UsualLockDemo {
//检测
public static void main(String[] args) {
UsualLock lock = new UsualLock();
Thread t1 = new Thread(()->{
//t1尝试获取锁
lock.lock();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("线程:"+Thread.currentThread().getName()+"做了一些事情,时间:"+new Date().toString());
lock.unlock();
},"t1");
Thread t2 = new Thread(()->{
//t2未获取到锁,尝试先释放锁,看看会发生什么
lock.unlock();
//t2尝试获取锁
lock.lock();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("线程:"+Thread.currentThread().getName()+"做了一些事情,时间:"+new Date().toString());
lock.unlock();
},"t2");
t1.start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
t2.start();
}
}
执行结果:
分析:
t1线程先执行,成功获取到锁,并开始执行自己的逻辑(耗时3S),同时在t1线程开始执行1s后,t2线程尝试获取锁,但是此时锁已经被t1占用,t2线程调用了wait()方法,知道t1线程执行unlock()时调用了notifyAll()方法,t2线程才被唤醒,可以看到,t2线程一共尝试了2次获取锁,这个过程中t2线程是阻塞状态.
总结/优缺点:
自旋锁与非自旋锁的核心区别在于,如果线程获取锁时,但这个锁已经被其他线程占用时,这个线程所执行的动作,执行循环重试的就是自旋锁,执行等待的就是非自旋锁.
还有一个区别就是,自旋锁在释放锁的时候不需要通知其他线程(因为其他线程根本就没有进入等待阻塞状态),而非自旋锁释放锁时需要执行notify()/notifyAll()方法(其他线程进入了阻塞等待状态)
自旋锁的方式时一种乐观的方式来获取锁,这种方式在并发量不是特别高的时候和任务执行时间比较短的时候比较好(不需要切换线程),但在超高并发或者任务执行时间长的时候需要慎用(每个线程在自旋的过程中都会消耗CPU资源,如果多个线程长时间处于自旋状态,那么对CPU资源的消耗可想而知.)