Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

07-21 1250阅读

导航:

【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/黑马旅游/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码-CSDN博客

推荐视频:

黑马程序员全套Java教程_哔哩哔哩

尚硅谷Java入门视频教程_哔哩哔哩

推荐书籍:

《Java编程思想 (第4版)》 

《Java核心技术·卷I(原书第12版) : 开发基础》

目录

十一、多线程

11.1 基本介绍

11.1.1  线程和进程的关系

11.1.2 多线程

11.2 创建线程方法

11.2.0 简介

11.2.1 方法1:继承Thread类

11.2.2 方法2:实现 Runnable 接口 

11.2.3 方法3:实现Callable接口

11.2.4 方法4:线程池

11.3 知识加油站

11.3.1 线程生命周期

11.3.2 线程的通信方式

11.3.3 线程池

11.3.3.1 作用

11.3.3.2 生命周期 

11.3.3.3 创建线程池的方式1:线程池工具类

11.3.3.4 创建线程池的方式2:自定义线程池(推荐)

11.3.3.5 如何为线程池设置合适的线程数

11.3.3.6 线程池的原理

11.3.4 练习:多线程交替打印A/B/C,每个打印3次

11.4 线程安全

11.4.1 基本介绍 

11.4.2 原子类

11.4.3 volatile关键字

11.4.4 锁

11.4.5 线程安全的集合

11.5  线程同步

11.5.1 基本介绍

11.5.2 synchronized锁

11.5.2.1 基本介绍

11.5.2.2 同步代码块

11.5.2.3 同步方法

11.5.2.4 知识加油站:synchronized锁的原理

11.5.3 Lock锁

11.5.4 synchronized和Lock的区别

十二、 反射

12.1 基本介绍

12.2 反射获取Class对象

12.2.1 基本介绍

12.2.2 全限定名和规范名

12.3 反射获取成员

12.3.1 反射获取构造方法

12.3.2 反射获取字段

12.3.3 反射获取普通方法


十一、多线程

11.1 基本介绍

11.1.1  线程和进程的关系

进程:是操作系统分配资源的基本单位,有独立的地址空间(内存空间的一部分,用于存储进程中的代码、数据和堆栈等信息)和内存空间,进程之间不能共享资源,上下文切换慢,并发低,能独立执行(有程序入口、执行序列、出口),更健壮(因为进程崩溃后不会影响其他进程)。

线程:是操作系统调度的基本单位,没有独立的地址空间和内存空间(只有自己的堆栈和局部变量,只能共享所在进程的内存空间),线程之间可以共享进程内的资源,上下文切换快,并发高,不能独立执行(应用程序控制多线程执行,进程通过管理线程优先级间接控制线程执行),不健壮(因为一个线程崩溃会导致整个进程崩溃)。

关系:一个程序运行后至少包括一个进程,一个进程至少有一个线程。 

运行时数据区包括本地方法栈、虚拟机栈、方法区、堆、程序计数器。每个线程都有独自的本地方法栈、虚拟机栈、程序计数器。各线程共享进程的方法区和堆。

JVM运行时数据区参考:

什么是JVM的内存模型?详细阐述Java中局部变量、常量、类名等信息在JVM中的存储位置_jvm中主要用于存储类的元数据(类型信息(类的描述信息 类的元数据))、静态变量、常-CSDN博客

11.1.2 多线程

一个程序运行后至少包括一个进程,一个进程至少有一个线程,一个进程下有多个线程并发地处理任务,称为多线程。

多线程的好处:当一个线程进入阻塞或者等待状态时,其他的线程可以获取CPU的执行权,提高了CPU的利用率。

多线程的缺点:

  • 死锁:多个进程或线程相互等待对方释放所持有的资源,从而无法继续执行的情况。若无外力作用,它们都将无法推进下去。死锁用占用CPU、内存等系统资源,导致资源浪费,死锁会导致程序无法正常退出,导致系统性能差。
  • 上下文频繁切换:频繁的上下文切换可能会造成资源的浪费;
  • 串行:如果因为资源的限制,多线程串行执行,可能速度会比单线程更慢。

     线程的优先级:java是抢占式调度模型,每一个 Java 线程都有一个优先级,优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

    默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。

    注意:优先级高的线程只是获取CPU时间片的几率高,但并不能保证先执行。

    11.2 创建线程方法

    11.2.0 简介

    创建线程有4种方式:

    •  继承Thread类:继承Thread类,重写run()方法;然后创建线程对象调用start()方法开启线程。start()方法里包括了run()方法,用于开启线程。注意如果直接调用run()方法的话,将是普通方法调用,无法起到开启线程的效果
    • 实现Runnable接口:实现Runnable接口并重写run()方法,将实现类作为构造参数创建Thread对象。推荐,因为Java是单继承,线程类实现接口的同时,还可以继承其他类实现其他接口。
    • 实现Callable:实现Callable接口,重写带返回值的call()方法;将实现类对象作为构造参数创建FutureTask对象;将FutureTask对象作为构造参数创建Thread对象。所以此方法可以获取线程执行完后的返回值,而前两种方式不能。
    • ExecutorService的submit或execute方法:execute和submit都是ExecutorService接口的方法,用于线程池提交任务。所有线程池都直接或间接实现ExecutorService接口。
      • execute:参数只能是Runnable,没有返回值
      • submit:参数可以是Runnable、Callable,返回值是FutureTask  

      11.2.1 方法1:继承Thread类

      创建并启动线程的步骤: 

      1. 创建一个继承了 Thread类的线程类,重写的run()方法是线程执行体。
      2. 创建这个类的对象。
      3. 调用线程对象的start()方法来启动该线程(之后Java虚拟机会调用该线程run方法)。

      run()和start()区别:

      • run():封装线程执行的代码,直接调用相当于普通方法的调用。
      • start():启动线程,虚拟机调用该线程的run()方法。

        构造方法:

        • Thread(): 创建一个新的线程对象。
        • Thread(String name): 创建一个新的线程对象并将其名称设置为指定的名称。
        • Thread(Runnable target): 创建一个新的线程对象并将其目标设置为指定的 Runnable 对象。主要用于后面通过Runable接口创建线程。
        • Thread(Runnable target, String name): 创建一个新的线程对象,将其目标设置为指定的 Runnable 对象,并将其名称设置为指定的名称。

          常用方法:

          • void start(): 使线程开始执行;Java 虚拟机调用此线程的 run 方法。
          • void run(): 如果此线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,此方法不执行任何操作并返回。
          • void join():等待该线程执行完成。A线程调用B线程的join()方法,A线程将被阻塞,直到B线程执行完。可以用于线程之间的通信。
          • void join(long millis): 等待该线程终止的时间最长为 millis 毫秒。
          • void join(long millis, int nanos): 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
          • void interrupt(): 中断该线程。
          • boolean isInterrupted(): 测试当前线程是否已中断。
          • boolean isAlive(): 测试线程是否处于活动状态。
          • static void sleep(long millis): 使当前正在执行的线程休眠(暂停执行)指定的毫秒数。
          • static void sleep(long millis, int nanos): 使当前正在执行的线程休眠(暂停执行)指定的毫秒数加指定的纳秒数。

            属性方法:

            • void setName(String name): 改变线程名称,使之与参数 name 相同。
            • String getName(): 返回该线程的名称。
            • void setPriority(int newPriority): 更改该线程的优先级。
            • int getPriority(): 返回该线程的优先级。
            • Thread.State getState(): 返回该线程的状态。
            • void setDaemon(boolean on): 将该线程标记为守护线程或用户线程。
            • boolean isDaemon(): 测试该线程是否为守护线程。用户线程是普通的线程,它们通常是应用程序执行任务的主要线程。守护线程为其他线程提供后台支持。当所有用户线程结束时,JVM 会自动退出,无论守护线程是否仍在运行。

              代码示例1:主线程设置名字并查看:

                  public static void main(String[] args) {
                      Thread.currentThread().setName("主线程");
                      System.out.println(Thread.currentThread().getName());
                  }

              Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

              代码示例2:创建并启动线程

              线程类: 

              /**
               * @Author: vince
               * @CreateTime: 2024/07/16
               * @Description: 打印数字线程类
               * @Version: 1.0
               */
              public class PrintNumberThread extends Thread{
                  /**
                   * 打印1-100
                   */
                  @Override
                  public void run(){
                      for(int i=0;i {
                          try {
                              Thread.sleep(3 * 1000);
                              System.out.println("--helloWorld_001--" + Thread.currentThread().getName());
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      });

              11.3 知识加油站

              11.3.1 线程生命周期

              Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

              Java线程在运行的生命周期中,在任意给定的时刻,只能处于下列6种状态之一:

              • NEW :初始状态,线程被创建,但是还没有调用start方法。
              • RUNNABLE:可运行状态,等待调度或运行。线程正在JVM中执行,但是有可能在等待操作系统的调度。
              • BLOCKED :阻塞状态,线程正在等待获取监视器锁。
              • WAITING :等待状态,线程正在等待其他线程的通知或中断。线程等待状态不占用 CPU 资源,被唤醒后进入可运行状态(等待调度或运行)。
              • TIMED_WAITING:超时等待状态,在WAITING的基础上增加了超时时间,即超出时间自动返回。Thread.sleep(1000);让线程超时等待1s。
              • TERMINATED:终止状态,线程已经执行完毕。

                等待状态:Object类的wait()方法让线程进入等待状态,notify()唤醒该对象上的随机一个线程,notifyAll()唤醒该对象上的所有线程。这3个方法必须处于synchronized代码块或方法中,否则会抛出IllegalMonitorStateException异常。因为调用这三个方法之前必须拿要到当前锁对象的监视器(Monitor对象),synchronized基于对象头和Monitor对象。

                也可以通过Condition类的 await/signal/signalAll方法实现线程的等待和唤醒,从而实现线程的通信,令线程之间协作处理任务。这两个方法依赖于Lock对象。

                线程运行过程:线程在创建之后默认为NEW初始状态,在调用start方法之后进入RUNNABLE可运行状态,可运行状态不代表线程正在运行,它有可能正在等待操作系统的调度。WAITING 等待状态的线程需要其他线程的通知才能返回到可运行状态,而TIMED_WAITING超时等待状态相当于在等待状态的基础上增加了超时限制,除了他线程的唤醒,在超时时间到达时也会返回运行状态。此外,线程在执行同步方法时,在没有获取到锁的情况下,会进入到BLOCKED 阻塞状态。线程在执行完run方法之后,会进入到TERMINATED终止状态。

                11.3.2 线程的通信方式

                线程通信:用于多个线程之间协作工作,共同完成某个任务。多个线程在并发执行的时候,他们在CPU中是随机切换执行的,这个时候我们想多个线程一起来完成一件任务,这个时候我们就需要线程之间的通信了,多个线程一起来完成一个任务。

                Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

                线程通信方式: 

                • 通过 volatile 关键字:多个线程同时监听一个volatile变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。利用了volatile可见性,即一旦修改变量则立即刷新到共享内存中。
                • 通过Object类的 wait/notify/notifyAll 方法:当我们使用synchronized同步时就会使用Monitor来实现线程通信,这里的Monitor其实就是锁对象,其利用Object类的wait,notify,notifyAll等方法来实现线程通信。Monitor是Java虚拟机实现锁的一种底层机制,用于控制线程对共享资源的访问。(Object类的wait()方法让线程进入等待状态,notify()唤醒该对象上的随机一个线程,notifyAll()唤醒该对象上的所有线程。这3个方法必须处于synchronized代码块或方法中,否则会抛出IllegalMonitorStateException异常。因为调用这三个方法之前必须拿要到当前锁对象的监视器(Monitor对象),synchronized基于对象头和Monitor对象。)
                • 通过Condition类的 await/signal 方法:而使用Lock进行同步时就是使用Condition对象来实现线程通信,Condition对象通过Lock的lock.newCondition()方法创建,使用其await,sign或signAll方法实现线程通信。 Condition 是一个与锁 Lock 相关联的条件对象,可以让等待线程在某个条件被满足时被唤醒,从而达到线程协作的目的。
                • 通过Semaphore的acquire/release方法: Semaphore是一个计数信号量,用于控制同时访问某个资源的线程数量。线程可以通过acquire()方法获取许可,release()方法释放许可。
                • 通过Thread类的join()方法:join() 方法是等待该线程执行完成。A线程调用B线程的join()方法,A线程将被阻塞,直到B线程执行完。

                  应用场景:

                  • 线程交替打印:在多线程交替打印A/B、或者交替打印1到100时,需要在锁中使用线程通信。如果不使用lock.notify()和lock.wait(),可能导致当前线程释放锁后立刻又拿回锁(因为多线程是CPU随机切换的),从而达不到交替打印的效果
                            //第一个线程,例如打印A
                            new Thread(
                                    () -> {
                                        while (true) {
                                            synchronized (lock) {
                                                // 1.临界值校验:到临界值唤醒其他线程,防止其他线程永远等待;
                                                // 2.打印判断:如果需要打印,则打印、操作原子类。 如果用的当前行值原子类,则加1;如果用的总行数原子类,则减1
                                                // 4.线程通信:唤醒、等待。
                                                // 如果删除下面两行代码,可能导致当前线程释放锁后立刻又拿到锁了,从而达不到交替打印的效果
                                                lock.notifyAll();
                                                try-catch{lock.wait();}
                                            }
                                        }
                                    }
                            ).start();
                            //另一个线程,例如打印B...

                    11.3.3 线程池

                    11.3.3.1 作用

                    为了对多线程进行统一的管理,Java引入了线程池,它通过限制并发线程的数量、将待执行的线程放入队列、销毁空闲线程,来控制资源消耗,使线程更合理地运行,避免系统因为创建过多线程而崩溃。

                    线程池作用: 

                    • 管理线程数量:它可以管理线程的数量,可以避免无节制的销毁、创建线程,导致额外的性格损耗、或者线程数超出系统负荷直至崩溃。
                    • 提高性能:当有新的任务到来时,可以直接从线程池中取出一个空闲线程来执行任务,而不需要等待创建新线程,从而减少了响应时间。
                    • 让线程复用:它还可以让线程复用,可以大大地减少创建和销毁线程所带来的开销。
                    • 合理的拒绝策略:线程池提供了多种拒绝策略,当线程池队列满了时,可以采用不同的策略进行处理,如抛出异常、丢弃任务或调用者运行等。
                      11.3.3.2 生命周期 

                      生命周期:

                      通常线程池的生命周期包含5个状态,对应状态值分别是:-1、0、1、2、3,这些状态只能由小到大迁移,不可逆。

                      1. RUNNING:运行。线程池处于正常状态,可以接受新的任务,同时会按照预设的策略来处理已有任务的执行。
                      2. SHUTDOWN:关闭。线程池处于关闭状态,不再接受新的任务,但是会继续执行已有任务直到执行完成。执行线程池对象的shutdown()时进入该状态。
                      3. STOP:停止。线程池处于关闭状态,不再接受新的任务,同时会中断正在执行的任务,清空线程队列。执行shutdownNow()时进入该状态。
                      4. TIDYING:整理。所有任务已经执行完毕,线程池进入该状态会开始进行一些结尾工作,比如及时清理线程池的一些资源。
                      5. TERMINATED:终止。线程池已经完全停止,所有的状态都已经结束了,线程池处于最终的状态。
                      11.3.3.3 创建线程池的方式1:线程池工具类

                      执行器工具类Executors创建线程池: 底层都是return new ThreadPoolExecutor(...)。一般不使用这种方式,参数配置死了不可控。

                      • newCachedThreadPool:缓存线程池(无限大)。 
                        • 核心线程数是0,最大线程数无限大:最大线程数Integer.MAX_VALUE。线程数量可以无限扩大,所有线程都是非核心线程。
                        • 空闲线程存活时间60s:keepAliveTime为60S,空闲线程超过60s会被杀死。
                        • 同步队列:因为最大线程数无限大,所以也用不到阻塞队列,所以设为没有存储空间的SynchronousQueue同步队列。这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。
                      • newFixedThreadPool:固定大小的线程池。
                        • 核心线程数:所有线程都是核心线程(通过构造参数指定),最大线程数=核心线程数。
                        • 存活时间0s:因为所有线程都是核心线程,所以用不到存活时间,线程都会一直存活。keepAliveTime为0S。
                        • 链表阻塞队列:超出的线程会在LinkedBlockingQueue队列中等待。
                      • newScheduledThreadPool:定时任务线程池。创建一个定长线程池, 支持定时及周期性任务执行。可指定核心线程数,最大线程数。
                      • newSingleThreadExecutor:单线程化的线程池。核心线程数与最大线程数都只有一个,不回收。后台从LinkedBlockingQueue队列中获取任务​。创建一个单线程化的线程池, 它只会用唯一的工作线程来执行任务, 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 
                        ExecutorService executorService = Executors.newFixedThreadPool(10);
                        //源码
                        FixedThredPool: new ThreadExcutor(n, n, 0L, ms, new LinkedBlockingQueue()
                        SingleThreadExecutor: new ThreadExcutor(1, 1, 0L, ms, new LinkedBlockingQueue())
                        CachedTheadPool: new ThreadExcutor(0, max_valuem, 60L, s, new SynchronousQueue());
                        ScheduledThreadPoolExcutor: ScheduledThreadPool, SingleThreadScheduledExecutor.

                        一般要搭配计数器CountDownLatch,await(时间)让主线程等待,直到任务线程都执行完(计数器减为零),或者到达超时时间,防止无线等待。

                        11.3.3.4 创建线程池的方式2:自定义线程池(推荐)

                        线程池执行器ThreadPoolExecutor创建自定义线程池:

                                ThreadPoolExecutor threadPoolExecutor= new ThreadPoolExecutor( 
                            			 5,    //核心线程数
                                         200,    //最大线程数量,控制资源并发
                                         10,    //存活时间
                                        TimeUnit.SECONDS,    //时间单位
                                        new LinkedBlockingDeque(  100000),    //任务队列,大小100000个
                                Executors.defaultThreadFactory(),    //线程的创建工厂
                                new ThreadPoolExecutor.AbortPolicy());    //拒绝策略
                                CompletableFuture future1 = CompletableFuture.supplyAsync(() -> {    //开启异步编排,有返回值
                                    return 1;
                                }, threadPoolExecutor).thenApplyAsync(res -> {    //串行化,接收参数并有返回值
                                    return res+1;
                                }, threadPoolExecutor);
                                Integer integer = future.get();    //获取返回值

                         七个参数:

                        • corePoolSize:核心线程数。创建以后,会一直存活到线程池销毁,空闲时也不销毁。
                        • maximumPoolSize:最大线程数量。阻塞队列满了
                        • keepAliveTime: 存活时间。释放空闲时间超过“存活时间”的线程,仅留核心线程数量的线程。
                        • TimeUnitunit:时间单位
                        • workQueue: 任务队列。如果线程数超过核心数量,就把剩余的任务放到队列里。只要有线程空闲,就会去队列取出新的任务执行。new LinkedBlockingDeque()队列大小默认是Integer的最大值,内存不够,所以建议指定队列大小。
                          • SynchronousQueue是一个同步队列,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。
                          • LinkedBlockingQueue是一个无界队列,可以缓存无限多的任务。由于其无界特性,因此需要合理地处理好任务的生产速率和线程池中线程的数量,以避免内存溢出等异常问题。无限缓存,拒绝策略就能随意了。
                          • ArrayBlockingQueue是一个有界(容量固定)队列,只能缓存固定数量的任务。通过固定队列容量,可以避免任务过多导致线程阻塞,保证线程池资源的可控性和稳定性。推荐,有界队列能增加系统的稳定性和预警能力。可以根据需要设大一点,比如几千,新任务丢弃后未来重新入队。
                          • PriorityBlockingQueue是一个优先级队列,能够对任务按照优先级进行排序,当任务数量超过队列容量时,会根据元素的Comparable或Comparator排序规则进行丢弃或抛异常。
                            new PriorityBlockingQueue((o1, o2) -> o1.length() - o2.length());
                        • threadFactory:线程的创建工厂。可以使用默认的线程工厂Executors.defaultThreadFactory(),也可以自定义线程工厂(实现ThreadFactory接口)
                        • RejectedExecutionHandler handler:拒绝策略。如果任务队列和最大线程数量满了,按照指定的拒绝策略执行任务。
                          • Rejected:丢弃最老的
                          • Caller:调用者同步调用,直接调用run方法,不创建线程了
                          • Abort (默认):直接丢弃新任务
                          • Discard:丢弃新任务,并且抛出异常
                          • 实现拒绝执行处理器接口(RejectedExecutionHandler),自定义拒绝策略。
                          11.3.3.5 如何为线程池设置合适的线程数

                          下面的参数只是一个预估值,适合初步设置,具体的线程数需要经过压测确定,压榨(更好的利用)CPU的性能。

                          CPU核心数为N;

                          核心线程数:

                          • CPU密集型:N+1。数量与CPU核数相近是为了不浪费CPU,并防止频繁的上下文切换,加1是为了有线程被阻塞后还能不浪费CPU的算力。
                          • I/O密集型:2N,或N/(1-阻塞系数)。I/O密集型任务CPU使用率并不是很高,可以让CPU在等待I/O操作的时去处理别的任务,充分利用CPU,所以数量就比CPU核心数高一倍。有些公司会考虑阻塞系数,阻塞系数是任务线程被阻塞的比例,一般是0.8~0.9。
                          • 实际开发中更适合的公式:N*((线程等待时间+线程计算时间)/线程计算时间)

                            最大线程数:设成核心线程数的2-4倍。数量主要由CPU和IO的密集性、处理的数据量等因素决定。

                            需要增加线程的情况:jstack打印线程快照,如果发现线程池中大部分线程都等待获取任务、则说明线程够用。如果大部分线程都处于运行状态,可以继续适当调高线程数量。

                            jstack:打印指定进程此刻的线程快照。定位线程长时间停顿的原因,例如死锁、等待资源、阻塞。如果有死锁会打印线程的互相占用资源情况。线程快照:该进程内每条线程正在执行的方法堆栈的集合。

                            11.3.3.6 线程池的原理

                            任务加入时判断的顺序:核心线程数 、阻塞队列、最大线程数、拒绝策略。

                            线程池执原理: 

                            1. 新加入任务,判断corePoolSize是否到最大值;如果没到最大值就创建核心线程执行新任务,如果到最大值就判断是否有空闲的核心线程;
                            2. 如果有空闲的核心线程,则空闲核心线程执行新任务,如果没空闲的核心线程,则尝试加入FIFO阻塞队列;
                            3. 若加入成功,则等待空闲核心线程将队头任务取出并执行,若加入失败(例如队列满了),则判断maximumPoolSize是否到最大值;
                            4. 如果没到最大值就创建非核心线程执行新任务,如果到了最大值就执行丢弃策略,默认丢弃新任务;
                            5. 线程数大于corePoolSize时,空闲线程将在keepAliveTime后回收,直到线程数等于核心线程数。这些核心线程也不会被回收。

                            实际上线程本身没有核心和非核心的概念,都是靠比较corePoolSize和当前线程数判断一个线程是不是能看作核心线程。

                            可能某个线程之前被看作是核心线程,等它空闲了,线程池又有corePoolSize个线程在执行任务,这个线程到keepAliveTime后还是会被回收。

                            11.3.4 练习:多线程交替打印A/B/C,每个打印3次

                             核心逻辑:创建线程,循环加锁,执行以下逻辑:

                            1. 临界值判断:到达临界值后唤醒其他线程并结束锁;
                            2. 打印判断:如果需要打印,则打印、操作原子类(只有打印后才操作原子类,否则就是不满足条件,需要下一步的唤醒等待后,进入下一轮的循环);
                            3. 线程通信:唤醒、等待。

                            坑点:

                            • 临界值判断不能放到while里:防止最后一个线程无法唤醒其他线程,从而导致死锁(其他线程没人唤醒了)。
                            • 必须用线程通信:防止当前线程释放锁后立刻又拿回锁(因为多线程是CPU随机切换的),从而达不到交替打印的效果

                              具体代码(Object类的wait()和notifyAll()方案、不抽取方法): 

                              /**
                               * @Author: vince
                               * @CreateTime: 2024/05/13
                               * @Description: 多线交替打印A/B/C
                               * @Version: 1.0
                               */
                              public class Test2 {
                                  /**
                                   * 当前行值
                                   */
                                  private static AtomicInteger index = new AtomicInteger(0);
                                  /**
                                   * 总打印行数
                                   */
                                  private static final int count = 9;
                                  public static void main(String[] args) {
                                      Object lock = new Object();
                                      // 下面创建三个线程可以抽取成一个方法,这里方便理解所以拆开
                                      new Thread(() -> {
                                          // tip:这里条件没必要index.get()=count){
                                                      lock.notifyAll();
                                                      break;
                                                  }
                                                  // 2.打印判断:如果需要打印,则打印、操作原子类
                                                  if (index.get() % 3 == 0) {
                                                      System.out.println("A");
                                                      // 只有打印后才操作原子类,否则就是不满足条件,需要下一步的唤醒等待后,进入下一轮的循环
                                                      index.getAndIncrement();
                                                  }
                                                  // 3.线程通信:唤醒、等待
                                                  // 3.1 唤醒其他线程:不管能不能整除,结束后都唤醒其他线程
                                                  // notifyAll()唤醒该对象上的所有线程
                                                  lock.notifyAll();
                                                  // 3.2 当前线程等待:Object类的wait()方法让线程进入等待状态,直到其他线程调用notify()或notifyAll()方法唤醒
                                                  try {
                                                      lock.wait();
                                                  } catch (InterruptedException e) {
                                                      throw new RuntimeException(e);
                                                  }
                                              }
                                          }
                                      }, "线程1打印A").start();
                                      new Thread(() -> {
                                          while (true) {
                                              synchronized (lock) {
                                                  if(index.get()>=count){
                                                      lock.notifyAll();
                                                      break;
                                                  }
                                                  if (index.get() % 3 == 1) {
                                                      System.out.println("B");
                                                      index.getAndIncrement();
                                                  }
                                                  lock.notifyAll();
                                                  try {
                                                      lock.wait();
                                                  } catch (InterruptedException e) {
                                                      throw new RuntimeException(e);
                                                  }
                                              }
                                          }
                                      }, "线程2打印B").start();
                                      new Thread(() -> {
                                          while (true) {
                                              synchronized (lock) {
                                                  if(index.get()>=count){
                                                      lock.notifyAll();
                                                      break;
                                                  }
                                                  if (index.get() % 3 == 2) {
                                                      System.out.println("C");
                                                      index.getAndIncrement();
                                                  }
                                                  lock.notifyAll();
                                                  try {
                                                      lock.wait();
                                                  } catch (InterruptedException e) {
                                                      throw new RuntimeException(e);
                                                  }
                                              }
                                          }
                                      }, "线程3打印C").start();
                                  }
                              }

                               结果:

                              Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

                              具体代码(创建和启动线程抽取方法):

                              import java.util.concurrent.atomic.AtomicInteger;
                              /**
                               * @Author: vince
                               * @CreateTime: 2024/05/13
                               * @Description: 多线交替打印A/B/C
                               * @Version: 1.0
                               */
                              public class Test2 {
                                  /**
                                   * 当前行值
                                   */
                                  private static AtomicInteger index = new AtomicInteger(0);
                                  /**
                                   * 总打印行数
                                   */
                                  private static final int count = 9;
                                  public static void main(String[] args) {
                                      Object lock = new Object();
                                      createAndStartThread("线程1打印A", lock, 0, "A");
                                      createAndStartThread("线程2打印B", lock, 1, "B");
                                      createAndStartThread("线程3打印C", lock, 2, "C");
                                  }
                                  private static void createAndStartThread(String threadName, Object lock, int remainder, String output) {
                                      new Thread(() -> {
                                          while (true) {
                                              synchronized (lock) {
                                                  if (index.get() >= count) {
                                                      lock.notifyAll();
                                                      break;
                                                  }
                                                  if (index.get() % 3 == remainder) {
                                                      System.out.println(output);
                                                      index.getAndIncrement();
                                                  }
                                                  lock.notifyAll();
                                                  try {
                                                      lock.wait();
                                                  } catch (InterruptedException e) {
                                                      throw new RuntimeException(e);
                                                  }
                                              }
                                          }
                                      }, threadName).start();
                                  }
                              }
                              

                              其他线程通信方式:

                              • Object类的wait()和notifyAll()(采用)
                              • Conditon的await,sign或signAll方法:创建三个Conditon对象A/B/C,A.await()就是让A线程等待;
                              • Semaphore的acquire和release方法:使用三个Semaphore对象,分别初始化为1、0、0,表示A、B、C三个线程的初始许可数。每个线程在打印字母之前,需要调用对应的Semaphore对象的acquire方法,获取许可。每个线程在打印字母之后,需要调用下一个Semaphore对象的release方法,释放许可。

                                11.4 线程安全

                                11.4.1 基本介绍 

                                线程安全:程序在多线程环境下可以持续进行正确的处理,不会产生数据竞争(例如死锁)和不一致的问题。解决方案:原子类、volatile、锁、线程安全的集合 

                                线程安全的解决方案:按照资源占用情况由轻到重排列:

                                • 原子类:具有原子操作特征(化学中原子是最小单位、不可分割)的类,只能保证单个共享变量的线程安全
                                • volatile:只能保证单个共享变量的线程安全
                                • 锁:可以保证临界区内的多个共享变量线程安全。

                                  11.4.2 原子类

                                  原子类是具有原子操作特征(化学中原子是最小单位、不可分割)的类,原子是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。

                                  在java.util.concurrent.atomic包下,有一系列“Atomic”开头的类,统称为原子类。例如AtomicInteger替代int ,底层采用CAS原子指令实现,内部的存储值使用volatile修饰,因此多线程之间是修改可见的。

                                  以AtomicInteger为例,某线程调用该对象的incrementAndGet()方式自增时采用CAS尝试修改它的值,若此时没有其他线程操作该值便修改成功否则反复执行CAS操作直到修改成功。

                                  CAS:不断对变量进行原子性比较和交换,从而解决单个变量的线程安全问题。。比较内存中值和预期值,如果相等则交换,如果不相等就代表被其他线程改了则重试。 

                                  AtomicInteger常用方法:   

                                  • 构造方法:
                                    • AtomicInteger (): 创建一个初始值为0的 AtomicInteger。
                                    • AtomicInteger(int initialValue): 创建一个初始值为 initialValue 的 AtomicInteger。
                                    • 获取和设置:
                                    • int get(): 获取当前的值。
                                    • void set(int newValue): 设置为 newValue。
                                    • int getAndSet(int newValue): 获取当前值,并设置为 newValue。 
                                  • 原子更新:
                                    •  boolean compareAndSet(int expect, int update): 如果当前值等于 expect,则更新为 update。
                                    • int getAndIncrement(): 以原子方式将当前值加1,返回的是旧值。
                                    • int incrementAndGet(): 以原子方式将当前值加1,返回的是新值。
                                    • int getAndDecrement(): 以原子方式将当前值减1,返回的是旧值。
                                    • int decrementAndGet(): 以原子方式将当前值减1,返回的是新值。
                                    • int getAndAdd(int delta): 以原子方式将当前值加上 delta,返回的是旧值。
                                    • int addAndGet(int delta): 以原子方式将当前值加上 delta,返回的是新值。
                                  • 其他方法:  
                                    •  int getAndUpdate(IntUnaryOperator updateFunction): 获取当前值,并按更新函数计算新值设置。
                                    • int updateAndGet(IntUnaryOperator updateFunction): 按更新函数计算新值设置,并返回新值。
                                    • int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction): 获取当前值,并按累加函数计算新值设置。
                                    • int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction): 按累加函数计算新值设置,并返回新值。 

                                    验证原子类的线程安全:

                                    /**
                                     * @Author: vince
                                     * @CreateTime: 2024/06/27
                                     * @Description: 测试类
                                     * @Version: 1.0
                                     */
                                    public class Test {
                                        public static int num=0;
                                        public static void main(String[] args) throws InterruptedException {
                                            AtomicInteger atomicInteger = new AtomicInteger(0);
                                            // 创建10个线程,分别对atomicInteger进行操作
                                            for (int i = 0; i  {
                                                    for (int j = 0; j  
                                     

                                    可以看到原子类正常加到100000,而num没有:

                                     Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇) 

                                    11.4.3 volatile关键字

                                    volatile是一个关键字,被volatile声明的变量存在共享内存中,所有线程要读取、修改这个变量,都是从内存中读取、修改,并且修改操作是原子性的,所以它能保证线程安全。

                                    volatile特性:

                                    • 有序性:被volatile声明的变量之前的代码一定会比它先执行,而之后的代码一定会比它慢执行。底层是在生成字节码文件时,在指令序列中插入内存屏障防止指令重排序。
                                    • 可见性:一旦修改变量则立即刷新到共享内存中,当其他线程要读取这个变量的时候,最终会去内存中读取,而不是从自己的工作空间中读取。每个线程自己的工作空间用于存放堆栈(存方法的参数和返回地址)和局部变量。
                                    • 原子性:volatile变量不能保证完全的原子性,只能保证单次的读/写操作具有原子性(在同一时刻只能被一个线程访问和修改),自增减、复合操作(+=,/=等)则不具有原子性。这也是和synchronized的区别。

                                      读写内存语义:

                                      • 写内存语义:当写一个volatile变量时,JMM(Java内存模型)会把该线程本地内存中的共享变量的值刷新到主内存中。
                                      • 读内存语义:当读一个volatile变量时,JMM会把该线程本地内存置为无效,使其从主内存中读取共享变量。

                                        有序性实现机制:

                                        volatile有序性是通过内存屏障来实现的。内存屏障就是在编译器生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

                                        机器指令:JVM包括类加载子系统、运行时数据区、执行引擎。 执行引擎负责将字节码指令转为操作系统能识别的本地机器指令。

                                        指令重排序:处理器为了提高运算速度会对指令重排序,重排序分三种类型:编译器优化重排序、处理器指令级并行重排序、内存系统重排序。 

                                        • 编译器优化的重排序:编译器在不改变单线程程序的语义前提下,可以重新安排语句的执行顺序。
                                        • 指令级并行的重排序:现在处理器采用了指令集并行技术,将多条指令重叠执行。如果不存在依赖性,处理器可以改变语句对应的机器指令的执行顺序。
                                        • 内存系统的重排序:由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

                                          11.4.4 锁

                                          加锁的方式有两种,分别是synchronized关键字和Lock接口(在JUC包下)。

                                          synchronized锁是互斥锁,可以作用于实例方法、静态方法、代码块,能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。synchronized锁基于对象头和Monitor对象,在1.6之后引入轻量级锁、偏向锁等优化。  

                                          lock锁接口可以通过lock、unlock方法锁住一段代码,Lock实现类都是基于AQS实现的。Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。

                                          11.4.5 线程安全的集合

                                          1. Collections工具类:Collections工具类的synchronizedXxx()方法,将ArrayList等集合类包装成线程安全的集合类。
                                          2. 古老api:java.util包下性能差的古老api,如Vector、Hashtable
                                          3. 降低锁粒度的并发容器:JUC包下Concurrent开头的、以降低锁粒度来提高并发性能的容器,如ConcurrentHashMap。
                                          4. 复制技术实现的并发容器:JUC包下以CopyOnWrite开头的、采用写时复制技术实现的并发容器,如CopyOnWriteArrayList。 

                                          11.5  线程同步

                                          11.5.1 基本介绍

                                          多条语句共享数据时,多线程程序会出现数据安全问题。

                                          线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。

                                          Java主要通过加锁的方式实现线程同步,而锁有两类,分别是synchronized关键字和Lock接口(在JUC包下)。

                                          synchronized锁是互斥锁,可以作用于实例方法、静态方法、代码块,能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。synchronized锁基于对象头和Monitor对象,在1.6之后引入轻量级锁、偏向锁等优化。  

                                          lock锁接口可以通过lock、unlock方法锁住一段代码,Lock实现类都是基于AQS实现的。Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。

                                          对比线程安全和线程同步:线程同步是实现线程安全的一种手段

                                          • 线程安全:程序在多线程环境下可以持续进行正确的处理,不会产生数据竞争(例如死锁)和不一致的问题。解决方案:原子类、volatile、锁、线程安全的集合
                                          • 线程同步:确保多个线程正确、有序地访问共享资源。解决方案:锁

                                            11.5.2 synchronized锁

                                            11.5.2.1 基本介绍

                                            synchronized锁: 

                                            synchronized锁是互斥锁,可以作用于实例方法、静态方法、代码块,能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。synchronized锁基于对象头、CAS、Monitor对象,在1.6之后引入轻量级锁、偏向锁等优化。   

                                            作用于三个位置:

                                             1.作用在静态方法上,则锁是当前类的Class对象。

                                             2. 作用在普通方法上,则锁是当前的实例(this)。

                                             3. 作用在代码块上,则需要在关键字后面的小括号里,显式指定锁对象,例如this、Xxx.class。

                                            11.5.2.2 同步代码块

                                            同步代码块作用在代码块上,则需要在关键字后面的小括号里,显式指定锁对象,例如this、Xxx.class。

                                            同步代码块简单来说就是将一段代码用一把锁给锁起来, 只有获得了这把锁的线程才访问, 并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码.

                                                synchronized(同步对象) {
                                                    //多条语句操作共享数据的代码 
                                                }

                                            同步代码块的好处:解决了多线程的数据安全问题

                                            弊端:线程很多时,每个线程都会去判断锁,这是很耗费资源和时间的。

                                            代码示例:共有100张票,三个窗口卖票,通过加锁防止超卖

                                            public class SellTicket implements Runnable {
                                                private int tickets = 100;
                                                private final Object obj = new Object();
                                                @Override
                                                public void run() {
                                                    while (true) {
                                                        synchronized (obj) {
                                                            if (tickets > 0) {
                                                                try {
                                                                    Thread.sleep(100);
                                                                } catch (InterruptedException e) {
                                                                    e.printStackTrace();
                                                                }
                                                                System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets + " 张票");
                                                                tickets--;
                                                            } else {
                                                                break;
                                                            }
                                                        }
                                                    }
                                                }
                                                public static void main(String[] args) {
                                                    SellTicket sellTicket = new SellTicket();
                                                    Thread t1 = new Thread(sellTicket, "窗口1");
                                                    Thread t2 = new Thread(sellTicket, "窗口2");
                                                    Thread t3 = new Thread(sellTicket, "窗口3");
                                                    t1.start();
                                                    t2.start();
                                                    t3.start();
                                                }
                                            }
                                            
                                            11.5.2.3 同步方法

                                             1.作用在静态方法上,则锁是当前类的Class对象。

                                             2. 作用在普通方法上,则锁是当前的实例(this)。

                                            非静态同步方法的锁对象为this。下面代码是相同功能的同步方法和同步代码块:

                                            代码示例:

                                            锁的粒度是当前对象: 

                                                // 方法1:实例方法,使用this对象锁
                                                private void sellTicket1() {
                                                    synchronized (this) {
                                                        if (tickets > 0) {
                                                            try {
                                                                Thread.sleep(100);
                                                            } catch (InterruptedException e) {
                                                                e.printStackTrace();
                                                            }
                                                            System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets + " 张票");
                                                            tickets--;
                                                        }
                                                    }
                                                }
                                                // 方法2:实例方法,使用this对象锁
                                                private void sellTicket2() {
                                                    synchronized (this) {
                                                        if (tickets > 0) {
                                                            try {
                                                                Thread.sleep(100);
                                                            } catch (InterruptedException e) {
                                                                e.printStackTrace();
                                                            }
                                                            System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets + " 张票");
                                                            tickets--;
                                                        }
                                                    }
                                                }
                                            

                                            锁的粒度是真个类:

                                            静态同步方法的锁对象为:类名.class。下面代码是相同功能的同步方法和同步代码块 

                                                // 方法3:静态方法,使用类对象锁
                                                private static synchronized void sellTicket3() {
                                                    if (tickets > 0) {
                                                        try {
                                                            Thread.sleep(100);
                                                        } catch (InterruptedException e) {
                                                            e.printStackTrace();
                                                        }
                                                        System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets + " 张票");
                                                        tickets--;
                                                    }
                                                }
                                                // 方法4:静态方法,使用类对象锁
                                                private static void sellTicket4() {
                                                    synchronized (SellTicket.class) {
                                                        if (tickets > 0) {
                                                            try {
                                                                Thread.sleep(100);
                                                            } catch (InterruptedException e) {
                                                                e.printStackTrace();
                                                            }
                                                            System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets + " 张票");
                                                            tickets--;
                                                        }
                                                    }
                                                }
                                            11.5.2.4 知识加油站:synchronized锁的原理

                                            synchronized锁: 

                                            synchronized锁是互斥锁,可以作用于实例方法、静态方法、代码块,能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。synchronized锁基于对象头、CAS、Monitor对象,在1.6之后引入轻量级锁、偏向锁等优化。   

                                            作用于三个位置:

                                             1.作用在静态方法上,则锁是当前类的Class对象。

                                             2. 作用在普通方法上,则锁是当前的实例(this)。

                                             3. 作用在代码块上,则需要在关键字后面的小括号里,显式指定锁对象,例如this、Xxx.class。

                                            对象头存储锁信息: synchronized的底层是采用对象头的Mark Word来存储锁信息的。Hotspot 虚拟机(JVM默认虚拟机)中每个对象都有一个对象头(Object Header),包含Mark Word(标记字段) 和 Class Pointer(类型指针)。

                                            • Mark Word(标记字段):存储哈希码、GC分代年龄、锁信息、GC标记(标志位,标记可达对象或垃圾对象)等。锁信息包括:
                                              • 锁标志位:64位的二进制数,通过末尾能判断锁状态。01未锁定、01可偏向、00轻量级锁、10重量级锁、11垃圾回收标记
                                              • 偏向锁线程ID、时间戳等;
                                              • 轻量级锁的指针:指向锁记录的指针
                                              • 重量级锁的指针:指向Monitor锁的指针
                                            • 类型指针:指向它的类元数据的指针,用于找到对象所在的类。  

                                              不考虑共享资源是类变量等特殊情况的话,有共享资源的多个线程通常都属于同一个对象。  

                                              Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

                                              Monitor对象:每个 Java 对象都可以关联一个 Monitor 对象,也称为监视器锁或Monitor锁。Monitor锁用于控制线程对共享资源的访问,开发人员不能直接访问Monitor对象。当一个线程获取了Monitor的锁后,其他试图获取该锁的线程就会被阻塞,直到当前线程释放锁为止。

                                              当一个线程执行synchronized方法或代码块并升级成重量级锁时,当前对象会关联一个Monitor对象,线程必须先获得该对象的Monitor锁才能执行。Monitor有Owner、EntryList、WaitSet三个字段,分别表示Monitor的持有者线程(获得锁的线程)、阻塞队列、和等待队列。

                                              线程通信:synchronized通过Monitor对象,利用Object的wait,notify,notifyAll等方法来实现线程通信。

                                              锁升级:JDK6之前synchronized只有无锁和重量级锁两个状态,JDK6引入偏向锁、轻量级锁两个状态,锁可以根据竞争程度从无锁状态慢慢升级到重量级锁。当竞争小的时候,只需以较小的代价加锁,直到竞争加剧,才使用重量级锁,从而减小了加锁带来的开销。

                                              • 锁升级顺序:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。
                                              • 无锁:没有线程访问同步代码块时。没有对资源进行锁定,所有的线程都能访问并不断修改同一个资源,但同时只有一个线程能修改成功,失败线程会不断重试。
                                              • 偏向锁:当有一个线程访问同步代码块时升级为偏向锁。一段同步代码块一直被一个线程所访问,那么该线程id会CAS写入对象头,下次再访问同步代码块时对象头检查到该线程id,就不用加锁解锁了,降低获取锁的代价。
                                              • 轻量级锁(自旋锁):有锁竞争时升级为轻量级锁。其他线程会通过自旋的形式尝试通过CAS将对象头中Mark Word替换为指向线程栈帧里锁记录的指针,从而获得锁;同时线程锁记录里存放Mark Word信息。竞争的线程不会阻塞但会一直自旋,消耗CPU性能,但速度快。
                                              • 重量级锁:锁膨胀(自旋失败10次)时升级为重量级锁。Mark Word中存储的是指向Monitor锁的指针,对象Mark Word信息也会保存在Monitor锁里,当一个线程获取了Monitor锁后,竞争线程会被阻塞,不再自旋,不消耗CPU,速度慢。

                                                Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

                                                11.5.3 Lock锁

                                                Lock提供比同步方法和代码块更广泛的锁定操作。

                                                • lock():获取锁。如果锁不可用,则当前线程将被禁用,直到获取锁为止。
                                                • tryLock():尝试获取锁,如果锁可用,则获取并立即返回 true;如果锁不可用,则立即返回 false,不会等待。
                                                • tryLock(long time, TimeUnit unit):尝试在指定的时间内获取锁。如果锁可用,则获取并立即返回 true;如果在指定时间内锁不可用,则等待直到超时,然后返回 false。
                                                • unlock():释放锁。
                                                • newCondition():返回一个绑定到此 Lock 实例的新 Condition 实例,可以用于线程之间的协调等待。

                                                  代码示例: 

                                                  import java.util.concurrent.locks.Lock;
                                                  import java.util.concurrent.locks.ReentrantLock;
                                                  public class SellTicket implements Runnable {
                                                      private int tickets = 100;
                                                      private Lock lock = new ReentrantLock();
                                                      @Override
                                                      public void run() {
                                                          while (true) {
                                                              try {
                                                                  lock.lock();
                                                                  if (tickets > 0) {
                                                                      try {
                                                                          Thread.sleep(100);
                                                                      } catch (InterruptedException e) {
                                                                          e.printStackTrace();
                                                                      }
                                                                      System.out.println(Thread.currentThread().getName() + " 正在出售第 " + tickets + " 张票");
                                                                      tickets--;
                                                                  } else {
                                                                      break;
                                                                  }
                                                              } finally {
                                                                  lock.unlock();
                                                              }
                                                          }
                                                      }
                                                      public static void main(String[] args) {
                                                          SellTicket sellTicket = new SellTicket();
                                                          Thread t1 = new Thread(sellTicket);
                                                          Thread t2 = new Thread(sellTicket);
                                                          Thread t3 = new Thread(sellTicket);
                                                          t1.start();
                                                          t2.start();
                                                          t3.start();
                                                      }
                                                  }
                                                  

                                                  11.5.4 synchronized和Lock的区别

                                                  Lock和synchronized有以下几点不同:

                                                  • 接口和关键字。Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
                                                  • 死锁问题。synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
                                                  • 让等待锁的线程响应中断。Lock可以可以通过lockInterruptibly()获取锁的方法让等待锁的线程响应中断。而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
                                                  • 得知是否成功获取锁。通过Lock可以通过tryLock()知道有没有成功获取锁,而synchronized却无法办到。
                                                  • 性能对比。两者性能差不多。JDK6之前synchronized没有锁升级,线程竞争非常激烈时Lock的性能要远远优于synchronized;而JDK6引入锁升级后,线程竞争激烈时候两者性能也相差无几。

                                                    lock锁中断线程:若有线程已拿到锁,其他线程使用lock()获取锁时会阻塞,使用lockInterruptibly()获取锁时会直接中断抛出InterruptedException异常

                                                    lock锁编码习惯:加锁代码要放到try外面。如果放在try里面的话,加锁失败抛异常或者加锁前的代码抛异常后,执行finally里的解锁代码,而其实加锁都没成功,最终解锁就也不合适了。

                                                    lock.lock(); // 加锁
                                                    try{
                                                        // do something
                                                    }finally{
                                                        lock.unlock(); // 解锁
                                                    }
                                                    //不推荐
                                                    try{
                                                    int a=3/0;//这里抛异常会直接进入finally
                                                    lock.lock(); // 加锁
                                                        // do something
                                                    }finally{
                                                        lock.unlock(); // 解锁
                                                    }

                                                    分布式锁:SETNX、Redisson。Redisson基于Redis协议,可以实现可重入锁、公平锁、读写锁、信号量、闭锁(计数器),支持看门狗自动续期。

                                                    十二、 反射

                                                    12.1 基本介绍

                                                    反射:在程序运行期间动态地获取类的信息并对类进行操作的机制。

                                                    通过反射机制可以实现:

                                                    • 获取类或对象的Class对象:程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的所有方法和属性(包括私有,私有需要给该字段调用setAccessible(true)方法开启私有权限)。注意类的class对象是运行时生成的,类的class字节码文件是编译时生成的。
                                                    • 创建实例:程序运行时,可以利用反射先创建类的Class对象再创建该类的实例,并访问该实例的成员;Xxx.class.newInstance() ;例如在Spring容器类源码里,Bean实例化就是通过Bean类的Class对象。Bean类的Class对象是从BeanDefinition对象的type成员变量取的。BeanDefinition对象存储一些Bean的类型、名称、作用域等声明信息。
                                                    • 生成动态代理类或对象:程序运行时,可以通过反射机制生成一个类的动态代理类或动态代理对象。例如JDK中Proxy类的newProxyInstance静态方法,可以通过它创建基于接口的动态代理对象。

                                                      类的字节码文件和Class对象的区别:

                                                      • 类的class字节码文件是编译时生成的,类的class对象是运行时生成的。
                                                      • 类的字节码文件是一个存储在电脑硬盘中的文件,例如Test.class;类的Class对象是存放在内存中的数据,可以快速获取其中的信息;
                                                      • 两者都存储类的各种信息;

                                                        获取类Class对象的JVM底层:如果该类没有被加载过,会首先通过JVM实现类的加载过程,即加载、链接(验证、准备、解析)、初始化,加载阶段会生成类的Class对象。

                                                        获取类Class对象的方法:dog.getClass();Dog.class;Class.forName("package1.Dog");

                                                        特点:

                                                        • 访问私有成员:构造方法、成员变量、方法对象取消访问检查可以访问私有成员;public void setAccessible(boolean flag):值为true,取消访问检查
                                                        • 越过泛型检查:反射可以越过泛型检查,例如在ArrayList中添加字符串

                                                          反射的优缺点:

                                                          • 优点:
                                                            • 运行时获取属性:运行期间能够动态的获取类,提高代码的灵活性。
                                                            • 访问私有成员:构造方法、成员变量、方法对象取消访问检查可以访问私有成员;public void setAccessible(boolean flag):值为true,取消访问检查
                                                            • 越过泛型检查:反射可以越过泛型检查,例如在ArrayList中添加字符串
                                                          • 缺点:性能差。性能比直接的Java代码要差很多。  

                                                            应用场景:

                                                            • JDBC加载数据库的驱动:使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序;
                                                            • Bean的生命周期:
                                                              • 实例化xml解析出的类:多数框架都支持注解或XML配置来定义应用程序中的类,从xml配置中解析出来的类是字符串,需要利用反射机制实例化;例如Spring通过和定义bean,然后通过Class.forName("xxx.Xxx")获取类的class对象,然后创建实例。
                                                              • 注解容器类加载Bean、实例化Bean:Bean的生命周期中,注解容器类的构造方法会遍历@ComponentScan("扫描路径")下的.class文件,通过类加载器.load("类名")方式获得类的class对象,存入beanDefinitionMap。然后遍历beanDefinitionMap,通过class对象实例化等。
                                                            • AOP创建动态代理对象:面向切面编程(AOP)的实现方案,是在程序运行时创建目标对象的代理对象,这必须由反射机制来实现。 

                                                              验证反射可以绕过泛型检查:

                                                              基于反射,我们可以给ArrayList对象中,加入字符串

                                                              public class Test {
                                                                  /**
                                                                   * 测试方法,实际场景建议try-catch,而不是throws
                                                                   * @param args
                                                                   * @throws NoSuchMethodException
                                                                   * @throws InvocationTargetException
                                                                   * @throws IllegalAccessException
                                                                   */
                                                                  public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
                                                                      ArrayList integers = new ArrayList();
                                                                      Class[] getInterfaces():返回类实现的所有接口。
                                                            • boolean isInterface():判断是否是接口。
                                                            • boolean isAnnotation():判断是否是注解类型。
                                                            • boolean isEnum():判断是否是枚举类型。
                                                            • Annotation[] getAnnotations():返回此元素上存在的所有注解。
                                                            • Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注解。
                                                            • T getAnnotation(Class annotationClass):返回指定类型的注解,如果该注解存在于此元素上,否则返回 null。例如Spring源码中,ApplicaitonContext构造器判断一个类是不是Bean,是通过这个方法判断类有没有@Comonent等注解,从而判断它是不是Bean。
                                                          • 获取成员:
                                                            • Field[] getFields():返回类的所有公共字段,包括从父类继承的字段。
                                                            • Field[] getDeclaredFields():返回类声明的所有字段,不包括继承的字段。
                                                            • Method[] getMethods():返回类的所有公共方法,包括从父类继承的方法。
                                                            • Method[] getDeclaredMethods():返回类声明的所有方法,不包括继承的方法。
                                                            • Constructor[] getConstructors():返回类的所有公共构造方法。
                                                            • Constructor[] getDeclaredConstructors():返回类声明的所有构造方法。
                                                          • 其他方法:
                                                            • T newInstance():创建此 Class 对象所表示的类的一个新实例(使用默认构造方法)。

                                                            Spring源码:Bean初始化时判断类是否Bean、判断属性是否需要填充都用到了反射

                                                            Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程-CSDN博客

                                                            代码示例:

                                                            准备Dog类

                                                            /**
                                                             * @Author: vince
                                                             * @CreateTime: 2024/07/02
                                                             * @Description: 狗类
                                                             * @Version: 1.0
                                                             */
                                                            public class Dog{
                                                                /**
                                                                 * 体重
                                                                 */
                                                                private int weight;
                                                                /**
                                                                 * 名字
                                                                 */
                                                                public String name;
                                                                public Dog() {
                                                                }
                                                                public int getWeight() {
                                                                    return weight;
                                                                }
                                                                public void setWeight(int weight) {
                                                                    this.weight = weight;
                                                                }
                                                                public String getName() {
                                                                    return name;
                                                                }
                                                                public void setName(String name) {
                                                                    this.name = name;
                                                                }
                                                                public Dog(int weight, String name) {
                                                                    this.weight = weight;
                                                                    this.name = name;
                                                                }
                                                                @Override
                                                                public String toString() {
                                                                    return "Dog{" +
                                                                            "weight=" + weight +
                                                                            ", name='" + name + '\'' +
                                                                            '}';
                                                                }
                                                            }

                                                            获取类的Class对象: 

                                                            public class Test {
                                                                /**
                                                                 * 测试方法,实际场景建议try-catch,而不是throws
                                                                 * @param args
                                                                 */
                                                                public static void main(String[] args) throws ClassNotFoundException {
                                                                    //方法1:类的class属性
                                                                    Class c1=Dog.class;
                                                                    //方法2:对象的getClass方法
                                                                    Dog wangCaiDog = new Dog(23, "旺财");
                                                                    Class c3= Class.forName("package1.Dog");
                                                                    // 方法4:使用类加载器
                                                                    ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
                                                                    Class c5 = systemClassLoader.loadClass("package1.Dog");
                                                                    // 输出:class package1.Dog
                                                                    System.out.println(c1);
                                                                    //三种方式获取到Class对象地址是完全一致的
                                                                    // 输出:true
                                                                    System.out.println(c1==c2&&c1==c3);
                                                                }
                                                            }

                                                            Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

                                                            获取类的信息:

                                                            /**
                                                             * @Author: vince
                                                             * @CreateTime: 2024/06/27
                                                             * @Description: 测试类
                                                             * @Version: 1.0
                                                             */
                                                            public class Test {
                                                                /**
                                                                 * 测试方法,实际场景建议try-catch,而不是throws
                                                                 *
                                                                 * @param args
                                                                 */
                                                                public static void main(String[] args) throws Exceptionn {
                                                                    Class[] cons = dogClass.getDeclaredConstructors();
                                                                    for(Constructor con:cons){
                                                                        System.out.println(con);
                                                                    }
                                                                }

                                                            Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)​​

                                                             获取单个构造器并实例化:

                                                            无参:

                                                            /**
                                                             * @Author: vince
                                                             * @CreateTime: 2024/06/27
                                                             * @Description: 测试类
                                                             * @Version: 1.0
                                                             */
                                                            public class Test {
                                                                /**
                                                                 * 测试方法,实际场景建议try-catch,而不是throws
                                                                 * @param args
                                                                 */
                                                                public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
                                                                    Class dogClass=Dog.class;
                                                                    //获取单个构造方法对象
                                                                    Constructor con=dogClass.getDeclaredConstructor();
                                                                    //构造方法对象实例化,会调用无参构造方法
                                                                    Object dogObject = con.newInstance();
                                                                    // 无参构造器实例化,也可以直接用Class对象的newInstance方法,带参就不行了
                                                                    Dog dog = dogClass.newInstance();
                                                                    //重写了Dog类的to_String,所以输出:Dog{weight=0, name='null'}
                                                                    System.out.println(dogObject);
                                                                    System.out.println(dog);
                                                                }
                                                            }

                                                            Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

                                                            带参:

                                                            /**
                                                             * @Author: vince
                                                             * @CreateTime: 2024/06/27
                                                             * @Description: 测试类
                                                             * @Version: 1.0
                                                             */
                                                            public class Test {
                                                                /**
                                                                 * 测试方法,实际场景建议try-catch,而不是throws
                                                                 * @param args
                                                                 */
                                                                public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
                                                                    Class dogClass=Dog.class;
                                                                    Constructor con=dogClass.getConstructor(int.class,String.class);
                                                                    Object obj = con.newInstance(32,"旺财");
                                                                    System.out.println(obj);
                                                                }
                                                            }

                                                            Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

                                                            12.3.2 反射获取字段

                                                            Class对象获取字段:

                                                            • getField(String name):返回指定名称的公共字段。返回类型是字段类Field。
                                                            • getDeclaredField(String name):返回指定名称的字段(包括私有字段)。
                                                            • getFields():返回所有公共字段。
                                                            • getDeclaredFields():返回所有字段(包括私有字段)。

                                                              字段类Field常用方法:

                                                              获取字段信息:

                                                              • getName():返回字段的名称。
                                                              • getType():返回字段的类型。
                                                              • getModifiers():返回字段的修饰符。
                                                              • getDeclaringClass():返回声明该字段的类的 Class 对象。

                                                                获取和设置字段值:

                                                                • get(Object obj):返回指定对象上此字段的值。
                                                                • getBoolean(Object obj):返回指定对象上此字段的值(如果字段类型是 boolean)。
                                                                • getByte(Object obj):返回指定对象上此字段的值(如果字段类型是 byte)。
                                                                • getChar(Object obj):返回指定对象上此字段的值(如果字段类型是 char)。
                                                                • getDouble(Object obj):返回指定对象上此字段的值(如果字段类型是 double)。
                                                                • getFloat(Object obj):返回指定对象上此字段的值(如果字段类型是 float)。
                                                                • getInt(Object obj):返回指定对象上此字段的值(如果字段类型是 int)。
                                                                • getLong(Object obj):返回指定对象上此字段的值(如果字段类型是 long)。
                                                                • getShort(Object obj):返回指定对象上此字段的值(如果字段类型是 short)。
                                                                • set(Object obj, Object value):设置指定对象上此字段的值。注意私有字段默认不允许赋值,要赋值必须给私有字段setAccessible(true)
                                                                • setBoolean(Object obj, boolean value):设置指定对象上此字段的值(如果字段类型是 boolean)。
                                                                • setByte(Object obj, byte value):设置指定对象上此字段的值(如果字段类型是 byte)。
                                                                • setChar(Object obj, char value):设置指定对象上此字段的值(如果字段类型是 char)。
                                                                • setDouble(Object obj, double value):设置指定对象上此字段的值(如果字段类型是 double)。
                                                                • setFloat(Object obj, float value):设置指定对象上此字段的值(如果字段类型是 float)。
                                                                • setInt(Object obj, int value):设置指定对象上此字段的值(如果字段类型是 int)。
                                                                • setLong(Object obj, long value):设置指定对象上此字段的值(如果字段类型是 long)。
                                                                • setShort(Object obj, short value):设置指定对象上此字段的值(如果字段类型是 short)。

                                                                  其他方法:

                                                                  • isAccessible():返回字段是否可访问。
                                                                  • setAccessible(boolean flag):设置字段的可访问性。通过这个方法可以让私有字段也可以赋值。
                                                                  • toGenericString():返回字段的描述,包括泛型信息。
                                                                  • getAnnotatedType():返回此字段的带注释的类型。
                                                                  • getAnnotations():返回字段的所有注解。
                                                                  • getAnnotation(Class annotationClass):返回字段的指定类型的注解,如果该注解不存在,则返回 null。例如Spring源码中依赖注入这一块,就是基于反射获取类中字段有没有@Resource、@Component等注解,有的话就是要注入Bean.
                                                                  • getDeclaredAnnotations():返回直接存在于此字段上的所有注解。

                                                                    Spring源码:Bean初始化时判断类是否Bean、判断属性是否需要填充都用到了反射

                                                                    Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程-CSDN博客

                                                                    代码示例:

                                                                    准备Dog类

                                                                    /**
                                                                     * @Author: vince
                                                                     * @CreateTime: 2024/07/02
                                                                     * @Description: 狗类
                                                                     * @Version: 1.0
                                                                     */
                                                                    public class Dog{
                                                                        /**
                                                                         * 体重
                                                                         */
                                                                        private int weight;
                                                                        /**
                                                                         * 名字
                                                                         */
                                                                        public String name;
                                                                        public Dog() {
                                                                        }
                                                                        public int getWeight() {
                                                                            return weight;
                                                                        }
                                                                        public void setWeight(int weight) {
                                                                            this.weight = weight;
                                                                        }
                                                                        public String getName() {
                                                                            return name;
                                                                        }
                                                                        public void setName(String name) {
                                                                            this.name = name;
                                                                        }
                                                                        public Dog(int weight, String name) {
                                                                            this.weight = weight;
                                                                            this.name = name;
                                                                        }
                                                                        @Override
                                                                        public String toString() {
                                                                            return "Dog{" +
                                                                                    "weight=" + weight +
                                                                                    ", name='" + name + '\'' +
                                                                                    '}';
                                                                        }
                                                                    }

                                                                    获取成员变量对象并赋值: 

                                                                    /**
                                                                     * @Author: vince
                                                                     * @CreateTime: 2024/06/27
                                                                     * @Description: 测试类
                                                                     * @Version: 1.0
                                                                     */
                                                                    public class Test {
                                                                        /**
                                                                         * 测试方法,实际场景建议try-catch,而不是throws
                                                                         *
                                                                         * @param args
                                                                         */
                                                                        public static void main(String[] args) throws NoSuchFieldException, InstantiationException, IllegalAccessException {
                                                                            // 1.获取Class对象,并实例化
                                                                            Class dogClassByField = weightField.getDeclaringClass();
                                                                            // 通过字段获取到的class对象和源class对象是地址是一样的,事实上一个类的所有Class对象都是一个实例
                                                                            // true
                                                                            System.out.println(dogClassByField==dogClass);
                                                                        }
                                                                    }

                                                                    Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)

                                                                    12.3.3 反射获取普通方法

                                                                    Class对象获取成员方法的方法: 

                                                                    • getMethod(String name, Class... parameterTypes):返回指定名称和参数类型的公共方法。返回值是方法类Method。
                                                                    • getDeclaredMethod(String name, Class... parameterTypes):返回指定名称和参数类型的方法(包括私有方法)。
                                                                    • getMethods():返回所有公共方法(包括从父类继承的方法)。
                                                                    • getDeclaredMethods():返回所有方法(包括私有方法)。

                                                                      Method类的方法:

                                                                      • 获取方法信息:
                                                                        • getName():返回方法的名称。
                                                                        • getReturnType():返回方法的返回类型。
                                                                        • getParameterTypes():返回方法参数类型的数组。
                                                                        • getModifiers():返回方法的修饰符。
                                                                        • getDeclaringClass():返回声明此方法的类的 Class 对象。
                                                                      • 调用方法:
                                                                        • Object invoke(Object obj, Object... args):调用指定对象上此 Method 对象表示的基础方法。
                                                                      • 其他方法:
                                                                        • isAccessible():返回方法是否可访问。
                                                                        • setAccessible(boolean flag):设置方法的可访问性。
                                                                        • getAnnotations():返回此方法的所有注解。例如Spring源码中通过此方法判断一个类中
                                                                        • isAnnotationPresent(Class
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]