精华内容
下载资源
问答
  • 偏向锁 / 轻量级锁 / 重量级锁 自旋锁 上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释。 公平锁 / 非公平锁 公平锁 公平锁是指...

    Java 中15种锁的介绍

    在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下:

    公平锁 / 非公平锁

    可重入锁 / 不可重入锁

    独享锁 / 共享锁

    互斥锁 / 读写锁

    乐观锁 / 悲观锁

    分段锁

    偏向锁 / 轻量级锁 / 重量级锁

    自旋锁

    上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释。

    公平锁 / 非公平锁

    公平锁

    公平锁是指多个线程按照申请锁的顺序来获取锁。

    非公平锁

    非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

    对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。 对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

    可重入锁 / 不可重入锁

    可重入锁

    广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁

    synchronized void setA() throws Exception{

    Thread.sleep(1000);

    setB();

    }

    synchronized void setB() throws Exception{

    Thread.sleep(1000);

    }

    上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

    不可重入锁

    不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。看到一个经典的讲解,使用自旋锁来模拟一个不可重入锁,代码如下

    import java.util.concurrent.atomic.AtomicReference;

    public class UnreentrantLock {

    private AtomicReference owner = new AtomicReference();

    public void lock() {

    Thread current = Thread.currentThread();

    //这句是很经典的“自旋”语法,AtomicInteger中也有

    for (;😉 {

    if (!owner.compareAndSet(null, current)) {

    return;

    }

    }

    }

    public void unlock() {

    Thread current = Thread.currentThread();

    owner.compareAndSet(current, null);

    }

    }

    代码也比较简单,使用原子引用来存放线程,同一线程两次调用lock()方法,如果不执行unlock()释放锁的话,第二次调用自旋的时候就会产生死锁,这个锁就不是可重入的,而实际上同一个线程不必每次都去释放锁再来获取锁,这样的调度切换是很耗资源的。

    把它变成一个可重入锁:

    import java.util.concurrent.atomic.AtomicReference;

    public class UnreentrantLock {

    private AtomicReference owner = new AtomicReference();

    private int state = 0;

    public void lock() {

    Thread current = Thread.currentThread();

    if (current == owner.get()) {

    state++;

    return;

    }

    //这句是很经典的“自旋”式语法,AtomicInteger中也有

    for (;😉 {

    if (!owner.compareAndSet(null, current)) {

    return;

    }

    }

    }

    public void unlock() {

    Thread current = Thread.currentThread();

    if (current == owner.get()) {

    if (state != 0) {

    state–;

    } else {

    owner.compareAndSet(current, null);

    }

    }

    }

    }

    在执行每次操作之前,判断当前锁持有者是否是当前对象,采用state计数,不用每次去释放锁。

    ReentrantLock中可重入锁实现

    这里看非公平锁的锁获取方法:

    final boolean nonfairTryAcquire(int acquires) {

    final Thread current = Thread.currentThread();

    int c = getState();

    if (c == 0) {

    if (compareAndSetState(0, acquires)) {

    setExclusiveOwnerThread(current);

    return true;

    }

    }

    //就是这里

    else if (current == getExclusiveOwnerThread()) {

    int nextc = c + acquires;

    if (nextc < 0) // overflow

    throw new Error(“Maximum lock count exceeded”);

    setState(nextc);

    return true;

    }

    return false;

    }

    在AQS中维护了一个private volatile int state来计数重入次数,避免了频繁的持有释放操作,这样既提升了效率,又避免了死锁。

    独享锁 / 共享锁

    独享锁和共享锁在你去读C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就会发现,它俩一个是独享一个是共享锁。

    独享锁:该锁每一次只能被一个线程所持有。

    共享锁:该锁可被多个线程共有,典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写锁确每次只能被独占。

    另外读锁的共享可保证并发读是非常高效的,但是读写和写写,写读都是互斥的。

    独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。 对于Synchronized而言,当然是独享锁。

    互斥锁 / 读写锁

    互斥锁

    在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。

    如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源

    读写锁

    读写锁既是互斥锁,又是共享锁,read模式是共享,write是互斥(排它锁)的。

    读写锁有三种状态:读加锁状态、写加锁状态和不加锁状态

    读写锁在Java中的具体实现就是ReadWriteLock

    一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。 只有一个线程可以占有写状态的锁,但可以有多个线程同时占有读状态锁,这也是它可以实现高并发的原因。当其处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;如果是处于读状态锁下,允许其它线程获得它的读状态锁,但是不允许获得它的写状态锁,直到所有线程的读状态锁被释放;为了避免想要尝试写操作的线程一直得不到写状态锁,当读写锁感知到有线程想要获得写状态锁时,便会阻塞其后所有想要获得读状态锁的线程。所以读写锁非常适合资源的读操作远多于写操作的情况。

    乐观锁 / 悲观锁

    悲观锁

    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

    乐观锁

    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

    分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

    并发容器类的加锁机制是基于粒度更小的分段锁,分段锁也是提升多并发程序性能的重要手段之一。

    在并发程序中,串行操作是会降低可伸缩性,并且上下文切换也会减低性能。在锁上发生竞争时将通水导致这两种问题,使用独占锁时保护受限资源的时候,基本上是采用串行方式—-每次只能有一个线程能访问它。所以对于可伸缩性来说最大的威胁就是独占锁。

    我们一般有三种方式降低锁的竞争程度:

    1、减少锁的持有时间

    2、降低锁的请求频率

    3、使用带有协调机制的独占锁,这些机制允许更高的并发性。

    在某些情况下我们可以将锁分解技术进一步扩展为一组独立对象上的锁进行分解,这成为分段锁。

    其实说的简单一点就是:

    容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

    比如:在ConcurrentHashMap中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设使用合理的散列算法使关键字能够均匀的分部,那么这大约能使对锁的请求减少到越来的1/16。也正是这项技术使得ConcurrentHashMap支持多达16个并发的写入线程。

    偏向锁 / 轻量级锁 / 重量级锁

    锁的状态:

    无锁状态

    偏向锁状态

    轻量级锁状态

    重量级锁状态

    锁的状态是通过对象监视器在对象头中的字段来表明的。 四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。 这四种状态都不是Java语言中的锁,而是Jvm为了提高锁的获取与释放效率而做的优化(使用synchronized时)。

    偏向锁

    偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

    轻量级

    轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

    重量级锁

    重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

    自旋锁

    我们知道CAS算法是乐观锁的一种实现方式,CAS算法中又涉及到自旋锁,所以这里给大家讲一下什么是自旋锁。

    简单回顾一下CAS算法

    CAS是英文单词Compare and Swap(比较并交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数

    需要读写的内存值 V

    进行比较的值 A

    拟写入的新值 B

    更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B,否则不会执行任何操作。一般情况下是一个自旋操作,即不断的重试。

    什么是自旋锁?

    自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

    它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。

    Java如何实现自旋锁?

    下面是个简单的例子:

    public class SpinLock {

    private AtomicReference cas = new AtomicReference();

    public void lock() {

    Thread current = Thread.currentThread();

    // 利用CAS

    while (!cas.compareAndSet(null, current)) {

    // DO nothing

    }

    }

    public void unlock() {

    Thread current = Thread.currentThread();

    cas.compareAndSet(current, null);

    }

    }

    lock()方法利用的CAS,当第一个线程A获取锁的时候,能够成功获取到,不会进入while循环,如果此时线程A没有释放锁,另一个线程B又来获取锁,此时由于不满足CAS,所以就会进入while循环,不断判断是否满足CAS,直到A线程调用unlock方法释放了该锁。

    自旋锁存在的问题

    1、如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。

    2、上面Java实现的自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。

    自旋锁的优点

    1、自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快 2、非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)

    可重入的自旋锁和不可重入的自旋锁

    文章开始的时候的那段代码,仔细分析一下就可以看出,它是不支持重入的,即当一个线程第一次已经获取到了该锁,在锁释放之前又一次重新获取该锁,第二次就不能成功获取到。由于不满足CAS,所以第二次获取会进入while循环等待,而如果是可重入锁,第二次也是应该能够成功获取到的。

    而且,即使第二次能够成功获取,那么当第一次释放锁的时候,第二次获取到的锁也会被释放,而这是不合理的。

    为了实现可重入锁,我们需要引入一个计数器,用来记录获取锁的线程数。

    public class ReentrantSpinLock {

    private AtomicReference cas = new AtomicReference();

    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);

    }

    }

    }

    }

    自旋锁与互斥锁

    自旋锁与互斥锁都是为了实现保护资源共享的机制。

    无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。

    获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。

    自旋锁总结

    自旋锁:线程获取锁的时候,如果锁被其他线程持有,则当前线程将循环等待,直到获取到锁。

    自旋锁等待期间,线程的状态不会改变,线程一直是用户态并且是活动的(active)。

    自旋锁如果持有锁的时间太长,则会导致其它等待获取锁的线程耗尽CPU。

    自旋锁本身无法保证公平性,同时也无法保证可重入性。

    基于自旋锁,可以实现具备公平性和可重入性质的锁。

    作者:Python编程社区
    链接:https://www.jianshu.com/p/12dadbbfaf08
    来源:简书

    展开全文
  • 自旋 一般都是采用一个循环来占用某个资源,可想而知,它不适合对某个资源长期占用,采用循环会严重损耗CPU性能。 CAS CAS即Compare and Set,即比较设置的意思,它的核心思想是通过比较预期值原值...

    自旋锁

    一般都是采用一个循环来占用某个资源,可想而知,它不适合对某个资源长期占用,采用循环会严重损耗CPU性能。

    CAS

    CAS即Compare and Set,即比较和设置的意思,它的核心思想是通过比较预期值和原值是否一样,如果不一样,则进行修改成预期,如果一样就返回false。Java里AtomicInteger.compareAndSet(int,int);就是典型的CAS操作。
    细心的读者会发现,如果一个值原来是A,变成了B,然后又变成了A,这时候另外的线程访问的时候,发现值没有变化,但实际上已经发生了变化,从而导致更新这个值。
    解决的常用办法是:设置一个版本号version,每次读取的时候,顺便将version也读取出来,写回的时候,若当前读出的version+1比记录中的version大,则写入,这个比较大小与写入操作必选是原子的。
    试想一种应用场景,两个线程都读取到某一资源,都读取到了version字段信息,其中一个线程率先写回,在写回的时候,发现读到的version+1>记录中的version,则将记录中的version设置程version+1,另一个线程写回的时候,发现version+1==记录中的version了,这时候,它就自旋,重新读取version,然后再重复上面的操作。

    偏向锁

    在对象头和帧栈中记录偏向线程的ID,下次再访问该资源的时候,无需争抢,可直接获取到该资源,这就好像你去借书,借书之前需要争抢,借到该书之后,该书就暂时属于你一样。
    由于多个线程并发同一资源的时候,发现总是由同一线程获取到锁,这时候,偏向锁是你要的,当多个线程并发的时候,偏向的线程无需争抢锁,发现有自身的ID,则可直接拿到资源。

    轻量级锁

    一般是由CAS加自旋判断来实现,应用在追求相应时间,但是这种锁相对互斥锁而言,更加消耗CPU性能,因为自旋会产生较大开销。

    重量级锁(互斥锁)

    synchronized就是一种重量级锁,当轻量级锁的自旋时间太长,那么就会蜕化成互斥锁。

    展开全文
  • 临界区和互斥量都可用来实现此,通常情况下操作失败会将该线程睡眠等待释放时被唤醒 自旋(spinlock): 同样用来标记只能有一个线程访问该对象,在同一线程多次加锁操作会造成死锁;使用硬件提供的swap指令...

    互斥锁(mutexlock):


    最常使用于线程同步的锁;标记用来保证在任一时刻,只能有一个线程访问该对象,同一线程多次加锁操作会造成死锁;临界区和互斥量都可用来实现此锁,通常情况下锁操作失败会将该线程睡眠等待锁释放时被唤醒

    自旋锁(spinlock):

    同样用来标记只能有一个线程访问该对象,在同一线程多次加锁操作会造成死锁;使用硬件提供的swap指令或test_and_set指令实现;同互斥锁不同的是在锁操作需要等待的时候并不是睡眠等待唤醒,而是循环检测保持者已经释放了锁,这样做的好处是节省了线程从睡眠状态到唤醒之间内核会产生的消耗,在加锁时间短暂的环境下这点会提高很大效率

    读写锁(rwlock):

    高级别锁,区分读和写,符合条件时允许多个线程访问对象。处于读锁操作时可以允许其他线程和本线程的读锁, 但不允许写锁, 处于写锁时则任何锁操作都会睡眠等待;常见的操作系统会在写锁等待时屏蔽后续的读锁操作以防写锁被无限孤立而等待,在操作系统不支持情况下可以用引用计数加写优先等待来用互斥锁实现。 读写锁适用于大量读少量写的环境,但由于其特殊的逻辑使得其效率相对普通的互斥锁和自旋锁要慢一个数量级;值得注意的一点是按POSIX标准 在线程申请读锁并未释放前本线程申请写锁是成功的,但运行后的逻辑结果是无法预测

    递归锁(recursivelock)

    严格上讲递归锁只是互斥锁的一个特例,同样只能有一个线程访问该对象,但允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作; windows下的临界区默认是支持递归锁的,而linux下的互斥量则需要设置参数PTHREAD_MUTEX_RECURSIVE_NP,默认则是不支持 

    展开全文
  • 自旋锁和互斥锁的区别

    千次阅读 2017-11-25 21:21:50
    自旋锁和互斥锁的区别POSIX threads(简称Pthreads)是在多核平台上进行并行编程的一套API。线程同步是并行编程中非常重要的通讯手段,其中最典型的应用就是用Pthreads提供的机制(lock)来对多个线程之间的共享临界区...

    自旋锁和互斥锁的区别

    POSIX threads(简称Pthreads)是在多核平台上进行并行编程的一套API。线程同步是并行编程中非常重要的通讯手段,其中最典型的应用就是用

    Pthreads提供的锁机制(lock)来对多个线程之间的共享临界区(Critical Section)进行保护(另一种常用的同步机制是barrier)。

    Pthreads提供了多种锁机制:

    • Mutex(互斥量):pthread_mutex_t
    • Spin lock(自旋锁): pthread_spin_t
    • Condition Variable(条件变量): pthread_cond_t
    • Read/Write lock(读写锁):pthread_rwlock_t

    Pthreads提供的Mutex锁操作相关的API主要有:

    • pthread_mutex_lock(pthread_mutex_t *mutex);
    • pthread_mutex_trylock(pthread_mutex_t *mutex);
    • pthread_mutex_unlock(pthread_mutex_t *mutex);

    Pthreads提供的Spin Lock锁操作相关的API主要有:

    • pthread_spin_lock(pthread_spinlock_t *lock);
    • pthread_spin_trylock(pthread_spinlock_t *lock);
    • pthread_spin_unlock(pthread_spinlock_t *lock);

    从实现原理上来讲,Mutex(互斥锁)属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞,

    Core0会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其它的任务而不必进行忙等待。而Spin lock(自旋锁)则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。

    自旋锁(Spin lock)

    自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋锁”的作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。

    自旋锁的不足之处:

    自旋锁一直占用着CPU,他在未获得锁的情况下,一直运行(自旋),所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。

    在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。

    因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。


    自旋锁-原理


    互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。由此我们可以看出,自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:

    1、死锁试图递归地获得自旋锁必然会引起死锁:递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。

    在递归程序中使用自旋锁应遵守下列策略:

    递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。此外如果一个进程已经将资源锁定,那么,即使其它申请这个资源的进程不停地疯狂"自旋",也无法获得资源,从而进入死循环。

    2、过多占用cpu资源。如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,单cpu的时候自旋锁会让其它process动不了. 因此,一般自旋锁实现会有一个参数限定最多持续尝试次数. 超出后, 自旋锁放弃当前time slice. 等下一次机会

    由此可见,自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作,


    转自:https://www.cnblogs.com/guagua2016/p/6068925.html

    https://baike.so.com/doc/9186718-9519957.html

    ------------------------------------------------------------

    自旋锁 互斥锁 读写锁 递归锁


    互斥锁(mutexlock):

    最常使用于线程同步的锁;标记用来保证在任一时刻,只能有一个线程访问该对象,同一线程多次加锁操作会造成死锁;临界区和互斥量都可用来实现此锁,通常情况下锁操作失败会将该线程睡眠等待锁释放时被唤醒

    自旋锁(spinlock):

    同样用来标记只能有一个线程访问该对象,在同一线程多次加锁操作会造成死锁;使用硬件提供的swap指令或test_and_set指令实现;同互斥锁不同的是在锁操作需要等待的时候并不是睡眠等待唤醒,而是循环检测保持者已经释放了锁,这样做的好处是节省了线程从睡眠状态到唤醒之间内核会产生的消耗,在加锁时间短暂的环境下这点会提高很大效率

    读写锁(rwlock):

    高级别锁,区分读和写,符合条件时允许多个线程访问对象。处于读锁操作时可以允许其他线程和本线程的读锁, 但不允许写锁, 处于写锁时则任何锁操作都会睡眠等待;常见的操作系统会在写锁等待时屏蔽后续的读锁操作以防写锁被无限孤立而等待,在操作系统不支持情况下可以用引用计数加写优先等待来用互斥锁实现。 读写锁适用于大量读少量写的环境,但由于其特殊的逻辑使得其效率相对普通的互斥锁和自旋锁要慢一个数量级;值得注意的一点是按POSIX标准 在线程申请读锁并未释放前本线程申请写锁是成功的,但运行后的逻辑结果是无法预测

    递归锁(recursivelock):

    严格上讲递归锁只是互斥锁的一个特例,同样只能有一个线程访问该对象,但允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作; windows下的临界区默认是支持递归锁的,而linux下的互斥量则需要设置参数PTHREAD_MUTEX_RECURSIVE_NP,默认则是不支持

    大读者锁(brlock-Big Reader Lock)

    大读者锁是读写锁的高性能版,读者可以非常快地获得锁,但写者获得锁的开销比较大。大读者锁只存在于2.4内核中,在2.6中已经没有这种锁(提醒读者特别注意)。它们的使用与读写锁的使用类似,只是所有的大读者锁都是事先已经定义好的。这种锁适合于读多写少的情况,它在这种情况下远好于读写锁。

    大读者锁的实现机制是:每一个大读者锁在所有CPU上都有一个本地读者写者锁,一个读者仅需要获得本地CPU的读者锁,而写者必须获得所有CPU上的锁。

    大读者锁的API非常类似于读写锁,只是锁变量为预定义的锁ID。

    大内核锁(BKL--Big Kernel Lock)

    大内核锁本质上也是自旋锁,但是它又不同于自旋锁,自旋锁是不可以递归获得锁的,因为那样会导致死锁。但大内核锁可以递归获得锁。大内核锁用于保护整个内核,而自旋锁用于保护非常特定的某一共享资源。进程保持大内核锁时可以发生调度,具体实现是:在执行schedule时,schedule将检查进程是否拥有大内核锁,如果有,它将被释放,以致于其它的进程能够获得该锁,而当轮到该进程运行时,再让它重新获得大内核锁。注意在保持自旋锁期间是不运行发生调度的。

    需要特别指出,整个内核只有一个大内核锁,其实不难理解,内核只有一个,而大内核锁是保护整个内核的,当然有且只有一个就足够了。

    还需要特别指出的是,大内核锁是历史遗留,内核中用的非常少,一般保持该锁的时间较长,因此不提倡使用它。从2.6.11内核起,大内核锁可以通过配置内核使其变得可抢占(自旋锁是不可抢占的),这时它实质上是一个互斥锁,使用信号量实现。

    RCU(Read-Copy Update)

    RCU(Read-Copy Update),顾名思义就是读-拷贝修改,它是基于其原理命名的。对于被RCU保护的共享数据结构,读者不需要获得任何锁就可以访问它但写者在访问它时首先拷贝一个副本,然后对副本进行修改,最后使用一个回调(callback)机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据。这个时机就是所有引用该数据的CPU都退出对共享数据的操作。

    RCU也是读写锁的高性能版本,但是它比大读者锁具有更好的扩展性和性能。 RCU既允许多个读者同时访问被保护的数据,又允许多个读者和多个写者同时访问被保护的数据(注意:是否可以有多个写者并行访问取决于写者之间使用的同步机制),读者没有任何同步开销,而写者的同步开销则取决于使用的写者间同步机制。但RCU不能替代读写锁,因为如果写比较多时,对读者的性能提高不能弥补写者导致的损失。

    顺序锁(seqlock)

    顺序锁也是对读写锁的一种优化,对于顺序锁,读者绝不会被写者阻塞,也就说,读者可以在写者对被顺序锁保护的共享资源进行写操作时仍然可以继续读,而不必等待写者完成写操作,写者也不需要等待所有读者完成读操作才去进行写操作。但是,写者与写者之间仍然是互斥的,即如果有写者在进行写操作,其他写者必须自旋在那里,直到写者释放了顺序锁。

    这种锁有一个限制,它必须要求被保护的共享资源不含有指针,因为写者可能使得指针失效,但读者如果正要访问该指针,将导致OOPs。

    如果读者在读操作期间,写者已经发生了写操作,那么,读者必须重新读取数据,以便确保得到的数据是完整的。

    这种锁对于读写同时进行的概率比较小的情况,性能是非常好的,而且它允许读写同时进行,因而更大地提高了并发性。


    转自:http://blog.csdn.net/qq100440110/article/details/51076609
    展开全文
  • 临界区和互斥量都可用来实现此,通常情况下操作失败会将该线程睡眠等待释放时被唤醒 自旋(spinlock): 同样用来标记只能有一个线程访问该对象,在同一线程多次加锁操作会造成死锁;使用硬件提供的swap指令...
  • 文章目录什么是自旋参考链接自旋锁和互斥锁的区别参考链接 什么是自旋 多线程对共享资源访问, 为防止并发引起的相关问题, 常引入的机制来处理并发问题。 \newline \newline 获取到资源的...
  • 是什么,我们为什么要用到?回到问题的本质,我们在什么场景下会用到,是在针对于公共资源,也就是临界资源的使用。对于多线程编程,当两个或多个线程同时访问或对一个临界资源操作的时候,为了防止出现数据不...
  • 共享(S) 又称为读,可以查看但无法修改删除的一种数据。如果事务T对数据A加上共享...又称为写、独占,若事务T对数据对象A加上X,则只允许T读取修改A,其他任何事务都不能再对A加任何类型的...
  • Java并发:互斥锁和读写

    千次阅读 2016-03-29 22:59:06
    互斥锁线程在进入同步代码块之前会自动获取,并且在退出同步代码块时会自动释放,当某个线程请求一个由其他线程持有的时,发出请求的线程就会阻塞。互斥锁其实提供了一种原子操作,让所有线程以串行的方式执行...
  • 自旋可以使线程在没有取得的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得,则继续执行。若线程依然不能获得,才会被挂起。 使用...
  • 一、使用互斥锁1、初始化互斥量pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥量 int pthread_mutex_init(pthread_mutex_t*mutex,pthread_mutexattr_t*attr);//动态初始化互斥量 int pthread_...
  • 答案是A, synchronized关键字是同步代码块关键字, 对对象互斥锁 详解: synchronized: 用来给对象和方法或者代码块加锁. 当它锁定一个方法或者一个代码块的时候, 同一时刻最多只有一个线程执行这个段代码 volatile:...
  • 自旋互斥锁有点类似,只是自旋不会引起调用者睡眠,如果自旋已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋的保持者已经释放了,”自旋”一词就是因此而得名。其作用是为了解决某项资源...
  • 临界区和互斥量都可用来实现此,通常情况下操作失败会将该线程睡眠等待释放时被唤醒 自旋(spinlock): 同样用来标记只能有一个线程访问该对象,在同一线程多次加锁操作会造成死锁;使用硬件提供的swap指令...
  • 自旋可以使线程在没有取得的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得,则继续执行。若线程依然不能获得,才会被挂起。 使用...
  • 原子操作 信号量 自旋 互斥锁

    千次阅读 2014-10-20 22:36:16
    原子操作 信号量 自旋 互斥锁
  • 自旋互斥锁的区别

    千次阅读 2019-07-21 17:33:47
    自旋锁和互斥锁的区别 POSIX threads(简称Pthreads)是在多核平台上进行并行编程的一套API。线程同步是并行编程中非常重要的通讯手段,其中最典型的应用就是用 Pthreads提供的机制(lock)来对多个线程之间的共享...
  • 可以看到除了 OSSpinLock 外,dispatch_semaphore  pthread_mutex 性能是最高的。苹果在新系统中已经优化了 pthread_mutex 的性能,所以它看上去 OSSpinLock 差距并没有那么大了。 可以看到YYKit组件...
  • pthread的互斥自旋

    千次阅读 2016-07-29 20:53:32
    一、自旋互斥量的区别 在多处理器环境中,自旋最多只能被一个可执行线程持有。如果一个可执行线程试图获得一个被争用(已经被持有的)自旋,那么该线程就会一直进行忙等待,自旋,也就是空转,等待重新可用...
  • 一,使用互斥锁 1,初始化互斥量 [cpp] view plain copy pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥量  int pthread_mutex_init(pthread_mutex_t*...
  • 是偏向于一个给定的线程,锁定解锁可以在不使用原子操作的情况下由该线程执行。当的偏差被撤销时,它将恢复到正常状态下面描述的锁定方案。注意:Hotspot不会在JVM启动的最初几秒(目前是4秒)内启用偏向锁定...
  • 三、的优化 1、升级 2、粗化 3、消除 一、Synchronized使用场景 Synchronized是一个同步关键字,在某些多线程场景下,如果不进行同步会导致数据不安全,而Synchronized关键字就是用于代码同步。什么...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 50,081
精华内容 20,032
关键字:

类锁和对象锁互斥