Java 多线程编程自旋锁详解

位置:首页>文章>详情   分类: 教程分享 > Java教程   阅读(781)   2023-03-28 11:29:14

一、什么是自旋锁?


    计算机系统资源总是有限的,有些资源需要互斥访问,因此就有了锁机制,只有获得锁的线程才能访问资源。锁保证了每次只有一个线程可以访问资源。当线程申请一个已经被其他线程占用的锁,就会出现两种情况:
  • 一种是没有获得锁的线程会阻塞自己,等到锁被释放后再被唤起,这就是互斥锁;
  • 一种是没有获得锁的线程一直循环在那里看是否该锁的保持者已经释放了锁,这就是自旋锁。

二、自旋锁可能引起的问题


1.过多占据CPU时间:如果锁的当前持有者长时间不释放该锁,那么等待者将长时间的占据cpu时间片,导致CPU资源的浪费,因此可以设定一个时间,当锁持有者超过这个时间不释放锁时,等待者会放弃CPU时间片阻塞;

2.死锁问题:试想一下,有一个线程连续两次试图获得自旋锁(比如在递归程序中),第一次这个线程获得了该锁,当第二次试图加锁的时候,检测到锁已被占用(其实是被自己占用),那么这时,线程会一直等待自己释放该锁,而不能继续执行,这样就引起了死锁。因此递归程序使用自旋锁应该遵循以下原则:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。
 

三、JAVA 多线程编程中一种自旋锁使用案例

import java.util.concurrent.atomic.AtomicReference;

public class SpinLock {
    //java中原子(CAS)操作
    AtomicReference<Thread> owner = new AtomicReference<Thread>();//持有自旋锁的线程对象
    private int count;

    public void lock() {
        Thread cur = Thread.currentThread();
        //lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。当有第二个线程调用lock操作时由于owner值不为空,导致循环

        //一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。
        while (!owner.compareAndSet(null, cur)) {
        }
    }

    public void unLock() {
        Thread cur = Thread.currentThread();
        owner.compareAndSet(cur, null);
    }
}
 
上述代码说明:

调用lock方法时,如果owner当前值为null,说明自旋锁还没有被占用,将owner设置为currentThread,并进行锁定。

调用lock方法时,如果owner当前值不为null,说明自旋锁已经被其他线程占用,当前线程就会在while中继续循环检测。

调用unlock方法时,会将owner置为空,相当于释放自旋锁。

 
public class TestThread extends Thread {
    static int sum;
    private SpinLock lock;

    public TestThread(SpinLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        this.lock.lock();
        sum++;
        System.out.println("当前值:=====>"+sum);
        this.lock.unLock();
    }

    public static void main(String[] args) throws InterruptedException {
        SpinLock lock = new SpinLock();
        for (int i = 0; i < 100; i++) {
            TestThread test = new TestThread(lock);
            Thread t = new Thread(test);
            t.start();
        }

        Thread.currentThread().sleep(1000);
        System.out.println(sum);
    }

}
上述代码执行的输出结果一定是顺序的1-100输出。


四、名词解释

java CAS原子操作:Compare And Set 在java.util.concurrent.atomic包下很多类都具有compareAndSet方法。


五、自旋锁的使用场景,什么时候适合使用自旋锁


由于自旋锁只是在当前线程不停地执行循环体,不进行线程状态的切换,因此响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要占用CPU时间。如果线程竞争不激烈,并且保持锁的时间很短,则适合使用自旋锁。

六、自旋锁缺点

  1. CAS操作需要硬件的配合;
  2. 保证各个CPU的缓存(L1、L2、L3、跨CPU Socket、主存)的数据一致性,通讯开销很大,在多处理器系统上更严重;
  3. 没法保证公平性,不保证等待进程/线程按照FIFO顺序获得锁。
地址:https://www.leftso.com/article/465.html

相关阅读

本文将讲述什么是自旋锁?自旋锁的使用场景,什么情况适合使用自旋锁?Java 怎么使用自旋锁?
java多线程编程_java多线程安全_java多线程实现安全锁CAS机制,CAS在java多线程中相当于数据库的乐观锁,synchronized相当于数据库的乐观锁。
本文将讲述CLH锁的使用场景,什么情况适合使用CLH锁?Java 怎么使用CLH锁?
本文将讲述排队锁的使用场景,什么情况适合使用排队锁?Java 怎么使用排队锁?
本文将讲述MCS锁的使用场景,什么情况适合使用MCS锁?Java 怎么使用MCS锁?
Java基础多线程之主线程等待子线程结束,Java基础编程之多线程入门学习篇。主要讲解几种方法来实现Java多线程中主线程等待子线程结束的最快方式。
线程安全是像Java这样的语言/平台中的类的重要质量,我们经常在线程之间共享对象。由于缺乏线程安全性而导致的问题非常难以调试,因为它们零星且几乎不可能有意再现。你如何测试你的对象以确保它们是线程...
1. 线程的安全性问题:线程安全和非线程安全: 一个类在单线程环境下能够正常运行,并且在多线程环境下,使用方不做特别处理也能运行正常,我们就称其实线程安全的
Java多线程生命周期