Qouson's blog Qouson's blog
首页
  • Java 基础

    • 基础
    • String
  • Java 中级

    • 网络编程
  • Java 高级

    • JVM
    • 多线程
  • Spring
  • SpringMVC
  • SpringBoot
  • MySQL
  • Redis
  • MQ
  • ZooKeeper
  • git
  • linux
  • 设计模式
  • 数据结构与算法
  • 计算机基础
  • Java相关框架
  • 分布式
  • DDD领域驱动设计
  • 系统设计
  • 杂乱无章
Java知识图谱
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

qouson

Java界的小学生
首页
  • Java 基础

    • 基础
    • String
  • Java 中级

    • 网络编程
  • Java 高级

    • JVM
    • 多线程
  • Spring
  • SpringMVC
  • SpringBoot
  • MySQL
  • Redis
  • MQ
  • ZooKeeper
  • git
  • linux
  • 设计模式
  • 数据结构与算法
  • 计算机基础
  • Java相关框架
  • 分布式
  • DDD领域驱动设计
  • 系统设计
  • 杂乱无章
Java知识图谱
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

  • String

  • 网络编程

  • JVM

  • 多线程

    • 多线程-线程基础
    • 多线程-AQS
    • 多线程-锁
      • 锁
        • 可重入锁
        • LockSupport
    • JUC
  • JavaSE
  • 多线程
qouson
2024-05-23
目录

多线程-锁

# 多线程

# 锁

# 可重入锁

  • 可重入锁又名递归锁
    • 是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁,前提锁对象是同一个对象
    • Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可以一定程度避免死锁
  • 分开解释
    • 可:可以
    • 重:再次
    • 入:进入
    • 锁:同步锁
    • 进入什么
      • 进入同步域(同步代码块/方法或显式锁锁定的代码)
    • 一句话
      • 一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入
      • 自己可以获取自己的内部锁
  • 种类
    • 隐式锁(即synchronized关键字使用的锁)默认是可重入锁
      • 同步块
      • 同步方法
      • synchronized的重入实现原理
        • 每一个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针
        • 当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且计数器加1。
        • 在目标锁对象的计数器不为零的情况下,如果锁对象持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
        • 当执行monitorexit时,Java虚拟机则需要将锁对象的计数器减1。计数器为零代表锁已经释放。
    • 显式锁(即Lock)也有ReentrantLock这样的可重入锁
  • Demo
public class ReentrantLockDemo {
    //同步对象
    static Object objectLockA = new Object();
    public static void m1(){
        new Thread(() -> {
            synchronized (objectLockA){
                System.out.println(Thread.currentThread().getName() + "\t外层代码块!");
                synchronized (objectLockA){
                    System.out.println(Thread.currentThread().getName() + "\t内层代码块!");
                }
            }
        },"AAA").start();
    }
    //同步方法
    public synchronized void method1(){
        System.out.println(Thread.currentThread().getName() + "\t外层方法调用");
        method2();
    }
    public synchronized  void method2(){
        System.out.println(Thread.currentThread().getName() + "\t内层方法调用");
    }
    //Lock
    static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
//        m1();
//        new ReentrantLockDemo().method1();
        new Thread(() -> {
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName() + "\t外层lock");
                lock.lock();
                try{
                    System.out.println(Thread.currentThread().getName() + "\t内层lock");
                }finally{
                    lock.unlock();
                }
            }finally{
                lock.unlock();
            }
        },"BBB").start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# LockSupport

  • LockSupport是什么?
    • LockSupport是用来创建锁和其他同步线程的基本线程阻塞原语
    • LockSupport中的park()和unpark()的作用分别是阻塞线程和解除阻塞线程
  • 线程等待唤醒机制(wait/notify)改良加强
    • 3种让线程等待和唤醒的方法

      • 方式1:使用Object类的wait()方法让线程等待,使用Object类中的notify()方法唤醒线程

        • Demo
        public class WaitNotifyDemo {
          static Object objectLock = new Object();
          public static void main(String[] args) {
              new Thread(() -> {
                  try{ TimeUnit.SECONDS.sleep(5); }catch(Exception e){e.printStackTrace();}
                  synchronized (objectLock){
                      System.out.println(Thread.currentThread().getName() + "\t come in");
                      try {
                          objectLock.wait();
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                      System.out.println(Thread.currentThread().getName() + "\t 被唤醒");
                  }
              },"AA").start();
              new Thread(() -> {
                  synchronized (objectLock){
                      objectLock.notify();
                      System.out.println(Thread.currentThread().getName() + "\t 唤醒某一个线程");
                  }
              },"BB").start();
          }
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        • 缺点
          • 只能在同步方法或同步代码块中使用,且成对出现使用,否则报java.lang.IllegalMonitorStateException。
          • 必须先wait后notify,否则无法唤醒,死锁。
      • 方式2:使用Condition类的await()方法让线程等待,使用Condition类的signal()方法唤醒线程

        • Demo
        public class AwaitSignalDemo {
            static Lock lock = new ReentrantLock();
            static Condition condition = lock.newCondition();
            public static void main(String[] args) {
                new Thread(() -> {
        //            try{ TimeUnit.SECONDS.sleep(5); }catch(Exception e){e.printStackTrace();}
                    lock.lock();
                    try{
                        System.out.println(Thread.currentThread().getName() + "\t come in");
                        try {
                            condition.await();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "\t 被唤醒");
                    }finally{
                        lock.unlock();
                    }
                },"AA").start();
                new Thread(() -> {
                    lock.lock();
                    try{
                        condition.signal();
                        System.out.println(Thread.currentThread().getName() + "\t 通知");
                    }finally{
                        lock.unlock();
                    }
                },"BB").start();
            }
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        • 缺点
          • 只能在lock.lock(),lock.unlock()中使用,且成对出现使用,否则报java.lang.IllegalMonitorStateException。
          • 必须先await后signal,否则无法唤醒,死锁。
      • 方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

    • Object类中的wait和notify方法实现线程等待和唤醒

    • Condition接口中的await后signal方法实现线程等待和唤醒

    • 传统的synchronized和Lock实现等待唤醒通知的约束

      • 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
      • 必须要先等待后唤醒,线程才能够被唤醒
    • LockSupport类中的park等待和unpark唤醒

      • 是什么
        • 通过park()和unpark(thread)方法来实现阻塞和唤醒线程
        • 官网解释--permit上限是1
          • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
          • LockSupport类使用了一种名为Permit(许可)的概念来阻塞和唤醒线程,每个线程都有一个许可,permit只有两个值,0和1,默认是0;可以把许可看做是一种(0,1)信号量(Semaphore),但与Semaphore不同,许可的累加上限是1
          • park() -- 除非许可证可用,否则禁用当前线程以进行线程调度。
          • unpark(thread) -- 如果给定线程尚不可用,则为其提供许可。
      • 主要方法
        • 阻塞
          • park()/park(Object)
            • 调用park()时 20210318185029 permit默认是0,所以一开始调用park()方法时,当前线程就会被阻塞,直到其他线程将当前线程的permit设置为1时,park()方法会被唤醒,然后将permit再次设置为0并返回。
          • 阻塞当前线程/阻塞传入的具体线程
        • 唤醒
          • unpark(Thread)
            • 调用unpark(Thread)时 20210318185324 调用unpark(thread)后,就会将thread线程的许可permit设置成1(注意,多次调用此方法,不会累加,permit值还是1),会自动唤醒thread线程,即之前阻塞中的LockSupport.park()会立即返回。
          • 唤醒处于阻塞状态的指定线程
      • Demo
      public class LockSupportDemo {
        public static void main(String[] args) {
            Thread a = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t come in");
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() + "\t 被唤醒");
            },"A");
            a.start();
            Thread b = new Thread(() -> {
                LockSupport.unpark(a);
                System.out.println(Thread.currentThread().getName() + "\t 通知");
            },"B");
            b.start();
        }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      • 优点
        • 正常+无锁块要求
        • 无需先等待后唤醒,可以任意位置
      • 重要说明
        • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
        • LockSupport是一个线程阻塞工具类,它的所有方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法,LockSupport调用UNSAFE中的native代码。
        • LockSupport提供park()和unpark(thread)方法实现阻塞线程和唤醒线程的过程。
        • LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0。
        • 调用一次unpark就加1变成1,也只能到1。
        • 调用一次park会消费permit,将1变成0,同时park立即返回。
        • 如果再次调用park会变成阻塞(因为permit为0了,一直到permit为1),这时调用unpark会把permit置为1。
        • 每个线程都有一个相关的permit,permit最多只有1个,重复调用unpark也不会累计凭证。
  • 面试题
    • 1.为什么可以先唤醒后阻塞线程?
      • 因为unpark获得了一个permit(凭证),之后再调用方法,就可以消费线程。
    • 2.为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
      • 因为permit(凭证)的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证,而调用两次park却需要两个凭证,故阻塞。
编辑 (opens new window)
上次更新: 2024/05/24, 11:36:46
多线程-AQS
JUC

← 多线程-AQS JUC→

最近更新
01
杂乱无章
12-25
02
基础-大彬
11-14
03
集合-大彬
11-14
更多文章>
Theme by Vdoing | Copyright © 2023-2025 qouson
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式