Java学习(十八)锁

一、内容结构

在这里插入图片描述

二、锁初识

1、锁类型

1)悲观锁

        对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候,一定有别的线程在同时修改数据,所以在获取数据的时候会先加锁,保证数据不会被别的数据修改。在Java中,常见的synchronizedLock使用的都是悲观锁;

2)乐观锁(无锁)

        悲观锁认为自己的在使用数据的时候,不会有其他的线程修改数据,因此不会加锁。在更新数据的时候,会判断别的线程有没有更新这个数据。如果这个数据没有被更新,则会把数据修改;如果这个数据已经被更新或者正在被更新,则会报错或者自动重试。在同一时刻多个线程同时修改数据的情况,只有一个线程能够修改成功,其他线程都需要重试;在Java中,乐观锁是通过原子操作实现的,原子类递增的操作通过CAS自旋实现;

3)悲观锁VS乐观锁

  • 悲观锁适合写操作多的场景,先加锁保证数据修改正确
  • 乐观锁适合读操作多的场景,不加锁保证并发读取的高性能

4)代码对比

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
class PessimistiLockUseType {
    public synchronized void SynchronizedMethod() {
        // operator
    }
    ReentrantLock lock = new ReentrantLock();
    public void ReentrantLockMethod() {
        lock.lock();
        // operator
        lock.unlock();
    }
}
class OptimisticLockUseType {
    AtomicInteger atomicInteger = new AtomicInteger();
    public void AtomicMethod() {
        atomicInteger.incrementAndGet(); //自增1 类比于 i++
    }
}

2、锁等级

  • 无锁,采用CAS自旋;
  • 偏向锁,当只有一个线程重复执行一段同步代码的时候,再次执行会自动获取锁;
  • 轻量级锁,当锁获取失败的时候,线程不阻塞,采用自旋不断询问锁的方式获取锁;
  • 重量级锁,当锁获取失败的时候,线程阻塞被挂起,等待被唤醒之后获取锁;

3、锁公平性

1)公平锁

        多个线程按照申请锁的顺序进入队列排队获取锁。

2)非公平锁

        多个线程申请锁的时候,先直接尝试(插队)获取锁,获取不到才会到队列的队尾排队获取锁。

3)公平锁VS非公平锁

  • 公平锁的优点等待锁的线程不会饿死,缺点是整体吞吐效率相对非公平锁低,等待队列中除第一个线程外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大;
  • 非公平锁的优点是直接尝试(插队)获取锁的时候,如果此时锁刚好可用,那么这个线程可以无需阻塞直接拿到锁,以此减少阻塞唤醒线程的开销,整体的吞吐效率高。因为线程有几率无需阻塞直接拿到锁,CPU不必唤醒所有线程。但是也有缺点,就是会出现饥饿线程,因为插队一直有,所以在队尾的线程要等很久才能拿到锁;

4、锁可重入性

1)基础知识

        先来看一段代码,如下

class ReentrantLockUserType {
    public synchronized void dothing() {
        // operator
        doOtherThing();
    }

    public synchronized void doOtherThing() {
        // operator
    }
}

        synchronized包含的同步代码块、方法都会采用锁机制,现在线程A调用方法dothing(),而dothing()方法会调用doOtherThing()方法,这样一来被synchronized修饰的dothing()方法会有一把锁,被synchronized修饰的doOtherThing()方法也会有一把锁。

2)可重入锁(递归锁)

        同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁,不会因为之前已经获取过还没释放而阻塞。但是有一个前提,锁对象必须是同一个对象或者class。如基础知识代码所展示,从dothing()方法进入doOtherThing()方法,就会自动获取doOtherThing()方法的锁。

3)不可重入锁(非递归锁)

        如基础知识代码所展示,从dothing()方法进入doOtherThing()方法,如果需要获取doOtherThing()方法的锁,就需要将dothing()方法的锁释放掉,这样会导致死锁。

5、锁共享性

1)读锁(共享锁)

        锁一次可以被多个线程持有,但是多个线程只能对数据进行读取操作,不能修改数据。当需要对数据进行修改操作的时候,需要等待其他持有读锁的多个线程都被释放,再单独持有写锁,进行数据修改操作,且在获取写锁之后,其他线程只能排队等待,不能同时获取。

2)写锁(排它锁)

        锁一次只能被一个线程持有。获取排它锁的线程,既可以读取数据,也可以修改数据。

扫描二维码关注公众号,回复: 9971115 查看本文章

二、伪并发获取锁

1、基础知识

        当一个线程访问同步代码并获取锁的时候,会在Mark Word里存储锁偏向的线程ID。在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是通过检测Mark Word里是否存储着指向当前线程的偏向锁。

2、偏向锁

        一段同步代码(或者同步方法)一直只是被一个线程访问,也就是伪并发。那么该线程会自动获取锁,降低获取锁的代价。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换线程ID的时候依赖一次CAS原子指令,提高性能。

        当只有一个线程多次访问执行这段代码块的时候,偏向锁会让该线程(假设是线程A)自动获取锁。但是如果一旦出现了其他线程(假设是线程B)也需要访问执行这段代码块,持有偏向锁的线程A就会释放锁。但是偏向锁的撤销,不是线程主动释放的,需要等待全局安全点(即在这个时间点上没有字节码正在执行),首先暂停线程A,判断锁对象是否处于被锁定状态。撤销偏向锁之后,会恢复到无锁或者轻量级锁的状态。

三、锁获取失败,线程不阻塞

1、基础知识

        在Java中,阻塞一个线程使用wait(),唤醒一个线程使用notify(),唤醒所有线程使用notifyall()。阻塞或者唤醒一个线程需要操作系统切换CPU状态来完成,十分耗费处理器时间。如果代码中的内容过于简单,那么状态转换的时间会比代码执行时间还要长,得不偿失。

        每一个Java对象都有一把看不见的对象锁,通常称为内部锁或者Monitor锁。Monitor可以理解为一个同步工具或者同步机制,它是线程私有的数据结构。每一个线程有一个Monitor Record列表和一个全局的可用列表,每一个被锁住的对象都会和一个Monitor关联,通过Monitor中的Owner字段标记持有锁的线程,表示这把锁被这个线程占用。synchronized正是通过Monitor来实现线程同步,可以理解为是在排队使用同一把锁访问共享数据。顺便科普一下,Monitor依赖于底层操作系统的Mutex Lock(互斥锁)来实现线程同步的。

2、轻量级锁

        在多核CPU的机器上,可以让多个线程并行执行。我们可以让后面那个请求锁的线程(假设是线程A)不放弃CPU的执行时间,等待持有锁的运行线程(假设是线程B)释放锁。在线程A等待锁释放的时间内,会进行自旋,也就是进行for循环等待。如果线程B释放了锁,那么线程A就可以不需要阻塞操作就获得锁执行代码修改数据。

1)自旋锁

        因为自旋锁需要不断for循环进行锁是否释放的询问,也是占用CPU的。如果锁被占用的时间很短,自旋锁的效果就会比阻塞被挂起好;如果锁一直被占用得不到释放,那么自旋锁的线程就会白白浪费CPU处理时间。在Java中,自旋锁自旋询问锁是否被释放的等待时间通常有个限制(默认是十次),如果超过限制锁还未被释放,就会被挂起,等待唤醒;

2)自适应自旋锁

        自适应顾名思义,有一点智能调节的意思,自旋的时间不再有固定的限制。如果线程A、B、C都需要锁L,线程B持有锁进行代码执行。如果线程A在自旋的过程中很快等到了锁的释放,那么系统判断锁L通过自旋很容易获取,线程C也会采用自旋锁的方式等待锁L释放;反之,如果线程A在自旋的过程中等了很久都没有等到锁的释放,那么系统判断锁L通过自旋难以获取,线程C可能会省略掉自旋过程,直接阻塞被挂起;

四、锁获取失败,线程阻塞

1、重量级锁

        将除了持有锁线程以外其他所有等待该锁的线程都阻塞挂起。

发布了622 篇原创文章 · 获赞 150 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/feizaoSYUACM/article/details/104861710
今日推荐