<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.0">Jekyll</generator><link href="https://wujunwei99.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://wujunwei99.github.io/" rel="alternate" type="text/html" /><updated>2021-01-26T07:25:40+00:00</updated><id>https://wujunwei99.github.io/feed.xml</id><title type="html">WuJunwei’s Blog</title><subtitle>Every day to be a little better</subtitle><author><name>WuJunwei</name></author><entry><title type="html">运行时数据区与程序计数器</title><link href="https://wujunwei99.github.io/notes/2020/12/09/java-jvm-RuntimeDataArea-PC.html" rel="alternate" type="text/html" title="运行时数据区与程序计数器" /><published>2020-12-09T07:02:25+00:00</published><updated>2020-12-09T07:02:25+00:00</updated><id>https://wujunwei99.github.io/notes/2020/12/09/java-jvm-RuntimeDataArea-PC</id><content type="html" xml:base="https://wujunwei99.github.io/notes/2020/12/09/java-jvm-RuntimeDataArea-PC.html">&lt;p&gt;运行时数据区概述及线程；运行时数据区结构；程序计数器&lt;/p&gt;

&lt;h2 id=&quot;运行时数据区概述及线程&quot;&gt;运行时数据区概述及线程&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/suGAZd.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;运行时数据区结构&quot;&gt;运行时数据区结构&lt;/h2&gt;

&lt;h3 id=&quot;运行时数据区与内存&quot;&gt;运行时数据区与内存&lt;/h3&gt;

&lt;h4 id=&quot;内存&quot;&gt;内存&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;内存是非常重要的系统资源，是硬盘和CPU的中间仓库及桥梁，承载着操作系统和应用程序的实时运行JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略，保证了JVM的高效稳定运行。&lt;/li&gt;
  &lt;li&gt;不同的JVM对于内存的划分方式和管理机制存在着部分差异。结合JVM虚拟机规范，来探讨一下经典的JVM内存布局。&lt;/li&gt;
  &lt;li&gt;我们通过磁盘或者网络IO得到的数据，都需要先加载到内存中，然后CPU从内存中获取数据进行读取，也就是说内存充当了CPU和磁盘之间的桥梁&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;运行时数据区的完整图&quot;&gt;运行时数据区的完整图&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/suGVII.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;线程的内存空间&quot;&gt;线程的内存空间&lt;/h3&gt;

&lt;h4 id=&quot;线程的内存空间-1&quot;&gt;线程的内存空间&lt;/h4&gt;

&lt;p&gt;java虚拟机定义了若干种程序运行期间会使用到的运行时数据区：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;其中有一些会随着虚拟机启动而创建，随着虚拟机退出而销毁。&lt;/li&gt;
  &lt;li&gt;另外一些则是与线程一一对应的，这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;灰色的为单独线程私有的，红色的为多个线程共享的。即：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;线程独有：独立包括程序计数器、栈、本地方法栈&lt;/li&gt;
  &lt;li&gt;线程间共享：堆、堆外内存（永久代或元空间、代码缓存）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/suGPMD.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;关于-runtime-类的说明&quot;&gt;关于 Runtime 类的说明&lt;/h4&gt;

&lt;p&gt;每个JVM只有一个Runtime实例。即为运行时环境，相当于内存结构的中间的那个框框：运行时环境。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/suGise.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;线程&quot;&gt;线程&lt;/h2&gt;

&lt;h3 id=&quot;jvm-线程&quot;&gt;JVM 线程&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行的执行&lt;/li&gt;
  &lt;li&gt;在Hotspot JVM里，每个线程都与操作系统的本地线程直接映射&lt;/li&gt;
  &lt;li&gt;当一个Java线程准备好执行以后，此时一个操作系统的本地线程也同时创建。Java线程执行终止后，本地线程也会回收&lt;/li&gt;
  &lt;li&gt;操作系统负责将线程安排调度到任何一个可用的CPU上。一旦本地线程初始化成功，它就会调用Java线程中的run()方法&lt;/li&gt;
  &lt;li&gt;如果一个线程抛异常，并且该线程是进程中最后一个非守护线程，那么进程将停止&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;jvm-系统线程&quot;&gt;JVM 系统线程&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;如果你使用jconsole或者是任何一个调试工具，都能看到在后台有许多线程在运行。&lt;/li&gt;
  &lt;li&gt;这些后台线程不包括调用public static void main(String [])的main线程以及所有这个main线程自己创建的线程。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这些主要的后台系统线程在Hotspot JVM里主要是以下几个：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;虚拟机线程：这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点，这样堆才不会变化。这种线程的执行类型括”stop-the-world”的垃圾收集，线程栈收集，线程挂起以及偏向锁撤销&lt;/li&gt;
  &lt;li&gt;周期任务线程：这种线程是时间周期事件的体现（比如中断），他们一般用于周期性操作的调度执行&lt;/li&gt;
  &lt;li&gt;GC线程：这种线程对在JVM里不同种类的垃圾收集行为提供了支持&lt;/li&gt;
  &lt;li&gt;编译线程：这种线程在运行时会将字节码编译成到本地代码&lt;/li&gt;
  &lt;li&gt;信号调度线程：这种线程接收信号并发送给JVM，在它内部通过调用适当的方法进行处理&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;程序计数器&quot;&gt;程序计数器&lt;/h2&gt;

&lt;h3 id=&quot;pc-寄存器概述&quot;&gt;PC 寄存器概述&lt;/h3&gt;

&lt;h4 id=&quot;文档网址&quot;&gt;文档网址&lt;/h4&gt;

&lt;p&gt;https://docs.oracle.com/javase/specs/jvms/se8/html/index.html&lt;/p&gt;

&lt;h4 id=&quot;pc-寄存器介绍&quot;&gt;PC 寄存器介绍&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;JVM中的程序计数寄存器（Program Counter Register）中，Register的命名源于CPU的寄存器，寄存器&lt;strong&gt;存储指令相关的现场信息&lt;/strong&gt;。CPU只有把数据装载到寄存器才能够运行。&lt;/li&gt;
  &lt;li&gt;这里，并非是广义上所指的物理寄存器，或许将其翻译为PC计数器（或指令计数器）会更加贴切（也称为程序钩子），并且也不容易引起一些不必要的误会。&lt;strong&gt;JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟&lt;/strong&gt;。&lt;/li&gt;
  &lt;li&gt;它是一块很小的内存空间，几乎可以忽略不记。也是&lt;strong&gt;运行速度最快的存储区域&lt;/strong&gt;。&lt;/li&gt;
  &lt;li&gt;在JVM规范中，&lt;strong&gt;每个线程都有它自己的程序计数器，是线程私有的&lt;/strong&gt;，生命周期与线程的生命周期保持一致。&lt;/li&gt;
  &lt;li&gt;任何时间一个线程都只有一个方法在执行，也就是所谓的当前方法。&lt;strong&gt;程序计数器会存储当前线程正在执行的Java方法的JVM指令地址&lt;/strong&gt;；或者，如果是在执行native方法，则是未指定值（undefned）。&lt;/li&gt;
  &lt;li&gt;它是&lt;strong&gt;程序控制流&lt;/strong&gt;的指示器，分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。&lt;/li&gt;
  &lt;li&gt;它是&lt;strong&gt;唯一一个&lt;/strong&gt;在Java虚拟机规范中没有规定任何OutofMemoryError情况的区域&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/suGFqH.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;pc-寄存器的作用&quot;&gt;PC 寄存器的作用&lt;/h4&gt;

&lt;p&gt;PC寄存器用来存储指向下一条指令的地址，也即将要执行的指令代码。由执行引擎将指令解释为机器码交由cpu进行处理&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/suGSG6.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;代码示例&quot;&gt;代码示例&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class PCRegisterTest {

    public static void main(String[] args) {
        int i = 10;
        int j = 20;
        int k = i + j;

        String s = &quot;abc&quot;;
        System.out.println(i);
        System.out.println(k);

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;反编译：javap -v xxx.class&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Constant pool:
   #1 = Methodref          #6.#26         // java/lang/Object.&quot;&amp;lt;init&amp;gt;&quot;:()V
   #2 = String             #27            // abc
   #3 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Methodref          #30.#31        // java/io/PrintStream.println:(I)V
   #5 = Class              #32            // com/atguigu/java/PCRegisterTest
   #6 = Class              #33            // java/lang/Object
   #7 = Utf8               &amp;lt;init&amp;gt;
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/atguigu/java/PCRegisterTest;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               i
  #19 = Utf8               I
  #20 = Utf8               j
  #21 = Utf8               k
  #22 = Utf8               s
  #23 = Utf8               Ljava/lang/String;
  #24 = Utf8               SourceFile
  #25 = Utf8               PCRegisterTest.java
  #26 = NameAndType        #7:#8          // &quot;&amp;lt;init&amp;gt;&quot;:()V
  #27 = Utf8               abc
  #28 = Class              #34            // java/lang/System
  #29 = NameAndType        #35:#36        // out:Ljava/io/PrintStream;
  #30 = Class              #37            // java/io/PrintStream
  #31 = NameAndType        #38:#39        // println:(I)V
  #32 = Utf8               com/atguigu/java/PCRegisterTest
  #33 = Utf8               java/lang/Object
  #34 = Utf8               java/lang/System
  #35 = Utf8               out
  #36 = Utf8               Ljava/io/PrintStream;
  #37 = Utf8               java/io/PrintStream
  #38 = Utf8               println
  #39 = Utf8               (I)V
{
  public com.atguigu.java.PCRegisterTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object.&quot;&amp;lt;init&amp;gt;&quot;:()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/atguigu/java/PCRegisterTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=5, args_size=1
         0: bipush        10
         2: istore_1
         3: bipush        20
         5: istore_2
         6: iload_1
         7: iload_2
         8: iadd
         9: istore_3
        10: ldc           #2                  // String abc
        12: astore        4
        14: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        17: iload_1
        18: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        21: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        24: iload_3
        25: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        28: return
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 6
        line 14: 10
        line 15: 14
        line 16: 21
        line 18: 28
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      29     0  args   [Ljava/lang/String;
            3      26     1     i   I
            6      23     2     j   I
           10      19     3     k   I
           14      15     4     s   Ljava/lang/String;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;左边的数字代表指令地址（指令偏移），即 PC 寄存器中可能存储的值，然后执行引擎读取 PC 寄存器中的值，并执行该指令&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/suGpRK.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;两个面试题&quot;&gt;两个面试题&lt;/h3&gt;

&lt;h4 id=&quot;使用pc寄存器存储字节码指令地址有什么用呢&quot;&gt;使用PC寄存器存储字节码指令地址有什么用呢？&lt;/h4&gt;

&lt;p&gt;或者问&lt;/p&gt;

&lt;p&gt;为什么使用 PC 寄存器来记录当前线程的执行地址呢？&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;因为线程是一个个的顺序执行流，CPU需要不停的切换各个线程，这时候切换回来以后，就得知道接着从哪开始继续执行&lt;/li&gt;
  &lt;li&gt;JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su8vI1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;pc寄存器为什么被设定为私有的&quot;&gt;PC寄存器为什么被设定为私有的？&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法，CPU会不停地做任务切换，这样必然导致经常中断或恢复，如何保证分毫无差呢？&lt;/li&gt;
  &lt;li&gt;为了能够准确地记录各个线程正在执行的当前字节码指令地址，最好的办法自然是为每一个线程都分配一个PC寄存器，这样一来各个线程之间便可以进行独立计算，从而不会出现相互干扰的情况。&lt;/li&gt;
  &lt;li&gt;由于CPU时间片轮限制，众多线程在并发执行过程中，任何一个确定的时刻，一个处理器或者多核处理器中的一个内核，只会执行某个线程中的一条指令。&lt;/li&gt;
  &lt;li&gt;这样必然导致经常中断或恢复，如何保证分毫无差呢？每个线程在创建后，都会产生自己的程序计数器和栈帧，程序计数器在各个线程之间互不影响。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;cpu-时间片&quot;&gt;CPU 时间片&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;CPU时间片即CPU分配给各个程序的时间，每个线程被分配一个时间段，称作它的时间片。&lt;/li&gt;
  &lt;li&gt;在宏观上：我们可以同时打开多个应用程序，每个程序并行不悖，同时运行。&lt;/li&gt;
  &lt;li&gt;但在微观上：由于只有一个CPU，一次只能处理程序要求的一部分，如何处理公平，一种方法就是引入时间片，每个程序轮流执行。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su8zPx.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;</content><author><name>WuJunwei</name></author><category term="notes" /><category term="java" /><category term="jvm" /><summary type="html">运行时数据区概述及线程；运行时数据区结构；程序计数器</summary></entry><entry><title type="html">类加载子系统</title><link href="https://wujunwei99.github.io/notes/2020/12/08/java-jvm.html" rel="alternate" type="text/html" title="类加载子系统" /><published>2020-12-08T07:02:25+00:00</published><updated>2020-12-08T07:02:25+00:00</updated><id>https://wujunwei99.github.io/notes/2020/12/08/java-jvm</id><content type="html" xml:base="https://wujunwei99.github.io/notes/2020/12/08/java-jvm.html">&lt;p&gt;内存结构概述；类加载子系统；类加载过程；类加载器的分类；双亲委派机制；沙箱安全机制&lt;/p&gt;

&lt;h2 id=&quot;内存结构概述&quot;&gt;内存结构概述&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su9ngO.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su9uvD.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;类加载子系统&quot;&gt;类加载子系统&lt;/h2&gt;

&lt;h3 id=&quot;类加载器子系统作用&quot;&gt;类加载器子系统作用&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;类加载器子系统负责从文件系统或者网络中加载Class文件，class文件在文件开头有特定的文件标识。&lt;/li&gt;
  &lt;li&gt;ClassLoader只负责class文件的加载，至于它是否可以运行，则由Execution Engine决定。&lt;/li&gt;
  &lt;li&gt;加载的类信息存放于一块称为方法区的内存空间。除了类的信息外，方法区中还会存放运行时常量池信息，可能还包括字符串字面量和数字常量（这部分常量信息是Class文件中常量池部分的内存映射）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su9m8K.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;class –&amp;gt; Java.lang.Class&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;class file存在于本地硬盘上，可以理解为设计师画在纸上的模板，而最终这个模板在执行的时候是要加载到JVM当中来根据这个文件实例化出n个一模一样的实例&lt;/li&gt;
  &lt;li&gt;class file加载到JVM中，被称为DNA元数据模板，放在方法区&lt;/li&gt;
  &lt;li&gt;在.class文件–&amp;gt;JVM–&amp;gt;最终成为元数据模板，此过程就要一个运输工具（类装载器Class Loader），扮演一个快递员的角色&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su9V4x.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;类加载过程&quot;&gt;类加载过程&lt;/h2&gt;

&lt;h3 id=&quot;类加载过程概述&quot;&gt;类加载过程概述&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su9Pu4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;执行 main() 方法（静态方法）就需要先加载承载类 HelloLoader&lt;/li&gt;
  &lt;li&gt;加载成功，则进行链接、初始化等操作，完成后调用 HelloLoader 类中的静态方法 main&lt;/li&gt;
  &lt;li&gt;加载失败则抛出异常&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;完整的流程图如下所示：加载 –&amp;gt; 链接（验证 –&amp;gt; 准备 –&amp;gt; 解析） –&amp;gt; 初始化&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su9Fb9.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;加载阶段&quot;&gt;加载阶段&lt;/h3&gt;

&lt;h4 id=&quot;加载流程&quot;&gt;加载流程&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;通过一个类的全限定名获取定义此类的二进制字节流&lt;/li&gt;
  &lt;li&gt;将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构&lt;/li&gt;
  &lt;li&gt;在内存中生成一个代表这个类的java.lang.Class对象，作为方法区这个类的各种数据的访问入口&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;加载class文件的方式&quot;&gt;加载class文件的方式&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;从本地系统中直接加载&lt;/li&gt;
  &lt;li&gt;通过网络获取，典型场景：Web Applet&lt;/li&gt;
  &lt;li&gt;从zip压缩包中读取，成为日后jar、war格式的基础&lt;/li&gt;
  &lt;li&gt;运行时计算生成，使用最多的是：动态代理技术&lt;/li&gt;
  &lt;li&gt;由其他文件生成，典型场景：JSP应用从专有数据库中提取.class文件，比较少见&lt;/li&gt;
  &lt;li&gt;从加密文件中获取，典型的防Class文件被反编译的保护措施&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;链接阶段&quot;&gt;链接阶段&lt;/h3&gt;

&lt;p&gt;链接分为三个子阶段：验证 –&amp;gt; 准备 –&amp;gt; 解析&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su9EU1.th.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;验证verify&quot;&gt;验证(Verify)&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求，保证被加载类的正确性，不会危害虚拟机自身安全&lt;/li&gt;
  &lt;li&gt;主要包括四种验证，文件格式验证，元数据验证，字节码验证，符号引用验证。&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;举例&quot;&gt;举例&lt;/h4&gt;

&lt;p&gt;使用 BinaryViewer 查看字节码文件，其开头均为 CAFE BABE ，如果出现不合法的字节码文件，那么将会验证不通过&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su9AER.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;解析resolve&quot;&gt;解析(Resolve)&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;将常量池内的符号引用转换为直接引用的过程&lt;/li&gt;
  &lt;li&gt;事实上，解析操作往往会伴随着JVM在执行完初始化之后再执行&lt;/li&gt;
  &lt;li&gt;符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄&lt;/li&gt;
  &lt;li&gt;解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT Class info、CONSTANT Fieldref info、CONSTANT Methodref info等&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;符号引用&quot;&gt;符号引用&lt;/h4&gt;

&lt;p&gt;反编译 class 文件后可以查看符号引用&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su9iDJ.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;初始化阶段&quot;&gt;初始化阶段&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;初始化阶段就是执行类构造器方法&lt;clinit&gt;()的过程&lt;/clinit&gt;&lt;/li&gt;
  &lt;li&gt;此方法不需定义，是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。也就是说，当我们代码中包含static变量的时候，就会有clinit方法&lt;/li&gt;
  &lt;li&gt;
    &lt;clinit&gt;()方法中的指令按语句在源文件中出现的顺序执行
&lt;/clinit&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;clinit&gt;()不同于类的构造器。（关联：构造器是虚拟机视角下的&lt;init&gt;()）
&lt;/init&gt;&lt;/clinit&gt;
  &lt;/li&gt;
  &lt;li&gt;若该类具有父类，JVM会保证子类的&lt;clinit&gt;()执行前，父类的&lt;clinit&gt;()已经执行完毕&lt;/clinit&gt;&lt;/clinit&gt;&lt;/li&gt;
  &lt;li&gt;虚拟机必须保证一个类的&lt;clinit&gt;()方法在多线程下被同步加锁&lt;/clinit&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;示例-1无-static-变量&quot;&gt;示例 1：无 static 变量&lt;/h4&gt;

&lt;p&gt;代码&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class ClinitTest {
    private int a = 1;

    public static void main(String[] args) {
        int b = 2;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;并没有生成 clinit 方法&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su99vF.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;示例-2有-static-变量&quot;&gt;示例 2：有 static 变量&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class ClinitTest {
    //任何一个类声明以后，内部至少存在一个类的构造器
    private int a = 1;
    private static int c = 3;
    
    public static void main(String[] args) {
        int b = 2;
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 clinit 方法中初始化静态变量的值为 3&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su9pgU.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;构造器方法中指令按语句在源文件中出现的顺序执行&lt;/p&gt;

&lt;h4 id=&quot;示例-1&quot;&gt;示例 1&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;代码：

public class ClassInitTest {
    private static int num = 1;
    private static int number = 10;      //linking之prepare: number = 0 --&amp;gt; initial: 10 --&amp;gt; 20

    static {
        num = 2;
        number = 20;
        System.out.println(num);
        //System.out.println(number);    //报错：非法的前向引用（可以赋值，但不能调用）
    }

    public static void main(String[] args) {
        System.out.println(ClassInitTest.num);//2
        System.out.println(ClassInitTest.number);//10
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;静态变量 number 的值变化过程如下&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;准备阶段时：0&lt;/li&gt;
  &lt;li&gt;执行静态变量初始化：10&lt;/li&gt;
  &lt;li&gt;执行静态代码块：20&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/su9S3T.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;构造器是虚拟机视角下的&lt;init&gt;()&lt;/init&gt;&lt;/p&gt;

&lt;h4 id=&quot;代码&quot;&gt;代码&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class ClinitTest {
    //任何一个类声明以后，内部至少存在一个类的构造器
    private int a = 1;
    private static int c = 3;

    public static void main(String[] args) {
        int b = 2;
    }

    public ClinitTest(){
        a = 10;
        int d = 20;
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在构造器中：&lt;/p&gt;

&lt;p&gt;先将类变量 a 赋值为 10&lt;/p&gt;

&lt;p&gt;再将局部变量赋值为 20&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/supzCV.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;若该类具有父类，JVM会保证子类的&lt;clinit&gt;()执行前，父类的&lt;clinit&gt;()已经执行完毕&lt;/clinit&gt;&lt;/clinit&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class ClinitTest1 {
    static class Father{
        public static int A = 1;
        static{
            A = 2;
        }
    }

    static class Son extends Father{
        public static int B = A;
    }

    public static void main(String[] args) {
        //加载Father类，其次加载Son类。
        System.out.println(Son.B);//2
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如上代码，加载流程如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;首先，执行 main() 方法需要加载 ClinitTest1 类&lt;/li&gt;
  &lt;li&gt;获取 Son.B 静态变量，需要加载 Son 类&lt;/li&gt;
  &lt;li&gt;Son 类的父类是 Father 类，所以需要先执行 Father 类的加载，再执行 Son 类的加载&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;虚拟机必须保证一个类的&amp;lt;clinit&amp;gt;()方法在多线程下被同步加锁

public class DeadThreadTest {
    public static void main(String[] args) {
        Runnable r = () -&amp;gt; {
            System.out.println(Thread.currentThread().getName() + &quot;开始&quot;);
            DeadThread dead = new DeadThread();
            System.out.println(Thread.currentThread().getName() + &quot;结束&quot;);
        };

        Thread t1 = new Thread(r, &quot;线程1&quot;);
        Thread t2 = new Thread(r, &quot;线程2&quot;);

        t1.start();
        t2.start();
    }
}

class DeadThread {
    static {
        if (true) {
            System.out.println(Thread.currentThread().getName() + &quot;初始化当前类&quot;);
            while (true) {

            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;程序卡死，分析原因：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;两个线程同时去加载 DeadThread 类，而 DeadThread 类中静态代码块中有一处死循环&lt;/li&gt;
  &lt;li&gt;先加载 DeadThread 类的线程抢到了同步锁，然后在类的静态代码块中执行死循环，而另一个线程在等待同步锁的释放&lt;/li&gt;
  &lt;li&gt;所以无论哪个线程先执行 DeadThread 类的加载，另外一个类也不会继续执行&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/sup7jg.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;类加载器的分类&quot;&gt;类加载器的分类&lt;/h2&gt;

&lt;h3 id=&quot;类加载器概述&quot;&gt;类加载器概述&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;JVM支持两种类型的类加载器 。分别为引导类加载器（Bootstrap ClassLoader）和自定义类加载器（User-Defined ClassLoader）&lt;/li&gt;
  &lt;li&gt;从概念上来讲，自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器，但是Java虚拟机规范却没有这么定义，而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器&lt;/li&gt;
  &lt;li&gt;无论类加载器的类型如何划分，在程序中我们最常见的类加载器始终只有3个，如下所示&lt;/li&gt;
  &lt;li&gt;这里的四者之间是包含关系，不是上层和下层，也不是子父类的继承关系。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;我们尝试获取引导类加载器，获取到的值为 null ，这并不代表引导类加载器不存在，因为引导类加载器右 C/C++ 语言，我们获取不到&lt;/p&gt;

&lt;p&gt;两次获取系统类加载器的值都相同：sun.misc.Launcher$AppClassLoader@18b4aac2 ，这说明系统类加载器是全局唯一的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class ClassLoaderTest {
    public static void main(String[] args) {

        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //获取其上层：扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d

        //获取其上层：获取不到引导类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null

        //对于用户自定义类来说：默认使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //String类使用引导类加载器进行加载的。---&amp;gt; Java的核心类库都是使用引导类加载器进行加载的。
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;虚拟机自带的加载器&quot;&gt;虚拟机自带的加载器&lt;/h3&gt;

&lt;h4 id=&quot;启动类加载器引导类加载器bootstrap-classloader&quot;&gt;启动类加载器（引导类加载器，Bootstrap ClassLoader）&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;这个类加载使用C/C++语言实现的，嵌套在JVM内部&lt;/li&gt;
  &lt;li&gt;它用来加载Java的核心库（JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容），用于提供JVM自身需要的类&lt;/li&gt;
  &lt;li&gt;并不继承自java.lang.ClassLoader，没有父加载器&lt;/li&gt;
  &lt;li&gt;加载扩展类和应用程序类加载器，并作为他们的父类加载器（当他俩的爹）&lt;/li&gt;
  &lt;li&gt;出于安全考虑，Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;扩展类加载器extension-classloader&quot;&gt;扩展类加载器（Extension ClassLoader）&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;Java语言编写，由sun.misc.Launcher$ExtClassLoader实现&lt;/li&gt;
  &lt;li&gt;派生于ClassLoader类&lt;/li&gt;
  &lt;li&gt;父类加载器为启动类加载器&lt;/li&gt;
  &lt;li&gt;从java.ext.dirs系统属性所指定的目录中加载类库，或从JDK的安装目录的jre/lib/ext子目录（扩展目录）下加载类库。如果用户创建的JAR放在此目录下，也会自动由扩展类加载器加载&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;应用程序类加载器系统类加载器appclassloader&quot;&gt;应用程序类加载器（系统类加载器，AppClassLoader）&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;Java语言编写，由sun.misc.LaunchersAppClassLoader实现&lt;/li&gt;
  &lt;li&gt;派生于ClassLoader类&lt;/li&gt;
  &lt;li&gt;父类加载器为扩展类加载器&lt;/li&gt;
  &lt;li&gt;它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库&lt;/li&gt;
  &lt;li&gt;该类加载是程序中默认的类加载器，一般来说，Java应用的类都是由它来完成加载&lt;/li&gt;
  &lt;li&gt;通过classLoader.getSystemclassLoader()方法可以获取到该类加载器&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class ClassLoaderTest1 {
    public static void main(String[] args) {

        System.out.println(&quot;**********启动类加载器**************&quot;);
        //获取BootstrapClassLoader能够加载的api的路径
        URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (URL element : urLs) {
            System.out.println(element.toExternalForm());
        }
        //从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
        ClassLoader classLoader = Provider.class.getClassLoader();
        System.out.println(classLoader);//null

        System.out.println(&quot;***********扩展类加载器*************&quot;);
        String extDirs = System.getProperty(&quot;java.ext.dirs&quot;);
        for (String path : extDirs.split(&quot;;&quot;)) {
            System.out.println(path);
        }

        //从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器
        ClassLoader classLoader1 = CurveDB.class.getClassLoader();
        System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;**&lt;/strong&gt;&lt;strong&gt;**启动类加载器&lt;/strong&gt;&lt;strong&gt;**&lt;/strong&gt;&lt;strong&gt;**&lt;/strong&gt;
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/classes
null
&lt;strong&gt;**&lt;/strong&gt;&lt;strong&gt;**&lt;em&gt;扩展类加载器&lt;/em&gt;&lt;/strong&gt;&lt;strong&gt;**&lt;/strong&gt;&lt;em&gt;**&lt;/em&gt;
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
sun.misc.Launcher$ExtClassLoader@7ea987ac&lt;/p&gt;

&lt;h3 id=&quot;用户自定义类加载器&quot;&gt;用户自定义类加载器&lt;/h3&gt;

&lt;h4 id=&quot;为什么需要自定义类加载器&quot;&gt;为什么需要自定义类加载器？&lt;/h4&gt;

&lt;p&gt;在Java的日常应用程序开发中，类的加载几乎是由上述3种类加载器相互配合执行的，在必要时，我们还可以自定义类加载器，来定制类的加载方式。那为什么还需要自定义类加载器？&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;隔离加载类&lt;/li&gt;
  &lt;li&gt;修改类加载的方式&lt;/li&gt;
  &lt;li&gt;扩展加载源&lt;/li&gt;
  &lt;li&gt;防止源码泄漏&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;如何自定义类加载器&quot;&gt;如何自定义类加载器？&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;开发人员可以通过继承抽象类java.lang.ClassLoader类的方式，实现自己的类加载器，以满足一些特殊的需求&lt;/li&gt;
  &lt;li&gt;在JDK1.2之前，在自定义类加载器时，总会去继承ClassLoader类并重写loadClass()方法，从而实现自定义的类加载类，但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法，而是建议把自定义的类加载逻辑写在findclass()方法中&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;在编写自定义类加载器时，如果没有太过于复杂的需求，可以直接继承URLClassLoader类，这样就可以避免自己去编写findclass()方法及其获取字节码流的方式，使自定义类加载器编写更加简洁。&lt;/p&gt;

    &lt;p&gt;public class CustomClassLoader extends ClassLoader {
      @Override
      protected Class&amp;lt;?&amp;gt; findClass(String name) throws ClassNotFoundException {&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      try {
          byte[] result = getClassFromCustomPath(name);
          if (result == null) {
              throw new FileNotFoundException();
          } else {
              return defineClass(name, result, 0, result.length);
          }
      } catch (FileNotFoundException e) {
          e.printStackTrace();
      }

      throw new ClassNotFoundException(name);
  }

  private byte[] getClassFromCustomPath(String name) {
      //从自定义路径中加载指定类:细节略
      //如果指定路径的字节码文件进行了加密，则需要在此方法中进行解密操作。
      return null;
  }

  public static void main(String[] args) {
      CustomClassLoader customClassLoader = new CustomClassLoader();
      try {
          Class&amp;lt;?&amp;gt; clazz = Class.forName(&quot;One&quot;, true, customClassLoader);
          Object obj = clazz.newInstance();
          System.out.println(obj.getClass().getClassLoader());
      } catch (Exception e) {
          e.printStackTrace();
      }
  }   }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;关于-classloader&quot;&gt;关于 ClassLoader&lt;/h3&gt;

&lt;p&gt;ClassLoader类，它是一个抽象类，其后所有的类加载器都继承自ClassLoader（不包括启动类加载器）&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/supjNq.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;获取-classloader-途径&quot;&gt;获取 ClassLoader 途径&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class ClassLoaderTest2 {
    public static void main(String[] args) {
        try {
            
            //1.Class.forName().getClassLoader()
            ClassLoader classLoader = Class.forName(&quot;java.lang.String&quot;).getClassLoader();
            System.out.println(classLoader); // String 类由启动类加载器加载，我们无法获取

            //2.Thread.currentThread().getContextClassLoader()
            ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
            System.out.println(classLoader1);

            //3.ClassLoader.getSystemClassLoader().getParent()
            ClassLoader classLoader2 = ClassLoader.getSystemClassLoader();
            System.out.println(classLoader2);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

// 输出
null
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;双亲委派机制&quot;&gt;双亲委派机制&lt;/h2&gt;

&lt;h3 id=&quot;双亲委派机制原理&quot;&gt;双亲委派机制原理&lt;/h3&gt;

&lt;h4 id=&quot;双亲委派机制的原理&quot;&gt;双亲委派机制的原理&lt;/h4&gt;

&lt;p&gt;Java虚拟机对class文件采用的是&lt;strong&gt;按需加载&lt;/strong&gt;的方式，也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且&lt;strong&gt;加载某个类的class文件时，Java虚拟机采用的是双亲委派模式，即把请求交由父类处理，它是一种任务委派模式&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果一个类加载器收到了类加载请求，它并不会自己先去加载，而是把这个请求委托给父类的加载器去执行；&lt;/li&gt;
  &lt;li&gt;如果父类加载器还存在其父类加载器，则进一步向上委托，依次递归，请求最终将到达顶层的启动类加载器；&lt;/li&gt;
  &lt;li&gt;如果父类加载器可以完成类加载任务，就成功返回，倘若父类加载器无法完成此加载任务，子加载器才会尝试自己去加载，这就是双亲委派模式。&lt;/li&gt;
  &lt;li&gt;父类加载器一层一层往下分配任务，如果子类加载器能加载，则加载此类，如果将加载任务分配至系统类加载器也无法加载此类，则抛出异常&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/supbuQ.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;双亲委派机制代码示例&quot;&gt;双亲委派机制代码示例&lt;/h3&gt;

&lt;h4 id=&quot;举例-1-&quot;&gt;举例 1 ：&lt;/h4&gt;

&lt;p&gt;代码：我们自己建立一个 java.lang.String 类，写上 static 代码块&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package java.lang;

public class String {
    static{
        System.out.println(&quot;我是自定义的String类的静态代码块&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在另外的程序中加载 String 类，看看加载的 String 类是 JDK 自带的 String 类，还是我们自己编写的 String 类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class StringTest {

    public static void main(String[] args) {
        java.lang.String str = new java.lang.String();
        System.out.println(&quot;hello,atguigu.com&quot;);

        StringTest test = new StringTest();
        System.out.println(test.getClass().getClassLoader());
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;程序并没有输出我们静态代码块中的内容，可见仍然加载的是 JDK 自带的 String 类&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/supo38.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;举例-2&quot;&gt;举例 2&lt;/h4&gt;

&lt;p&gt;代码：在我们自己的 String 类中整个 main() 方法&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package java.lang;

public class String {
    static{
        System.out.println(&quot;我是自定义的String类的静态代码块&quot;);
    }
    //错误: 在类 java.lang.String 中找不到 main 方法
    public static void main(String[] args) {
        System.out.println(&quot;hello,String&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/supTgS.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;举例-3-&quot;&gt;举例 3 ：&lt;/h4&gt;

&lt;p&gt;代码：在 java.lang 包下整个 ShkStart 类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package java.lang;

public class ShkStart {
    public static void main(String[] args) {
        System.out.println(&quot;hello!&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;出于保护机制，java.lang 包下不允许我们自定义类&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/supqBj.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;举例-4-&quot;&gt;举例 4 ：&lt;/h4&gt;

&lt;p&gt;当我们加载jdbc.jar 用于实现数据库连接的时候&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;首先我们需要知道的是 jdbc.jar是基于SPI接口进行实现的&lt;/li&gt;
  &lt;li&gt;所以在加载的时候，会进行双亲委派，最终从根加载器中加载 SPI核心类，然后再加载SPI接口类&lt;/li&gt;
  &lt;li&gt;接着在进行反向委托，通过线程上下文类加载器进行实现类 jdbc.jar的加载。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2021/01/08/supv40.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;双亲委派机制优势&quot;&gt;双亲委派机制优势&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;避免类的重复加载&lt;/li&gt;
  &lt;li&gt;保护程序安全，防止核心API被随意篡改
    &lt;ol&gt;
      &lt;li&gt;自定义类：java.lang.String 没有调用&lt;/li&gt;
      &lt;li&gt;自定义类：java.lang.ShkStart（报错：阻止创建 java.lang开头的类）&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;沙箱安全机制&quot;&gt;沙箱安全机制&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;自定义String类时：在加载自定义String类的时候会率先使用引导类加载器加载，而引导类加载器在加载的过程中会先加载jdk自带的文件（rt.jar包中java.lang.String.class），报错信息说没有main方法，就是因为加载的是rt.jar包中的String类。&lt;/li&gt;
  &lt;li&gt;这样可以保证对java核心源代码的保护，这就是沙箱安全机制。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;其他&quot;&gt;其他&lt;/h2&gt;

&lt;h4 id=&quot;如何判断两个class对象是否相同&quot;&gt;如何判断两个class对象是否相同？&lt;/h4&gt;

&lt;p&gt;在JVM中表示两个class对象是否为同一个类存在两个必要条件：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;类的完整类名必须一致，包括包名&lt;/li&gt;
  &lt;li&gt;加载这个类的ClassLoader（指ClassLoader实例对象）必须相同&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;换句话说，在JVM中，即使这两个类对象（class对象）来源同一个Class文件，被同一个虚拟机所加载，但只要加载它们的ClassLoader实例对象不同，那么这两个类对象也是不相等的&lt;/p&gt;

&lt;h4 id=&quot;对类加载器的引用&quot;&gt;对类加载器的引用&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;JVM必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的&lt;/li&gt;
  &lt;li&gt;如果一个类型是由用户类加载器加载的，那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中&lt;/li&gt;
  &lt;li&gt;当解析一个类型到另一个类型的引用的时候，JVM需要保证这两个类型的类加载器是相同的&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;类的主动使用和被动使用&quot;&gt;类的主动使用和被动使用&lt;/h4&gt;

&lt;p&gt;Java程序对类的使用方式分为：主动使用和被动使用。主动使用，又分为七种情况：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;创建类的实例&lt;/li&gt;
  &lt;li&gt;访问某个类或接口的静态变量，或者对该静态变量赋值&lt;/li&gt;
  &lt;li&gt;调用类的静态方法&lt;/li&gt;
  &lt;li&gt;反射（比如：Class.forName(“com.atguigu.Test”)）&lt;/li&gt;
  &lt;li&gt;初始化一个类的子类&lt;/li&gt;
  &lt;li&gt;Java虚拟机启动时被标明为启动类的类&lt;/li&gt;
  &lt;li&gt;JDK7开始提供的动态语言支持：java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF putStatic、REF_invokeStatic句柄对应的类没有初始化，则初始化&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;除了以上七种情况，其他使用Java类的方式都被看作是对类的被动使用，都不会导致类的初始化，即不会执行初始化阶段（不会调用 clinit() 方法和 init() 方法）&lt;/p&gt;</content><author><name>WuJunwei</name></author><category term="notes" /><category term="java" /><category term="jvm" /><summary type="html">内存结构概述；类加载子系统；类加载过程；类加载器的分类；双亲委派机制；沙箱安全机制</summary></entry><entry><title type="html">JUC并发编程</title><link href="https://wujunwei99.github.io/notes/2020/12/05/java-Thread-jUC.html" rel="alternate" type="text/html" title="JUC并发编程" /><published>2020-12-05T07:27:54+00:00</published><updated>2020-12-05T07:27:54+00:00</updated><id>https://wujunwei99.github.io/notes/2020/12/05/java-Thread-jUC</id><content type="html" xml:base="https://wujunwei99.github.io/notes/2020/12/05/java-Thread-jUC.html">&lt;p&gt;回顾线程进程；CopyOnWrite；常用的辅助类；读写锁；塞队列线程池(重点)；四大函数式接口（必需掌握）；Stream流式计算；ForkJoin；异步回调；深入理解CAS；原子引用；各种锁的理解&lt;/p&gt;

&lt;h2 id=&quot;回顾线程进程&quot;&gt;回顾线程进程&lt;/h2&gt;

&lt;h3 id=&quot;什么是juc&quot;&gt;什么是JUC&lt;/h3&gt;

&lt;p&gt;在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中的 Collection 实现等;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfSvPH.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;线程有几个状态&quot;&gt;线程有几个状态&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public enum State { 

// 新生 NEW,
// 运行 RUNNABLE, 
// 阻塞 BLOCKED, 
// 等待，死死地等 WAITING, 
// 超时等待 TIMED_WAITING, 
// 终止 TERMINATED; 

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;waitsleep-区别&quot;&gt;wait/sleep 区别&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;来自不同的类
    &lt;ul&gt;
      &lt;li&gt;wait =&amp;gt; Object&lt;/li&gt;
      &lt;li&gt;sleep =&amp;gt; Thread&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;关于锁的释放
    &lt;ul&gt;
      &lt;li&gt;wait 会释放锁，sleep 睡觉了，抱着锁睡觉，不会释放！&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;使用的范围是不同的
    &lt;ul&gt;
      &lt;li&gt;wait必须在同步代码块中使用&lt;/li&gt;
      &lt;li&gt;sleep没有限制&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;lock锁重点&quot;&gt;Lock锁（重点）&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WQzt.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;公平锁：十分公平：可以先来后到&lt;/p&gt;

&lt;p&gt;非公平锁：十分不公平：可以插队 （默认）&lt;/p&gt;

&lt;p&gt;其中tryAcquire是公平锁和非公平锁实现的区别，下面的两种类型的锁的tryAcquire的实现，从中我们可以看出在公平锁中，每一次的tryAcquire都会检查CLH队列中是否仍有前驱的元素，如果仍然有那么继续等待，通过这种方式来保证先来先服务的原则；而非公平锁，首先是检查并设置锁的状态，这种方式会出现即使队列中有等待的线程，但是新的线程仍然会与排队线程中的对头线程竞争（但是排队的线程是先来先服务的），所以新的线程可能会抢占已经在排队的线程的锁，这样就无法保证先来先服务，但是已经等待的线程们是仍然保证先来先服务的，&lt;/p&gt;

&lt;h3 id=&quot;synchronized-和-lock-区别&quot;&gt;Synchronized 和 Lock 区别&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Synchronized 内置的Java关键字，Lock 是一个Java类&lt;/li&gt;
  &lt;li&gt;Synchronized 无法判断获取锁的状态，Lock 可以判断是否获取到了锁&lt;/li&gt;
  &lt;li&gt;Synchronized 会自动释放锁，lock 必须要手动释放锁！如果不释放锁，死锁&lt;/li&gt;
  &lt;li&gt;Synchronized 线程 1（获得锁，阻塞）、线程2（等待，傻傻的等）；Lock锁就不一定会等待下去；&lt;/li&gt;
  &lt;li&gt;Synchronized 可重入锁，不可以中断的，非公平；Lock ，可重入锁，可以 判断锁，非公平（可以自己设置）；&lt;/li&gt;
  &lt;li&gt;Synchronized 适合锁少量的代码同步问题，Lock 适合锁大量的同步代码！&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;生产者和消费者问题&quot;&gt;生产者和消费者问题&lt;/h3&gt;

&lt;p&gt;面试的：单例模式、排序算法、生产者和消费者、死锁&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 线程之间的通信问题：生产者和消费者问题！  等待唤醒，通知唤醒
 * 线程交替执行  A   B 操作同一个变量   num = 0
 * A num+1
 * B num-1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(()-&amp;gt;{
            for (int i = 0; i &amp;lt; 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },&quot;A&quot;).start();

        new Thread(()-&amp;gt;{
            for (int i = 0; i &amp;lt; 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },&quot;B&quot;).start();

        new Thread(()-&amp;gt;{
            for (int i = 0; i &amp;lt; 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },&quot;C&quot;).start();


        new Thread(()-&amp;gt;{
            for (int i = 0; i &amp;lt; 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },&quot;D&quot;).start();
    }
}

// 判断等待，业务，通知
class Data{ // 数字 资源类

    private int number = 0;

    //+1
    public synchronized void increment() throws InterruptedException {
        while (number!=0){  //0
            // 等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+&quot;=&amp;gt;&quot;+number);
        // 通知其他线程，我+1完毕了
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        while (number==0){ // 1
            // 等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+&quot;=&amp;gt;&quot;+number);
        // 通知其他线程，我-1完毕了
        this.notifyAll();
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WKJA.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;if 改为 while 判断&lt;/p&gt;

&lt;h4 id=&quot;juc版的生产者和消费者问题&quot;&gt;JUC版的生产者和消费者问题&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2Wuid.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 判断等待，业务，通知
class Data2{ // 数字 资源类

    private int number = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    //condition.await(); // 等待
    //condition.signalAll(); // 唤醒全部
    //+1
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            // 业务代码
            while (number!=0){  //0
                // 等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+&quot;=&amp;gt;&quot;+number);
            // 通知其他线程，我+1完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Condition 精准的通知和唤醒线程&lt;/p&gt;

&lt;h3 id=&quot;8锁现象&quot;&gt;8锁现象&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 8锁，就是关于锁的8个问题
 * 1、标准情况下，两个线程先打印 发短信还是 打电话？ 1/发短信  2/打电话	//发短信
 * 1、sendSms延迟4秒，两个线程先打印 发短信还是 打电话？ 1/发短信  2/打电话	//发短信
 */
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        //锁的存在
        new Thread(()-&amp;gt;{
            phone.sendSms();
        },&quot;A&quot;).start();

        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()-&amp;gt;{
            phone.call();
        },&quot;B&quot;).start();
    }
}

class Phone{

    // synchronized 锁的对象是方法的调用者！、
    // 两个方法用的是同一个锁，谁先拿到谁执行！
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(&quot;发短信&quot;);
    }

    public synchronized void call(){
        System.out.println(&quot;打电话&quot;);
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 3、 增加了一个普通方法后！先执行发短信还是Hello？ 普通方法
 * 4、 两个对象，两个同步方法， 发短信还是 打电话？ // 打电话
 */
public class Test2  {
    public static void main(String[] args) {
        // 两个对象，两个调用者，两把锁！
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();

        //锁的存在
        new Thread(()-&amp;gt;{
            phone1.sendSms();
        },&quot;A&quot;).start();

        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()-&amp;gt;{
            phone2.call();
        },&quot;B&quot;).start();
    }
}

class Phone2{

    // synchronized 锁的对象是方法的调用者！
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(&quot;发短信&quot;);
    }

    public synchronized void call(){
        System.out.println(&quot;打电话&quot;);
    }

    // 这里没有锁！不是同步方法，不受锁的影响
    public void hello(){
        System.out.println(&quot;hello&quot;);
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 5、增加两个静态的同步方法，只有一个对象，先打印 发短信？打电话？发短信
 * 6、两个对象！增加两个静态的同步方法， 先打印 发短信？打电话？发短信
 */
public class Test3  {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个，static，锁的是Class
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();

        //锁的存在
        new Thread(()-&amp;gt;{
            phone1.sendSms();
        },&quot;A&quot;).start();

        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()-&amp;gt;{
            phone2.call();
        },&quot;B&quot;).start();
    }
}

// Phone3唯一的一个 Class 对象
class Phone3{

    // synchronized 锁的对象是方法的调用者！
    // static 静态方法
    // 类一加载就有了！锁的是Class
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(&quot;发短信&quot;);
    }

    public static synchronized void call(){
        System.out.println(&quot;打电话&quot;);
    }


}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 1、1个静态的同步方法，1个普通的同步方法 ，一个对象，先打印 发短信？打电话？打电话
 * 2、1个静态的同步方法，1个普通的同步方法 ，两个对象，先打印 发短信？打电话？打电话
 */
public class Test4  {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个，static，锁的是Class
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        //锁的存在
        new Thread(()-&amp;gt;{
            phone1.sendSms();
        },&quot;A&quot;).start();

        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()-&amp;gt;{
            phone2.call();
        },&quot;B&quot;).start();
    }
}

// Phone3唯一的一个 Class 对象
class Phone4{

    // 静态的同步方法 锁的是 Class 类模板
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(&quot;发短信&quot;);
    }

    // 普通的同步方法  锁的调用者
    public synchronized void call(){
        System.out.println(&quot;打电话&quot;);
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;copyonwrite&quot;&gt;CopyOnWrite&lt;/h2&gt;

&lt;h4 id=&quot;集合类不安全&quot;&gt;集合类不安全&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// java.util.ConcurrentModificationException 并发修改异常！
public class ListTest {
    public static void main(String[] args) {
        // 并发下 ArrayList 不安全的吗，Synchronized；
        /**
         * 解决方案；
         * 1、List&amp;lt;String&amp;gt; list = new Vector&amp;lt;&amp;gt;();
         * 2、List&amp;lt;String&amp;gt; list = Collections.synchronizedList(new ArrayList&amp;lt;&amp;gt;());
         * 3、List&amp;lt;String&amp;gt; list = new CopyOnWriteArrayList&amp;lt;&amp;gt;()；
         */
        // CopyOnWrite 写入时复制  COW  计算机程序设计领域的一种优化策略；
        // 多个线程调用的时候，list，读取的时候，固定的，写入（覆盖）
        // 在写入的时候避免覆盖，造成数据问题！
        // 读写分离
        // CopyOnWriteArrayList  比 Vector Nb 在哪里？

        List&amp;lt;String&amp;gt; list = new CopyOnWriteArrayList&amp;lt;&amp;gt;();

        for (int i = 1; i &amp;lt;= 10; i++) {
            new Thread(()-&amp;gt;{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WMRI.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id=&quot;set不安全&quot;&gt;Set不安全&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * 同理可证 ： ConcurrentModificationException
 * //1、Set&amp;lt;String&amp;gt; set = Collections.synchronizedSet(new HashSet&amp;lt;&amp;gt;());
 * //2、
 */
public class SetTest {
    public static void main(String[] args) {
        Set&amp;lt;String&amp;gt; set = new HashSet&amp;lt;&amp;gt;();
        // hashmap
        // Set&amp;lt;String&amp;gt; set = Collections.synchronizedSet(new HashSet&amp;lt;&amp;gt;());
        // Set&amp;lt;String&amp;gt; set = new CopyOnWriteArraySet&amp;lt;&amp;gt;();

        for (int i = 1; i &amp;lt;=30 ; i++) {
           new Thread(()-&amp;gt;{
               set.add(UUID.randomUUID().toString().substring(0,5));
               System.out.println(set);
           },String.valueOf(i)).start();
        }

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;map-不安全&quot;&gt;Map 不安全&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

// ConcurrentModificationException
public class MapTest {

    public static void main(String[] args) {
        // map 是这样用的吗？ 不是，工作中不用 HashMap
        // 默认等价于什么？  new HashMap&amp;lt;&amp;gt;(16,0.75);
        // Map&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        // 唯一的一个家庭作业：研究ConcurrentHashMap的原理
        Map&amp;lt;String, String&amp;gt; map = new ConcurrentHashMap&amp;lt;&amp;gt;();

        for (int i = 1; i &amp;lt;=30; i++) {
            new Thread(()-&amp;gt;{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;常用的辅助类必会&quot;&gt;常用的辅助类(必会)&lt;/h2&gt;

&lt;h3 id=&quot;countdownlatch&quot;&gt;CountDownLatch&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2Wede.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.concurrent.CountDownLatch;

// 计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 总数是6，必须要执行任务的时候，再使用！
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i &amp;lt;=6 ; i++) {
            new Thread(()-&amp;gt;{
                System.out.println(Thread.currentThread().getName()+&quot; Go out&quot;);
                countDownLatch.countDown(); // 数量-1
            },String.valueOf(i)).start();
        }

        countDownLatch.await(); // 等待计数器归零，然后再向下执行

        System.out.println(&quot;Close Door&quot;);

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;countDownLatch.countDown(); // 数量-1&lt;/p&gt;

&lt;p&gt;countDownLatch.await(); // 等待计数器归零，然后再向下执行&lt;/p&gt;

&lt;p&gt;每次有线程调用 countDown() 数量-1，假设计数器变为0，countDownLatch.await() 就会被唤醒，继续执行！&lt;/p&gt;

&lt;h3 id=&quot;cyclicbarrier&quot;&gt;CyclicBarrier&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WmIH.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /**
         * 集齐7颗龙珠召唤神龙
         */
        // 召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(8,()-&amp;gt;{
            System.out.println(&quot;召唤神龙成功！&quot;);
        });

        for (int i = 1; i &amp;lt;=7 ; i++) {
            final int temp = i;
            // lambda能操作到 i 吗
            new Thread(()-&amp;gt;{
                System.out.println(Thread.currentThread().getName()+&quot;收集&quot;+temp+&quot;个龙珠&quot;);
                try {
                    cyclicBarrier.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }



    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;semaphore&quot;&gt;Semaphore&lt;/h3&gt;

&lt;p&gt;Semaphore：信号量&lt;/p&gt;

&lt;p&gt;6车—3个停车位置&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 线程数量：停车位! 限流！
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i &amp;lt;=6 ; i++) {
            new Thread(()-&amp;gt;{
                // acquire() 得到
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+&quot;抢到车位&quot;);
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+&quot;离开车位&quot;);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // release() 释放
                }

            },String.valueOf(i)).start();
        }

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;semaphore.acquire() 获得，假设如果已经满了，等待，等待被释放为止！&lt;/p&gt;

&lt;p&gt;semaphore.release(); 释放，会将当前的信号量释放 + 1，然后唤醒等待的线程！&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;作用：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;多个共享资源互斥的使用！并发限流，控制最大的线程数！&lt;/p&gt;

&lt;h2 id=&quot;读写锁&quot;&gt;读写锁&lt;/h2&gt;

&lt;p&gt;ReadWriteLock&lt;/p&gt;

&lt;p&gt;读的时候可以被多线程读，写的时候只能有一个线程去写。&lt;/p&gt;

&lt;p&gt;A ReadWriteLock维护一对关联的locks ，一个用于只读操作，一个用于写入。 read lock可以由多个阅读器线程同时进行，只要没有作者。 write lock是独家的。&lt;/p&gt;

&lt;p&gt;所有ReadWriteLock实现必须保证的存储器同步效应writeLock操作（如在指定Lock接口）也保持相对于所述相关联的readLock 。 也就是说，一个线程成功获取读锁定将会看到在之前发布的写锁定所做的所有更新。&lt;/p&gt;

&lt;p&gt;读写锁允许访问共享数据时的并发性高于互斥锁所允许的并发性。 它利用了这样一个事实：一次只有一个线程（ 写入线程）可以修改共享数据，在许多情况下，任何数量的线程都可以同时读取数据（因此读取器线程）。 从理论上讲，通过使用读写锁允许的并发性增加将导致性能改进超过使用互斥锁。 实际上，并发性的增加只能在多处理器上完全实现，然后只有在共享数据的访问模式是合适的时才可以。&lt;/p&gt;

&lt;p&gt;读写锁是否会提高使用互斥锁的性能取决于数据被读取的频率与被修改的频率相比，读取和写入操作的持续时间以及数据的争用 - 即是，将尝试同时读取或写入数据的线程数。 例如，最初填充数据的集合，然后经常被修改的频繁搜索（例如某种目录）是使用读写锁的理想候选。 然而，如果更新变得频繁，那么数据的大部分时间将被专门锁定，并且并发性增加很少。 此外，如果读取操作太短，则读写锁定实现（其本身比互斥锁更复杂）的开销可以支配执行成本，特别是因为许多读写锁定实现仍将序列化所有线程通过小部分代码。 最终，只有剖析和测量将确定使用读写锁是否适合您的应用程序。&lt;/p&gt;

&lt;h3 id=&quot;使用&quot;&gt;使用&lt;/h3&gt;

&lt;p&gt;虽然读写锁的基本操作是直接的，但是执行必须做出许多策略决策，这可能会影响给定应用程序中读写锁定的有效性。 这些政策的例子包括：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在写入器释放写入锁定时，确定在读取器和写入器都在等待时是否授予读取锁定或写入锁定。 作家偏好是常见的，因为写作预计会很短，很少见。 读者喜好不常见，因为如果读者经常和长期的预期，写作可能导致漫长的延迟。 公平的或“按顺序”的实现也是可能的。&lt;/li&gt;
  &lt;li&gt;确定在读卡器处于活动状态并且写入器正在等待时请求读取锁定的读取器是否被授予读取锁定。 读者的偏好可以无限期地拖延作者，而对作者的偏好可以减少并发的潜力。&lt;/li&gt;
  &lt;li&gt;确定锁是否可重入：一个具有写锁的线程是否可以重新获取？ 持有写锁可以获取读锁吗？ 读锁本身是否可重入？&lt;/li&gt;
  &lt;li&gt;写入锁可以降级到读锁，而不允许插入写者？ 读锁可以升级到写锁，优先于其他等待读者或作者吗？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在评估应用程序的给定实现的适用性时，应考虑所有这些问题。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 独占锁（写锁） 一次只能被一个线程占有
 * 共享锁（读锁） 多个线程可以同时占有
 * ReadWriteLock
 * 读-读  可以共存！
 * 读-写  不能共存！
 * 写-写  不能共存！
 */
	
/**
 * 自定义缓存
 */
class MyCache{


    private volatile Map&amp;lt;String,Object&amp;gt; map = new HashMap&amp;lt;&amp;gt;();

    // 读写锁： 更加细粒度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();


    // 存，写入的时候，只希望同时只有一个线程写
    public void put(String key,Object value){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+&quot;写入&quot;+key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+&quot;写入OK&quot;);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    // 取，读，所有人都可以读！
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+&quot;读取&quot;+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+&quot;读取OK&quot;);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}



class Thread1 extends Thread{
	MyCache myCache = new MyCache();
	
	public Thread1(MyCache myCache){
		super();
		this.myCache = myCache;
	}
	
	public void run(){
  	myCache.put(Thread.currentThread().getName(),Thread.currentThread().getName());
  }
		
}

class Thread2 extends Thread{
	MyCache myCache = new MyCache();

	public Thread2(MyCache myCache){
		super();
		this.myCache = myCache;
	}
	
	
	public void run(){
		synchronized(this){
			myCache.get(Thread.currentThread().getName());
		} 
	}
}

public class ReadWriteLockDemo {
    public static void main(String[] args) {
    	MyCache myCache = new MyCache();
        // 写入
        for (int i = 1; i &amp;lt;= 5 ; i++) {
            Thread1 t = new Thread1(myCache);
            t.setName(String.valueOf(i));
            t.start();
        }
//
        // 读取
        for (int i = 1; i &amp;lt;= 5 ; i++) {
            Thread2 t = new Thread2(myCache);
            t.setName(String.valueOf(i));
            t.start();
        }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;阻塞队列&quot;&gt;阻塞队列&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WZZD.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WEqO.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WkM6.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WAsK.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;使用情况&quot;&gt;使用情况&lt;/h3&gt;

&lt;p&gt;多线程并发处理，线程池！&lt;/p&gt;

&lt;h3 id=&quot;四组api&quot;&gt;四组API&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2Wixx.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 抛出异常
 */
public static void test1(){
        // 队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue&amp;lt;&amp;gt;(3);

        System.out.println(blockingQueue.add(&quot;a&quot;));
        System.out.println(blockingQueue.add(&quot;b&quot;));
        System.out.println(blockingQueue.add(&quot;c&quot;));
        // IllegalStateException: Queue full 抛出异常！
        // System.out.println(blockingQueue.add(&quot;d&quot;));

        System.out.println(&quot;=-===========&quot;);

        System.out.println(blockingQueue.element()); // 查看队首元素是谁
        System.out.println(blockingQueue.remove());


        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());

        // java.util.NoSuchElementException 抛出异常！
        // System.out.println(blockingQueue.remove());
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 有返回值，没有异常
 */
public static void test2(){
    // 队列的大小
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue&amp;lt;&amp;gt;(3);

    System.out.println(blockingQueue.offer(&quot;a&quot;));
    System.out.println(blockingQueue.offer(&quot;b&quot;));
    System.out.println(blockingQueue.offer(&quot;c&quot;));

    System.out.println(blockingQueue.peek());
    // System.out.println(blockingQueue.offer(&quot;d&quot;)); // false 不抛出异常！
    System.out.println(&quot;============================&quot;);
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll()); // null  不抛出异常！
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2W9i9.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 等待，阻塞（一直阻塞）
 */
public static void test3() throws InterruptedException {
    // 队列的大小
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue&amp;lt;&amp;gt;(3);

    // 一直阻塞
    blockingQueue.put(&quot;a&quot;);
    blockingQueue.put(&quot;b&quot;);
    blockingQueue.put(&quot;c&quot;);
    // blockingQueue.put(&quot;d&quot;); // 队列没有位置了，一直阻塞
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take()); // 没有这个元素，一直阻塞

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WCGR.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 等待，阻塞（等待超时）
 */
public static void test4() throws InterruptedException {
    // 队列的大小
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue&amp;lt;&amp;gt;(3);

    blockingQueue.offer(&quot;a&quot;);
    blockingQueue.offer(&quot;b&quot;);
    blockingQueue.offer(&quot;c&quot;);
    // blockingQueue.offer(&quot;d&quot;,2,TimeUnit.SECONDS); // 等待超过2秒就退出
    System.out.println(&quot;===============&quot;);
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    blockingQueue.poll(2,TimeUnit.SECONDS); // 等待超过2秒就退出

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WSIJ.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;synchronousqueue-同步队列&quot;&gt;SynchronousQueue 同步队列&lt;/h3&gt;

&lt;p&gt;没有容量，进去一个元素，必须等待取出来之后，才能再往里面放一个元素！&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.sql.Time;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
 * 同步队列
 * 和其他的BlockingQueue 不一样， SynchronousQueue 不存储元素
 * put了一个元素，必须从里面先take取出来，否则不能在put进去值！
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue&amp;lt;String&amp;gt; blockingQueue = new SynchronousQueue&amp;lt;&amp;gt;(); // 同步队列

        new Thread(()-&amp;gt;{
            try {
                System.out.println(Thread.currentThread().getName()+&quot; put 1&quot;);
                blockingQueue.put(&quot;1&quot;);
                System.out.println(Thread.currentThread().getName()+&quot; put 2&quot;);
                blockingQueue.put(&quot;2&quot;);
                System.out.println(Thread.currentThread().getName()+&quot; put 3&quot;);
                blockingQueue.put(&quot;3&quot;);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },&quot;T1&quot;).start();


        new Thread(()-&amp;gt;{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+&quot;=&amp;gt;&quot;+blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+&quot;=&amp;gt;&quot;+blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+&quot;=&amp;gt;&quot;+blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },&quot;T2&quot;).start();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2Rza4.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;线程池重点&quot;&gt;线程池(重点)&lt;/h2&gt;

&lt;p&gt;线程池：三大方法、7大参数、4种拒绝策略&lt;/p&gt;

&lt;p&gt;程序的运行，本质：占用系统的资源！ 优化资源的使用！=&amp;gt;池化技术&lt;/p&gt;

&lt;p&gt;线程池、连接池、内存池、对象池///….. 创建、销毁。十分浪费资源&lt;/p&gt;

&lt;p&gt;池化技术：事先准备好一些资源，有人要用，就来我这里拿，用完之后还给我。&lt;/p&gt;

&lt;h3 id=&quot;线程池的好处&quot;&gt;线程池的好处:&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;降低资源的消耗&lt;/li&gt;
  &lt;li&gt;提高响应的速度&lt;/li&gt;
  &lt;li&gt;方便管理&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;线程复用、可以控制最大并发数、管理线程&lt;/p&gt;

&lt;h3 id=&quot;三大方法&quot;&gt;三大方法&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2RxZF.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Executors 工具类、3大方法
public class Demo01 {
	public static void main(String[] args) {
		myThread1 myThread = new myThread1();
		//ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程
		// ExecutorService threadPool = Executors.newFixedThreadPool(5); // 创建一个固定的线程池的大小
		 ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩的，遇强则强，遇弱则弱
		try {
			for (int i = 0; i &amp;lt; 100; i++) {
				// 使用了线程池之后，使用线程池来创建线程
				threadPool.execute(myThread);
			}
		} catch (Exception e) {
		e.printStackTrace();
		} finally {
		// 线程池用完，程序结束，关闭线程池
		threadPool.shutdown();
		}
	}
}

class myThread1 extends Thread{
	public void run(){
		System.out.println(Thread.currentThread().getName()+&quot; ok&quot;);
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2RjqU.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2RXrT.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;7大参数&quot;&gt;7大参数&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue&amp;lt;Runnable&amp;gt;()));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue&amp;lt;Runnable&amp;gt;());
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue&amp;lt;Runnable&amp;gt;());
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue&amp;lt;Runnable&amp;gt; workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public ThreadPoolExecutor(int corePoolSize,// 核心线程池大小
                          int maximumPoolSize,// 最大核心线程池大小
                          long keepAliveTime,// 超时了没有人调用就会释放
                          TimeUnit unit,// 超时单位
                          BlockingQueue&amp;lt;Runnable&amp;gt; workQueue,// 阻塞队列
                          ThreadFactory threadFactory,// 线程工厂：创建线程的，一般不用动
                          RejectedExecutionHandler handler// 拒绝策略) {
    if (corePoolSize &amp;lt; 0 ||
        maximumPoolSize &amp;lt;= 0 ||
        maximumPoolSize &amp;lt; corePoolSize ||
        keepAliveTime &amp;lt; 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;4种拒绝策略&quot;&gt;4种拒绝策略&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2RHGn.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
* new ThreadPoolExecutor.AbortPolicy() // 银行满了，还有人进来，不处理这个人的，抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里！
* new ThreadPoolExecutor.DiscardPolicy() //队列满了，丢掉任务，不会抛出异常！
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了，尝试去和最早的竞争，也不会抛出异常！
*/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;池的最大的大小如何去设置&quot;&gt;池的最大的大小如何去设置&lt;/h3&gt;

&lt;p&gt;了解：IO密集型，CPU密集型：（调优）&lt;/p&gt;

&lt;h3 id=&quot;自定义线程池&quot;&gt;自定义线程池&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Executors 工具类、3大方法

/**
 * new ThreadPoolExecutor.AbortPolicy() // 银行满了，还有人进来，不处理这个人的，抛出异常
 * new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里！
 * new ThreadPoolExecutor.DiscardPolicy() //队列满了，丢掉任务，不会抛出异常！
 * new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了，尝试去和最早的竞争，也不会抛出异常！
 */
public class Demo01 {
    public static void main(String[] args) {
        // 自定义线程池！工作 ThreadPoolExecutor

        // 最大线程到底该如何定义
        // 1、CPU 密集型，几核，就是几，可以保持CPU的效率最高！
        // 2、IO  密集型   &amp;gt; 判断你程序中十分耗IO的线程，
        // 程序   15个大型任务  io十分占用资源！

        // 获取CPU的核数
        System.out.println(Runtime.getRuntime().availableProcessors());

        List  list = new ArrayList();

        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                Runtime.getRuntime().availableProcessors(),
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque&amp;lt;&amp;gt;(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());  //队列满了，尝试去和最早的竞争，也不会抛出异常！
        try {
            // 最大承载：Deque + max
            // 超过 RejectedExecutionException
            for (int i = 1; i &amp;lt;= 9; i++) {
                // 使用了线程池之后，使用线程池来创建线程
                threadPool.execute(()-&amp;gt;{
                    System.out.println(Thread.currentThread().getName()+&quot; ok&quot;);
                });
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完，程序结束，关闭线程池
            threadPool.shutdown();
        }

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;四大函数式接口必需掌握&quot;&gt;四大函数式接口（必需掌握）&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2R7Ps.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;lambda表达式、链式编程、函数式接口、Stream流式计算&lt;/p&gt;

&lt;p&gt;函数式接口： 只有一个方法的接口&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@FunctionalInterface
public interface Runnable {
	public abstract void run();
}
// 泛型、枚举、反射
// lambda表达式、链式编程、函数式接口、Stream流式计算
// 超级多FunctionalInterface
// 简化编程模型，在新版本的框架底层大量应用！
// foreach(消费者类的函数式接口)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;function函数式接口&quot;&gt;Function函数式接口&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2ROMV.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Function 函数型接口, 有一个输入参数，有一个输出
 * 只要是 函数型接口 可以 用 lambda表达式简化
 */
public class Demo01 {
    public static void main(String[] args) {
        //
//        Function&amp;lt;String,String&amp;gt; function = new Function&amp;lt;String,String&amp;gt;() {
//            @Override
//            public String apply(String str) {
//                return str;
//            }
//        };

        Function&amp;lt;String,String&amp;gt; function = str-&amp;gt;{return str;};

        System.out.println(function.apply(&quot;asd&quot;));
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;断定型接口有一个输入参数返回值只能是-布尔值&quot;&gt;断定型接口：有一个输入参数，返回值只能是 布尔值！&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2Rb2q.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 断定型接口：有一个输入参数，返回值只能是 布尔值！
 */
public class Demo02 {
    public static void main(String[] args) {
        // 判断字符串是否为空
//        Predicate&amp;lt;String&amp;gt; predicate = new Predicate&amp;lt;String&amp;gt;(){
////            @Override
////            public boolean test(String str) {
////                return str.isEmpty();
////            }
////        };

        Predicate&amp;lt;String&amp;gt; predicate = (str)-&amp;gt;{return str.isEmpty(); };
        System.out.println(predicate.test(&quot;&quot;));

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;consumer-消费型接口&quot;&gt;Consumer 消费型接口&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2Rqx0.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Consumer 消费型接口: 只有输入，没有返回值
 */
public class Demo03 {
    public static void main(String[] args) {
//        Consumer&amp;lt;String&amp;gt; consumer = new Consumer&amp;lt;String&amp;gt;() {
//            @Override
//            public void accept(String str) {
//                System.out.println(str);
//            }
//        };
        Consumer&amp;lt;String&amp;gt; consumer = (str)-&amp;gt;{System.out.println(str);};
        consumer.accept(&quot;sdadasd&quot;);

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;supplier-供给型接口&quot;&gt;Supplier 供给型接口&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpCsP.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.function.Supplier;

/**
 * Supplier 供给型接口 没有参数，只有返回值
 */
public class Demo04 {
    public static void main(String[] args) {
//        Supplier supplier = new Supplier&amp;lt;Integer&amp;gt;() {
//            @Override
//            public Integer get() {
//                System.out.println(&quot;get()&quot;);
//                return 1024;
//            }
//        };

        Supplier supplier = ()-&amp;gt;{ return 1024; };
        System.out.println(supplier.get());
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;stream流式计算&quot;&gt;Stream流式计算&lt;/h2&gt;

&lt;h3 id=&quot;什么是stream流式计算&quot;&gt;什么是Stream流式计算&lt;/h3&gt;

&lt;p&gt;大数据：存储 + 计算&lt;/p&gt;

&lt;p&gt;集合、MySQL 本质就是存储东西的；&lt;/p&gt;

&lt;p&gt;计算都应该交给流来操作！&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfSLVO.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;import java.util.Arrays;
import java.util.List;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * 题目要求：一分钟内完成此题，只能用一行代码实现！
 * 现在有5个用户！筛选：
 * 1、ID 必须是偶数
 * 2、年龄必须大于23岁
 * 3、用户名转为大写字母
 * 4、用户名字母倒着排序
 * 5、只输出一个用户！
 */
public class Test {
    public static void main(String[] args) {
        User u1 = new User(1,&quot;a&quot;,21);
        User u2 = new User(2,&quot;b&quot;,22);
        User u3 = new User(3,&quot;c&quot;,23);
        User u4 = new User(4,&quot;d&quot;,24);
        User u5 = new User(6,&quot;e&quot;,25);
        // 集合就是存储
        List&amp;lt;User&amp;gt; list = Arrays.asList(u1, u2, u3, u4, u5);

        // 计算交给Stream流
        // lambda表达式、链式编程、函数式接口、Stream流式计算
        list.stream()
                .filter(u-&amp;gt;{return u.getId()%2==0;})
                .filter(u-&amp;gt;{return u.getAge()&amp;gt;23;})
                .map(u-&amp;gt;{return u.getName().toUpperCase();})
                .sorted((uu1,uu2)-&amp;gt;{return uu2.compareTo(uu1);})
                .limit(1)
                .forEach(System.out::println);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;forkjoin&quot;&gt;ForkJoin&lt;/h2&gt;

&lt;h3 id=&quot;什么是-forkjoin&quot;&gt;什么是 ForkJoin&lt;/h3&gt;

&lt;p&gt;ForkJoin 在 JDK 1.7 ， 并行执行任务！提高效率。大数据量！&lt;/p&gt;

&lt;p&gt;大数据：Map Reduce （把大任务拆分为小任务）&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfSOaD.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;forkjoin-特点&quot;&gt;ForkJoin 特点：&lt;/h3&gt;

&lt;p&gt;工作窃取&lt;/p&gt;

&lt;p&gt;这个里面维护的都是双端队列&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfSXIe.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WDyV.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/24/r2WrLT.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.concurrent.RecursiveTask;

/**
 * 求和计算的任务！
 * 3000   6000（ForkJoin）  9000（Stream并行流）
 * // 如何使用 forkjoin
 * // 1、forkjoinPool 通过它来执行
 * // 2、计算任务 forkjoinPool.execute(ForkJoinTask task)
 * // 3. 计算类要继承 ForkJoinTask
 */
public class ForkJoinDemo extends RecursiveTask&amp;lt;Long&amp;gt; {

    private Long start;  // 1
    private Long end;    // 1990900000

    // 临界值
    private Long temp = 10000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    // 计算方法
    @Override
    protected Long compute() {
        if ((end-start)&amp;lt;temp){
            Long sum = 0L;
            for (Long i = start; i &amp;lt;= end; i++) {
                sum += i;
            }
            return sum;
        }else { // forkjoin 递归
            long middle = (start + end) / 2; // 中间值
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork(); // 拆分任务，把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
            task2.fork(); // 拆分任务，把任务压入线程队列

            return task1.join() + task2.join();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

/**
 * 同一个任务，别人效率高你几十倍！
 */
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // test1(); // 12224
        // test2(); // 10038
        // test3(); // 153
    }

    // 普通程序员
    public static void test1(){
        Long sum = 0L;
        long start = System.currentTimeMillis();
        for (Long i = 1L; i &amp;lt;= 10_0000_0000; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println(&quot;sum=&quot;+sum+&quot; 时间：&quot;+(end-start));
    }

    // 会使用ForkJoin
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask&amp;lt;Long&amp;gt; task = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask&amp;lt;Long&amp;gt; submit = forkJoinPool.submit(task);// 提交任务
        Long sum = submit.get();

        long end = System.currentTimeMillis();

        System.out.println(&quot;sum=&quot;+sum+&quot; 时间：&quot;+(end-start));
    }

    public static void test3(){
        long start = System.currentTimeMillis();
        // Stream并行流 ()  (]
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println(&quot;sum=&quot;+&quot;时间：&quot;+(end-start));
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;异步回调&quot;&gt;异步回调&lt;/h2&gt;

&lt;p&gt;Future 设计的初衷： 对将来的某个事件的结果进行建模&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpFZ8.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 异步调用： CompletableFuture
 * // 异步执行
 * // 成功回调
 * // 失败回调
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 没有返回值的 runAsync 异步回调
//        CompletableFuture&amp;lt;Void&amp;gt; completableFuture = CompletableFuture.runAsync(()-&amp;gt;{
//            try {
//                TimeUnit.SECONDS.sleep(2);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName()+&quot;runAsync=&amp;gt;Void&quot;);
//        });
//
//        System.out.println(&quot;1111&quot;);
//
//        completableFuture.get(); // 获取阻塞执行结果

        // 有返回值的 supplyAsync 异步回调
        // ajax，成功和失败的回调
        // 返回的是错误信息；
        CompletableFuture&amp;lt;Integer&amp;gt; completableFuture = CompletableFuture.supplyAsync(()-&amp;gt;{
            System.out.println(Thread.currentThread().getName()+&quot;supplyAsync=&amp;gt;Integer&quot;);
            int i = 10/0;
            return 1024;
        });

        System.out.println(completableFuture.whenComplete((t, u) -&amp;gt; {
            System.out.println(&quot;t=&amp;gt;&quot; + t); // 正常的返回结果
            System.out.println(&quot;u=&amp;gt;&quot; + u); // 错误信息：java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
        }).exceptionally((e) -&amp;gt; {
            System.out.println(e.getMessage());
            return 233; // 可以获取到错误的返回结果
        }).get());

        /**
         * succee Code 200
         * error Code 404 500
         */
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfSxGd.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;jmm&quot;&gt;JMM&lt;/h2&gt;

&lt;p&gt;JMM ： Java内存模型，不存在的东西，概念！约定！&lt;/p&gt;

&lt;h3 id=&quot;关于jmm的一些同步的约定&quot;&gt;关于JMM的一些同步的约定：&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;线程解锁前，必须把共享变量立刻刷回主存。&lt;/li&gt;
  &lt;li&gt;线程加锁前，必须读取主存中的最新值到工作内存中！&lt;/li&gt;
  &lt;li&gt;加锁和解锁是同一把锁&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpqln.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;内存交互操作有8种，虚拟机实现必须保证每一个操作都是原子的，不可在分的（对于double和long类型的变量来说，load、store、read和write操作在某些平台上允许例外）&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;lock （锁定）：作用于主内存的变量，把一个变量标识为线程独占状态&lt;/li&gt;
  &lt;li&gt;unlock （解锁）：作用于主内存的变量，它把一个处于锁定状态的变量释放出来，释放后的变量才可以被其他线程锁定&lt;/li&gt;
  &lt;li&gt;read （读取）：作用于主内存变量，它把一个变量的值从主内存传输到线程的工作内存中，以便随后的load动作使用&lt;/li&gt;
  &lt;li&gt;load （载入）：作用于工作内存的变量，它把read操作从主存中变量放入工作内存中&lt;/li&gt;
  &lt;li&gt;use （使用）：作用于工作内存中的变量，它把工作内存中的变量传输给执行引擎，每当虚拟机遇到一个需要使用到变量的值，就会使用到这个指令&lt;/li&gt;
  &lt;li&gt;assign （赋值）：作用于工作内存中的变量，它把一个从执行引擎中接受到的值放入工作内存的变量副本中&lt;/li&gt;
  &lt;li&gt;store （存储）：作用于主内存中的变量，它把一个从工作内存中一个变量的值传送到主内存中，以便后续的write使用&lt;/li&gt;
  &lt;li&gt;write （写入）：作用于主内存中的变量，它把store操作从工作内存中得到的变量的值放入主内存的变量中&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;JMM对这八种指令的使用，制定了如下规则：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;不允许read和load、store和write操作之一单独出现。即使用了read必须load，使用了store必须write&lt;/li&gt;
  &lt;li&gt;不允许线程丢弃他最近的assign操作，即工作变量的数据改变了之后，必须告知主存&lt;/li&gt;
  &lt;li&gt;不允许一个线程将没有assign的数据从工作内存同步回主内存&lt;/li&gt;
  &lt;li&gt;一个新的变量必须在主内存中诞生，不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前，必须经过assign和load操作&lt;/li&gt;
  &lt;li&gt;一个变量同一时间只有一个线程能对其进行lock。多次lock后，必须执行相同次数的unlock才能解锁&lt;/li&gt;
  &lt;li&gt;如果对一个变量进行lock操作，会清空所有工作内存中此变量的值，在执行引擎使用这个变量前，必须重新load或assign操作初始化变量的值&lt;/li&gt;
  &lt;li&gt;如果一个变量没有被lock，就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量&lt;/li&gt;
  &lt;li&gt;对一个变量进行unlock操作之前，必须把此变量同步回主内存&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;问题： 程序不知道主内存的值已经被修改过了&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpbSs.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;volatile&quot;&gt;Volatile&lt;/h2&gt;

&lt;h3 id=&quot;保证可见性&quot;&gt;保证可见性&lt;/h3&gt;

&lt;p&gt;Volatile 是 Java 虚拟机提供&lt;strong&gt;轻量级的同步机制&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;保证可见性&lt;/li&gt;
  &lt;li&gt;不保证原子性&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;禁止指令重排&lt;/p&gt;

    &lt;p&gt;import java.util.concurrent.TimeUnit;&lt;/p&gt;

    &lt;p&gt;public class JMMDemo {
     // 不加 volatile 程序就会死循环！
     // 加 volatile 可以保证可见性
     private volatile static int num = 0;&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; public static void main(String[] args) { // main

     new Thread(()-&amp;gt;{ // 线程 1 对主内存的变化不知道的
         while (num==0){

         }
     }).start();

     try {
         TimeUnit.SECONDS.sleep(1);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }

     num = 1;
     System.out.println(num);

 }  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;不保证原子性&quot;&gt;不保证原子性&lt;/h3&gt;

&lt;p&gt;原子性 : 不可分割&lt;/p&gt;

&lt;p&gt;线程A在执行任务的时候，不能被打扰的，也不能被分割。要么同时成功，要么同时失败。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// volatile 不保证原子性
public class VDemo02 {

    // volatile 不保证原子性
    // 原子类的 Integer
    private volatile static int num = 0;

    public static void add(){
        num++; // 不是一个原子性操作

    }

    public static void main(String[] args) {

        //理论上num结果应该为 2 万
        for (int i = 1; i &amp;lt;= 20; i++) {
            new Thread(()-&amp;gt;{
                for (int j = 0; j &amp;lt; 1000 ; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()&amp;gt;2){ // main  gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + &quot; &quot; + num);


    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果不加 lock 和 synchronized ，怎么样保证原子性&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfp7Wj.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;使用原子类，解决 原子性问题&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpTYQ.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.concurrent.atomic.AtomicInteger;

// volatile 不保证原子性
public class VDemo02 {

    // volatile 不保证原子性
    // 原子类的 Integer
    private volatile static AtomicInteger num = new AtomicInteger();

    public static void add(){
        // num++; // 不是一个原子性操作
        num.getAndIncrement(); // AtomicInteger + 1 方法， CAS
    }

    public static void main(String[] args) {

        //理论上num结果应该为 2 万
        for (int i = 1; i &amp;lt;= 20; i++) {
            new Thread(()-&amp;gt;{
                for (int j = 0; j &amp;lt; 1000 ; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()&amp;gt;2){ // main  gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + &quot; &quot; + num);


    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这些类的底层都直接和操作系统挂钩！在内存中修改值！Unsafe类是一个很特殊的存在！&lt;/p&gt;

&lt;h3 id=&quot;指令重排&quot;&gt;指令重排&lt;/h3&gt;

&lt;p&gt;什么是 指令重排：你写的程序，计算机并不是按照你写的那样去执行的。&lt;/p&gt;

&lt;p&gt;源代码–&amp;gt;编译器优化的重排–&amp;gt; 指令并行也可能会重排–&amp;gt; 内存系统也会重排—&amp;gt; 执行&lt;/p&gt;

&lt;p&gt;处理器在进行指令重排的时候，考虑：数据之间的依赖性！&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们所期望的：1234 但是可能执行的时候回变成 2134 1324&lt;/p&gt;

&lt;p&gt;不可能是 4123！&lt;/p&gt;

&lt;p&gt;可能造成影响的结果： a b x y 这四个值默认都是 0；&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpoFg.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfp5TS.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;指令重排导致的诡异结果： x = 2；y = 1；&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;volatile可以避免指令重排：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;内存屏障。CPU指令。作用：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;保证特定的操作的执行顺序！&lt;/li&gt;
  &lt;li&gt;可以保证某些变量的内存可见性 （利用这些特性volatile实现了可见性）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfp4w8.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Volatile 是可以保持 可见性。不能保证原子性，由于&lt;strong&gt;内存屏障&lt;/strong&gt;，可以保证避免指令重排的现象产生！&lt;/p&gt;

&lt;h2 id=&quot;彻底玩转单例模式&quot;&gt;彻底玩转单例模式&lt;/h2&gt;

&lt;p&gt;饿汉式 DCL懒汉式，深究！&lt;/p&gt;

&lt;h3 id=&quot;dcl懒汉式&quot;&gt;DCL懒汉式&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 懒汉式单例
// 道高一尺，魔高一丈！
public class LazyMan {

    private static boolean qinjiang = false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (qinjiang == false){
                qinjiang = true;
            }else {
                throw new RuntimeException(&quot;不要试图使用反射破坏异常&quot;);
            }
        }
    }

    private volatile static LazyMan lazyMan;

    // 双重检测锁模式的 懒汉式单例  DCL懒汉式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

    // 反射！
    public static void main(String[] args) throws Exception {
//        LazyMan instance = LazyMan.getInstance();

        Field qinjiang = LazyMan.class.getDeclaredField(&quot;qinjiang&quot;);
        qinjiang.setAccessible(true);

        Constructor&amp;lt;LazyMan&amp;gt; declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance = declaredConstructor.newInstance();

        qinjiang.set(instance,false);

        LazyMan instance2 = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);
    }

}




/**
 * 1. 分配内存空间
 * 2、执行构造方法，初始化对象
 * 3、把这个对象指向这个空间
 *
 * 123
 * 132 A
 *     B // 此时lazyMan还没有完成构造
 */
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;枚举&quot;&gt;枚举&lt;/h3&gt;

&lt;p&gt;单例不安全，反射&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

// enum 是一个什么？ 本身也是一个Class类
public enum EnumSingle {

    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }

}

class Test{

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor&amp;lt;EnumSingle&amp;gt; declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

        // NoSuchMethodException: com.kuang.single.EnumSingle.&amp;lt;init&amp;gt;()
        System.out.println(instance1);
        System.out.println(instance2);

    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfphef.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;枚举类型的最终反编译源码:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpRyt.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;深入理解cas&quot;&gt;深入理解CAS&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpWOP.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;unsafe类&quot;&gt;Unsafe类&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfp2QI.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpgSA.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;CAS ： 比较当前工作内存中的值和主内存中的值，如果这个值是期望的，那么则执行操作！如果不是就一直循环！&lt;/p&gt;

&lt;h4 id=&quot;缺点&quot;&gt;缺点&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;循环会耗时&lt;/li&gt;
  &lt;li&gt;一次性只能保证一个共享变量的原子性&lt;/li&gt;
  &lt;li&gt;ABA问题&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;cas--aba-问题狸猫换太子&quot;&gt;CAS ： ABA 问题（狸猫换太子）&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfp6Wd.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpyJH.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;原子引用&quot;&gt;原子引用&lt;/h2&gt;

&lt;p&gt;解决ABA 问题，引入原子引用！ 对应的思想：乐观锁！&lt;/p&gt;

&lt;p&gt;带版本号的原子操作&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CASDemo {

    //AtomicStampedReference 注意，如果泛型是一个包装类，注意对象的引用问题

    // 正常在业务操作，这里面比较的都是一个个对象
    static AtomicStampedReference&amp;lt;Integer&amp;gt; atomicStampedReference = new AtomicStampedReference&amp;lt;&amp;gt;(1,1);

    // CAS  compareAndSet : 比较并交换！
    public static void main(String[] args) {

        new Thread(()-&amp;gt;{
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println(&quot;a1=&amp;gt;&quot;+stamp);

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Lock lock = new ReentrantLock(true);

            atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

            System.out.println(&quot;a2=&amp;gt;&quot;+atomicStampedReference.getStamp());


            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println(&quot;a3=&amp;gt;&quot;+atomicStampedReference.getStamp());

        },&quot;a&quot;).start();


        // 乐观锁的原理相同！
        new Thread(()-&amp;gt;{
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println(&quot;b1=&amp;gt;&quot;+stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(atomicStampedReference.compareAndSet(1, 6,
                    stamp, stamp + 1));

            System.out.println(&quot;b2=&amp;gt;&quot;+atomicStampedReference.getStamp());

        },&quot;b&quot;).start();

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意：
Integer 使用了对象缓存机制，默认范围是 -128 ~ 127 ，推荐使用静态工厂方法 valueOf 获取对象实例，而不是 new，因为 valueOf 使用缓存，而 new 一定会创建新的对象分配新的内存空间；&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpsFe.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;各种锁的理解&quot;&gt;各种锁的理解&lt;/h2&gt;

&lt;h3 id=&quot;公平锁非公平锁&quot;&gt;公平锁、非公平锁&lt;/h3&gt;

&lt;p&gt;公平锁： 非常公平， 不能够插队，必须先来后到！&lt;/p&gt;

&lt;p&gt;非公平锁：非常不公平，可以插队 （默认都是非公平）&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public ReentrantLock() {
	sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
	sync = fair ? new FairSync() : new NonfairSync();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;可重入锁&quot;&gt;可重入锁&lt;/h3&gt;

&lt;p&gt;可重入锁（递归锁）&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpBdO.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;synchronized&quot;&gt;Synchronized&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import javax.sound.midi.Soundbank;

// Synchronized
public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(()-&amp;gt;{
            phone.sms();
        },&quot;A&quot;).start();


        new Thread(()-&amp;gt;{
            phone.sms();
        },&quot;B&quot;).start();
    }
}

class Phone{

    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName() + &quot;sms&quot;);
        call(); // 这里也有锁
    }

    public synchronized void call(){
        System.out.println(Thread.currentThread().getName() + &quot;call&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpDoD.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;lock-版&quot;&gt;Lock 版&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo02 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();

        new Thread(()-&amp;gt;{
            phone.sms();
        },&quot;A&quot;).start();


        new Thread(()-&amp;gt;{
            phone.sms();
        },&quot;B&quot;).start();
    }
}

class Phone2{
    Lock lock = new ReentrantLock();

    public void sms(){
        lock.lock(); // 细节问题：lock.lock(); lock.unlock(); // lock 锁必须配对，否则就会死在里面
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + &quot;sms&quot;);
            call(); // 这里也有锁
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            lock.unlock();
        }

    }

    public void call(){

        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + &quot;call&quot;);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;自旋锁&quot;&gt;自旋锁&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rf98nP.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁
 */
public class SpinlockDemo {

    // int   0
    // Thread  null
    AtomicReference&amp;lt;Thread&amp;gt; atomicReference = new AtomicReference&amp;lt;&amp;gt;();

    // 加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + &quot;==&amp;gt; mylock&quot;);

        // 自旋锁
        while (!atomicReference.compareAndSet(null,thread)){

        }
    }


    // 解锁
    // 加锁
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + &quot;==&amp;gt; myUnlock&quot;);
        atomicReference.compareAndSet(thread,null);
    }



}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;测试&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();

        // 底层使用的自旋锁CAS
        SpinlockDemo lock = new SpinlockDemo();


        new Thread(()-&amp;gt; {
            lock.myLock();

            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }

        },&quot;T1&quot;).start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()-&amp;gt; {
            lock.myLock();

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }

        },&quot;T2&quot;).start();

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rf9tAS.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;死锁&quot;&gt;死锁&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfp0eK.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import com.sun.org.apache.xpath.internal.SourceTree;

import java.util.concurrent.TimeUnit;

public class DeadLockDemo {
    public static void main(String[] args) {

        String lockA = &quot;lockA&quot;;
        String lockB = &quot;lockB&quot;;

        new Thread(new MyThread(lockA, lockB), &quot;T1&quot;).start();
        new Thread(new MyThread(lockB, lockA), &quot;T2&quot;).start();

    }
}


class MyThread implements Runnable{

    private String lockA;
    private String lockB;

    public MyThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName() + &quot;lock:&quot;+lockA+&quot;=&amp;gt;get&quot;+lockB);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (lockB){
                System.out.println(Thread.currentThread().getName() + &quot;lock:&quot;+lockB+&quot;=&amp;gt;get&quot;+lockA);
            }

        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;解决问题&quot;&gt;解决问题&lt;/h3&gt;

&lt;h4 id=&quot;使用-jps--l-定位进程号&quot;&gt;使用 jps -l 定位进程号&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rfpdL6.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;使用jstack进程号找到死锁问题&quot;&gt;使用jstack进程号找到死锁问题&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/25/rf9w1s.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;</content><author><name>WuJunwei</name></author><category term="notes" /><category term="java" /><summary type="html">回顾线程进程；CopyOnWrite；常用的辅助类；读写锁；塞队列线程池(重点)；四大函数式接口（必需掌握）；Stream流式计算；ForkJoin；异步回调；深入理解CAS；原子引用；各种锁的理解</summary></entry><entry><title type="html">Java反射机制</title><link href="https://wujunwei99.github.io/notes/2020/11/26/java-Reflection.html" rel="alternate" type="text/html" title="Java反射机制" /><published>2020-11-26T07:21:37+00:00</published><updated>2020-11-26T07:21:37+00:00</updated><id>https://wujunwei99.github.io/notes/2020/11/26/java-Reflection</id><content type="html" xml:base="https://wujunwei99.github.io/notes/2020/11/26/java-Reflection.html">&lt;p&gt;Java反射机制概述;理解Class类并获取Class实例;类的加载与ClassLoader的理解;创建运行时类的对象;获取运行时类的完整结构;调用运行时类的指定结构;反射的应用：动态代理&lt;/p&gt;

&lt;h2 id=&quot;java反射机制概述&quot;&gt;Java反射机制概述&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Reflection（反射）是被视为动态语言的关键，反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息，并能直接操作任意对象的内部属性及方法。&lt;/li&gt;
  &lt;li&gt;加载完类之后，在堆内存的方法区中就产生了一个Class类型的对象（一个类只有一个Class对象），这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子，透过这个镜子看到类的结构，所以，我们形象的称之为：反射&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/DxomAe.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;补充动态语言-vs-静态语言&quot;&gt;补充：动态语言 vs 静态语言&lt;/h3&gt;

&lt;h4 id=&quot;动态语言&quot;&gt;动态语言&lt;/h4&gt;

&lt;p&gt;是一类在运行时可以改变其结构的语言：例如新的函数、对象、甚至代码可以被引进，已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。&lt;/p&gt;

&lt;p&gt;主要动态语言：Object-C、C#、JavaScript、PHP、Python、Erlang。&lt;/p&gt;

&lt;h4 id=&quot;静态语言&quot;&gt;静态语言&lt;/h4&gt;

&lt;p&gt;与动态语言相对应的，运行时结构不可变的语言就是静态语言。如Java、C、C++。&lt;/p&gt;

&lt;p&gt;Java不是动态语言，但Java可以称之为“准动态语言”。即Java有一定的动态性，我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活！&lt;/p&gt;

&lt;h3 id=&quot;java反射机制研究及应用&quot;&gt;Java反射机制研究及应用&lt;/h3&gt;

&lt;p&gt;Java反射机制提供的功能&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在运行时判断任意一个对象所属的类&lt;/li&gt;
  &lt;li&gt;在运行时构造任意一个类的对象&lt;/li&gt;
  &lt;li&gt;在运行时判断任意一个类所具有的成员变量和方法&lt;/li&gt;
  &lt;li&gt;在运行时获取泛型信息&lt;/li&gt;
  &lt;li&gt;在运行时调用任意一个对象的成员变量和方法&lt;/li&gt;
  &lt;li&gt;在运行时处理注解&lt;/li&gt;
  &lt;li&gt;生成动态代理&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;反射相关的主要api&quot;&gt;反射相关的主要API&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;java.lang.Class:代表一个类&lt;/li&gt;
  &lt;li&gt;java.lang.reflect.Method:代表类的方法&lt;/li&gt;
  &lt;li&gt;java.lang.reflect.Field:代表类的成员变量&lt;/li&gt;
  &lt;li&gt;java.lang.reflect.Constructor:代表类的构造器&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;问题&quot;&gt;问题&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;疑问1：通过直接new的方式或反射的方式都可以调用公共的结构，开发中到底用那个？
    &lt;ul&gt;
      &lt;li&gt;建议：直接new的方式。&lt;/li&gt;
      &lt;li&gt;什么时候会使用：反射的方式。 反射的特征：动态性&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;疑问2：反射机制与面向对象中的封装性是不是矛盾的？如何看待两个技术？
    &lt;ul&gt;
      &lt;li&gt;不矛盾。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;理解class类并获取class实例&quot;&gt;理解Class类并获取Class实例&lt;/h2&gt;

&lt;h3 id=&quot;class类&quot;&gt;Class类&lt;/h3&gt;

&lt;p&gt;在Object类中定义了以下的方法，此方法将被所有子类继承：&lt;/p&gt;

&lt;p&gt;● public final Class getClass()&lt;/p&gt;

&lt;p&gt;以上的方法返回值的类型是一个Class类，此类是Java反射的源头，实际上所谓反射从程序的运行结果来看也很好理解，即：可以通过对象反射求出类的名称。&lt;/p&gt;

&lt;p&gt;对象照镜子后可以得到的信息：某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言，JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Class本身也是一个类&lt;/li&gt;
  &lt;li&gt;Class 对象只能由系统建立对象&lt;/li&gt;
  &lt;li&gt;一个加载的类在 JVM 中只会有一个Class实例&lt;/li&gt;
  &lt;li&gt;一个Class对象对应的是一个加载到JVM中的一个.class文件&lt;/li&gt;
  &lt;li&gt;每个类的实例都会记得自己是由哪个 Class 实例所生成&lt;/li&gt;
  &lt;li&gt;通过Class可以完整地得到一个类中的所有被加载的结构&lt;/li&gt;
  &lt;li&gt;Class类是Reflection的根源，针对任何你想动态加载、运行的类，唯有先获得相应的Class对象&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;class类的常用方法&quot;&gt;Class类的常用方法&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/DxoZ7D.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;反射的应用举例&quot;&gt;反射的应用举例&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	Class clazz = Person.class;
    //1.通过反射，创建Person类的对象
    Constructor cons = clazz.getConstructor(String.class,int.class);
    Object obj = cons.newInstance(&quot;Tom&quot;, 12);
    Person p = (Person) obj;
    System.out.println(p.toString());
    //2.通过反射，调用对象指定的属性、方法
    //调用属性
    Field age = clazz.getDeclaredField(&quot;age&quot;);
    age.set(p,10);
    System.out.println(p.toString());

    //调用方法
    Method show = clazz.getDeclaredMethod(&quot;show&quot;);
    show.invoke(p);

    System.out.println(&quot;*******************************&quot;);

    //通过反射，可以调用Person类的私有结构的。比如：私有的构造器、方法、属性
    //调用私有的构造器
    Constructor cons1 = clazz.getDeclaredConstructor(String.class);
    cons1.setAccessible(true);
    Person p1 = (Person) cons1.newInstance(&quot;Jerry&quot;);
    System.out.println(p1);

    //调用私有的属性
    Field name = clazz.getDeclaredField(&quot;name&quot;);
    name.setAccessible(true);
    name.set(p1,&quot;HanMeimei&quot;);
    System.out.println(p1);

    //调用私有的方法
    Method showNation = clazz.getDeclaredMethod(&quot;showNation&quot;, String.class);
    showNation.setAccessible(true);
    String nation = (String) showNation.invoke(p1,&quot;中国&quot;);//相当于String nation = p1.showNation(&quot;中国&quot;)
    System.out.println(nation);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;关于javalangclass类的理解&quot;&gt;关于java.lang.Class类的理解&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;类的加载过程：
 程序经过javac.exe命令以后，会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类，我们就称为运行时类，此运行时类，就作为Class的一个实例。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;换句话说，Class的实例就对应着一个运行时类。&lt;/li&gt;
  &lt;li&gt;加载到内存中的运行时类，会缓存一定的时间。在此时间之内，我们可以通过不同的方式
来获取此运行时类。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;获取class类的实例四种方法&quot;&gt;获取Class类的实例(四种方法)&lt;/h3&gt;

&lt;p&gt;1）前提：若已知具体的类，通过类的class属性获取，该方法最为安全可靠，程序性能最高&lt;/p&gt;

&lt;p&gt;实例：Class clazz = String.class;&lt;/p&gt;

&lt;p&gt;2）前提：已知某个类的实例，调用该实例的getClass()方法获取Class对象&lt;/p&gt;

&lt;p&gt;实例： Person p1 = new Person();&lt;/p&gt;

&lt;p&gt;Class clazz2 = p1.getClass();&lt;/p&gt;

&lt;p&gt;3）前提：已知一个类的全类名，且该类在类路径下，可通过Class类的静态方法forName()获取，可能抛出ClassNotFoundException&lt;/p&gt;

&lt;p&gt;实例：Class clazz = Class.forName(“java.lang.String”);&lt;/p&gt;

&lt;p&gt;4）其他方式(不做要求)&lt;/p&gt;

&lt;p&gt;ClassLoader cl = this.getClass().getClassLoader();&lt;/p&gt;

&lt;p&gt;Class clazz4 = cl.loadClass(“类的全类名”)&lt;/p&gt;

&lt;h3 id=&quot;哪些类型可以有class对象&quot;&gt;哪些类型可以有Class对象？&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;class： 外部类，成员(成员内部类，静态内部类)，局部内部类，匿名内部类&lt;/li&gt;
  &lt;li&gt;interface：接口&lt;/li&gt;
  &lt;li&gt;[]：数组&lt;/li&gt;
  &lt;li&gt;enum：枚举&lt;/li&gt;
  &lt;li&gt;annotation：注解@interface&lt;/li&gt;
  &lt;li&gt;primitive type：基本数据类型&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;void&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; Class c1 = Object.class;
 Class c2 = Comparable.class;
 Class c3 = String[].class;
 Class c4 = int[][].class;
 Class c5 = ElementType.class;
 Class c6 = Override.class;
 Class c7 = int.class;
 Class c8 = void.class;
 Class c9 = Class.class;
 int[] a = new int[10];
 int[] b = new int[100];
 Class c10 = a.getClass();
 Class c11 = b.getClass();
 // 只要元素类型与维度一样，就是同一个Class
 System.out.println(c10 == c11);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;类的加载与classloader的理解&quot;&gt;类的加载与ClassLoader的理解&lt;/h2&gt;

&lt;p&gt;当程序主动使用某个类时，如果该类还未被加载到内存中，则系统会通过如下三个步骤来对该类进行初始化&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/DxoV0O.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;加载：将class文件字节码内容加载到内存中，并将这些静态数据转换成方法区的运行时数据结构，然后生成一个代表这个类的java.lang.Class对象，作为方法区中类数据的访问入口（即引用地址）。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。&lt;/li&gt;
  &lt;li&gt;链接：将Java类的二进制代码合并到JVM的运行状态之中的过程。
    &lt;ul&gt;
      &lt;li&gt;验证：确保加载的类信息符合JVM规范，例如：以cafe开头，没有安全方面的问题&lt;/li&gt;
      &lt;li&gt;准备：正式为类变量（static）分配内存并设置类变量默认初始值的阶段，这些内存都将在方法区中进行分配。&lt;/li&gt;
      &lt;li&gt;解析：虚拟机常量池内的符号引用（常量名）替换为直接引用（地址）的过程。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;初始化:
    &lt;ul&gt;
      &lt;li&gt;执行类构造器&lt;clinit&gt;()方法的过程。类构造器&lt;clinit&gt;()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。（类构造器是构造类信息的，不是构造该类对象的构造器）。&lt;/clinit&gt;&lt;/clinit&gt;&lt;/li&gt;
      &lt;li&gt;当初始化一个类的时候，如果发现其父类还没有进行初始化，则需要先触发其父类的初始化。&lt;/li&gt;
      &lt;li&gt;虚拟机会保证一个类的&lt;clinit&gt;()方法在多线程环境中被正确加锁和同步。&lt;/clinit&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/DxoEnK.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;什么时候会发生类初始化&quot;&gt;什么时候会发生类初始化？&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;类的主动引用（一定会发生类的初始化）
    &lt;ul&gt;
      &lt;li&gt;当虚拟机启动，先初始化main方法所在的类&lt;/li&gt;
      &lt;li&gt;new一个类的对象&lt;/li&gt;
      &lt;li&gt;调用类的静态成员（除了final常量）和静态方法&lt;/li&gt;
      &lt;li&gt;使用java.lang.reflect包的方法对类进行反射调用&lt;/li&gt;
      &lt;li&gt;当初始化一个类，如果其父类没有被初始化，则先会初始化它的父类&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;类的被动引用（不会发生类的初始化）
    &lt;ul&gt;
      &lt;li&gt;当访问一个静态域时，只有真正声明这个域的类才会被初始化&lt;/li&gt;
      &lt;li&gt;当通过子类引用父类的静态变量，不会导致子类初始化&lt;/li&gt;
      &lt;li&gt;通过数组定义类引用，不会触发此类的初始化&lt;/li&gt;
      &lt;li&gt;引用常量不会触发此类的初始化（常量在链接阶段就存入调用类的常量池中了）&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;类加载器的作用&quot;&gt;类加载器的作用&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;类加载的作用&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;将class文件字节码内容加载到内存中，并将这些静态数据转换成方法区的运行时数据结构，然后在堆中生成一个代表这个类的java.lang.Class对象，作为方法区中类数据的访问入口。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;类缓存&lt;/strong&gt;：标准的JavaSE类加载器可以按要求查找类，但一旦某个类被加载到类加载器
中，它将维持加载（缓存）一段时间。不过JVM垃圾回收机制可以回收这些Class对象。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;了解classloader&quot;&gt;了解：ClassLoader&lt;/h3&gt;

&lt;p&gt;类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/DxokX6.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	//对于自定义类，使用系统类加载器进行加载
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    System.out.println(classLoader);
    //调用系统类加载器的getParent()：获取扩展类加载器
    ClassLoader classLoader1 = classLoader.getParent();
    System.out.println(classLoader1);
    //调用扩展类加载器的getParent()：无法获取引导类加载器
    //引导类加载器主要负责加载java的核心类库，无法加载自定义类的。
    ClassLoader classLoader2 = classLoader1.getParent();
    System.out.println(classLoader2);

    ClassLoader classLoader3 = String.class.getClassLoader();
    System.out.println(classLoader3);

	//4.测试当前类由哪个类加载器进行加载
	• classloader = Class.forName(&quot;exer2.ClassloaderDemo&quot;).getClassLoader();
	• System.out.println(classloader);
	• //5.测试JDK提供的Object类由哪个类加载器加载
	• classloader = 
	• Class.forName(&quot;java.lang.Object&quot;).getClassLoader();
	• System.out.println(classloader);
	• //*6.关于类加载器的一个主要方法：getResourceAsStream(String str):获取类路
	径下的指定文件的输入流
	• InputStream in = null;
	• in = this.getClass().getClassLoader().getResourceAsStream(&quot;exer2\\test.properties&quot;);
	• System.out.println(in);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;创建运行时类的对象&quot;&gt;创建运行时类的对象&lt;/h2&gt;

&lt;p&gt;创建类的对象：调用Class对象的newInstance()方法&lt;/p&gt;

&lt;p&gt;要 求：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;类必须有一个无参数的构造器。&lt;/li&gt;
  &lt;li&gt;类的构造器的访问权限需要足够。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;难道没有无参的构造器就不能创建对象了吗？&lt;/p&gt;

&lt;p&gt;不是！只要在操作的时候明确的调用类中的构造器，并将参数传递进去之后，才可以实例化操作。
步骤如下：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器&lt;/li&gt;
  &lt;li&gt;向构造器的形参中传递一个对象数组进去，里面包含了构造器中所需的各个参数。&lt;/li&gt;
  &lt;li&gt;通过Constructor实例化对象。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dxoi11.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

 		Class&lt;Person&gt; clazz = Person.class;
&lt;/Person&gt;&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    /*
    newInstance():调用此方法，创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。

    要想此方法正常的创建运行时类的对象，要求：
    1.运行时类必须提供空参的构造器
    2.空参的构造器的访问权限得够。通常，设置为public。


    在javabean中要求提供一个public的空参构造器。原因：
    1.便于通过反射，创建运行时类的对象
    2.便于子类继承此运行时类时，默认调用super()时，保证父类有此构造器

     */
    Person obj = clazz.newInstance();
    System.out.println(obj);

/*
创建一个指定类的对象。
classPath:指定类的全类名
 */
public Object getInstance(String classPath) throws Exception {
   Class clazz =  Class.forName(classPath);
   return clazz.newInstance();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;1.根据全类名获取对应的Class对象&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;String name = “atguigu.java.Person&quot;;
Class clazz = null;
clazz = Class.forName(name);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2.调用指定参数结构的构造器，生成Constructor的实例&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Constructor con = clazz.getConstructor(String.class,Integer.class);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;3.通过Constructor的实例创建对应类的对象，并初始化类属性&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Person p2 = (Person) con.newInstance(&quot;Peter&quot;,20);
System.out.println(p2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;获取运行时类的完整结构&quot;&gt;获取运行时类的完整结构&lt;/h2&gt;

&lt;p&gt;通过反射获取运行时类的完整结构&lt;/p&gt;

&lt;p&gt;Field、Method、Constructor、Superclass、Interface、Annotation&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;实现的全部接口&lt;/li&gt;
  &lt;li&gt;所继承的父类&lt;/li&gt;
  &lt;li&gt;全部的构造器&lt;/li&gt;
  &lt;li&gt;全部的方法&lt;/li&gt;
  &lt;li&gt;全部的Field&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;使用反射可以取得：&lt;/p&gt;

&lt;p&gt;1.实现的全部接口
	public Class&amp;lt;?&amp;gt;[] getInterfaces()&lt;/p&gt;

&lt;p&gt;确定此对象所表示的类或接口实现的接口。&lt;/p&gt;

&lt;p&gt;2.所继承的父类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public Class&amp;lt;? Super T&amp;gt; getSuperclass()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;返回表示此 Class 所表示的实体（类、接口、基本类型）的父类的Class。&lt;/p&gt;

&lt;p&gt;3.全部的构造器&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;public Constructor&lt;T&gt;[] getConstructors()
&lt;/T&gt;    &lt;ul&gt;
      &lt;li&gt;返回此 Class 对象所表示的类的所有public构造方法。获取当前运行时类中声明为public的构造器&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;public Constructor&lt;T&gt;[] getDeclaredConstructors(
&lt;/T&gt;    &lt;ul&gt;
      &lt;li&gt;返回此 Class 对象表示的类声明的所有构造方法。获取当前运行时类中声明的所有的构造器&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Constructor类中：
    &lt;ul&gt;
      &lt;li&gt;取得修饰符: public int getModifiers();&lt;/li&gt;
      &lt;li&gt;取得方法名称: public String getName();&lt;/li&gt;
      &lt;li&gt;取得参数的类型：public Class&amp;lt;?&amp;gt;[] getParameterTypes()&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4.全部的方法&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;public Method[] getDeclaredMethods()
    &lt;ul&gt;
      &lt;li&gt;返回此Class对象所表示的类或接口的全部方法。获取当前运行时类及其所有父类中声明为public权限的方法&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;public Method[] getMethods()
    &lt;ul&gt;
      &lt;li&gt;返回此Class对象所表示的类或接口的public的方法。获取当前运行时类中声明的所有方法。（不包含父类中声明的方法）&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Method类中：
    &lt;ul&gt;
      &lt;li&gt;public Class&amp;lt;?&amp;gt; getReturnType()取得全部的返回值&lt;/li&gt;
      &lt;li&gt;public Class&amp;lt;?&amp;gt;[] getParameterTypes()取得全部的参数&lt;/li&gt;
      &lt;li&gt;public int getModifiers()取得修饰符&lt;/li&gt;
      &lt;li&gt;public Class&amp;lt;?&amp;gt;[] getExceptionTypes()取得异常信息&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5.全部的Field&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;public Field[] getFields()
    &lt;ul&gt;
      &lt;li&gt;返回此Class对象所表示的类或接口的public的Field。获取当前运行时类及其父类中声明为public访问权限的属性&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;public Field[] getDeclaredFields()
    &lt;ul&gt;
      &lt;li&gt;返回此Class对象所表示的类或接口的全部Field。 获取当前运行时类中声明的所有属性。（不包含父类中声明的属性）&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Field方法中：
    &lt;ul&gt;
      &lt;li&gt;public int getModifiers() 以整数形式返回此Field的修饰符&lt;/li&gt;
      &lt;li&gt;public Class&amp;lt;?&amp;gt; getType() 得到Field的属性类型&lt;/li&gt;
      &lt;li&gt;public String getName() 返回Field的名称。&lt;/li&gt;
      &lt;li&gt;6.Annotation相关&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;get Annotation(Class&lt;T&gt; annotationClass)&lt;/T&gt;&lt;/li&gt;
  &lt;li&gt;getDeclaredAnnotations()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;7.泛型相关&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;获取父类泛型类型：Type getGenericSuperclass()&lt;/li&gt;
  &lt;li&gt;泛型类型：ParameterizedType&lt;/li&gt;
  &lt;li&gt;获取实际的泛型类型参数数组：getActualTypeArguments()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;8.类所在的包 Package getPackage()&lt;/p&gt;

&lt;h2 id=&quot;调用运行时类的指定结构&quot;&gt;调用运行时类的指定结构&lt;/h2&gt;

&lt;h3 id=&quot;调用指定方法&quot;&gt;调用指定方法&lt;/h3&gt;

&lt;p&gt;通过反射，调用类中的方法，通过Method类完成。步骤：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象，并设置此方法操作时所需要的参数类型。&lt;/li&gt;
  &lt;li&gt;之后使用Object invoke(Object obj, Object[] args)进行调用，并向方法中传递要设置的obj对象的参数信息&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/DxoPpR.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; /*
如何操作运行时类中的指定的方法 -- 需要掌握
 */
@Test
public void testMethod() throws Exception {

    Class clazz = Person.class;

    //创建运行时类的对象
    Person p = (Person) clazz.newInstance();

    /*
    1.获取指定的某个方法
    getDeclaredMethod():参数1 ：指明获取的方法的名称  参数2：指明获取的方法的形参列表
     */
    Method show = clazz.getDeclaredMethod(&quot;show&quot;, String.class);
    //2.保证当前方法是可访问的
    show.setAccessible(true);

    /*
    3. 调用方法的invoke():参数1：方法的调用者  参数2：给方法形参赋值的实参
    invoke()的返回值即为对应类中调用的方法的返回值。
     */
    Object returnValue = show.invoke(p,&quot;CHN&quot;); //String nation = p.show(&quot;CHN&quot;);
    System.out.println(returnValue);

    System.out.println(&quot;*************如何调用静态方法*****************&quot;);

    // private static void showDesc()

    Method showDesc = clazz.getDeclaredMethod(&quot;showDesc&quot;);
    showDesc.setAccessible(true);
    //如果调用的运行时类中的方法没有返回值，则此invoke()返回null
	//        Object returnVal = showDesc.invoke(null);
    Object returnVal = showDesc.invoke(Person.class);
    System.out.println(returnVal);//null

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;invoke&quot;&gt;invoke&lt;/h4&gt;

&lt;p&gt;Object invoke(Object obj, Object … args)&lt;/p&gt;

&lt;p&gt;说明：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Object 对应原方法的返回值，若原方法无返回值，此时返回null&lt;/li&gt;
  &lt;li&gt;若原方法若为静态方法，此时形参Object obj可为null&lt;/li&gt;
  &lt;li&gt;若原方法形参列表为空，则Object[] args为null&lt;/li&gt;
  &lt;li&gt;若原方法声明为private,则需要在调用此invoke()方法前，显式调用&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;方法对象的setAccessible(true)方法，将可访问private的方法&lt;/p&gt;

&lt;h3 id=&quot;调用指定属性&quot;&gt;调用指定属性&lt;/h3&gt;

&lt;p&gt;在反射机制中，可以直接通过Field类操作类中的属性，通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;在Field中：
    &lt;ul&gt;
      &lt;li&gt;public Object get(Object obj) 取得指定对象obj上此Field的属性内容&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容&lt;/p&gt;

        &lt;p&gt;Class clazz = Person.class;&lt;/p&gt;

        &lt;p&gt;//创建运行时类的对象
  Person p = (Person) clazz.newInstance();&lt;/p&gt;

        &lt;p&gt;//获取指定的属性：要求运行时类中属性声明为public
  //通常不采用此方法
  Field id = clazz.getField(“id”);&lt;/p&gt;

        &lt;p&gt;/*
  设置当前属性的值&lt;/p&gt;

        &lt;p&gt;set():参数1：指明设置哪个对象的属性   参数2：将此属性值设置为多少
   */&lt;/p&gt;

        &lt;p&gt;id.set(p,1001);&lt;/p&gt;

        &lt;p&gt;/*
  获取当前属性的值
  get():参数1：获取哪个对象的当前属性值
   */
  int pId = (int) id.get(p);
  System.out.println(pId);&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/*
如何操作运行时类中的指定的属性 -- 需要掌握
 */
@Test
public void testField1() throws Exception {
    Class clazz = Person.class;

    //创建运行时类的对象
    Person p = (Person) clazz.newInstance();

    //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
    Field name = clazz.getDeclaredField(&quot;name&quot;);

    //2.保证当前属性是可访问的
    name.setAccessible(true);
    //3.获取、设置指定对象的此属性值
    name.set(p,&quot;Tom&quot;);

    System.out.println(name.get(p));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;关于setaccessible方法的使用&quot;&gt;关于setAccessible方法的使用&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Method和Field、Constructor对象都有setAccessible()方法。&lt;/li&gt;
  &lt;li&gt;setAccessible启动和禁用访问安全检查的开关。&lt;/li&gt;
  &lt;li&gt;参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
    &lt;ul&gt;
      &lt;li&gt;提高反射的效率。如果代码中必须用反射，而该句代码需要频繁的被调用，那么请设置为true。&lt;/li&gt;
      &lt;li&gt;使得原本无法访问的私有成员也可以访问&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;参数值为false则指示反射的对象应该实施Java语言访问检查&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;调用指定构造器&quot;&gt;调用指定构造器&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/*
如何调用运行时类中的指定的构造器
 */
@Test
public void testConstructor() throws Exception {
    Class clazz = Person.class;

    //private Person(String name)
    /*
    1.获取指定的构造器
    getDeclaredConstructor():参数：指明构造器的参数列表
     */

    Constructor constructor = clazz.getDeclaredConstructor(String.class);

    //2.保证此构造器是可访问的
    constructor.setAccessible(true);

    //3.调用此构造器创建运行时类的对象
    Person per = (Person) constructor.newInstance(&quot;Tom&quot;);
    System.out.println(per);

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;反射的应用动态代理&quot;&gt;反射的应用：动态代理&lt;/h2&gt;

&lt;h3 id=&quot;代理设计模式的原理&quot;&gt;代理设计模式的原理&lt;/h3&gt;

&lt;p&gt;使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;之前为大家讲解过代理机制的操作，属于静态代理，特征是代理类和目标对象的类都是在编译期间确定下来，不利于程序的扩展。同时，每一个代理类只能为一个接口服务，这样一来程序开发中必然产生过多的代理。&lt;strong&gt;最好可以通过一个代理类完成全部的代理功能&lt;/strong&gt;。&lt;/li&gt;
  &lt;li&gt;动态代理是指客户通过代理类来调用其它对象的方法，并且是在程序运行时根据需要动态创建目标类的代理对象。&lt;/li&gt;
  &lt;li&gt;动态代理使用场合:
    &lt;ul&gt;
      &lt;li&gt;调试&lt;/li&gt;
      &lt;li&gt;远程方法调用&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;动态代理相比于静态代理的优点&quot;&gt;动态代理相比于静态代理的优点&lt;/h4&gt;

&lt;p&gt;抽象角色中（接口）声明的所有方法都被转移到调用处理器一个集中的方法中处理，这样，我们可以更加灵活和统一的处理众多的方法。&lt;/p&gt;

&lt;h3 id=&quot;java动态代理相关api&quot;&gt;Java动态代理相关API&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Proxy ：专门完成代理的操作类，是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。&lt;/li&gt;
  &lt;li&gt;提供用于创建动态代理类和动态代理对象的静态方法
    &lt;ul&gt;
      &lt;li&gt;static Class&amp;lt;?&amp;gt; getProxyClass(ClassLoader loader, Class&amp;lt;?&amp;gt;… interfaces) 创建一个动态代理类所对应的Class对象&lt;/li&gt;
      &lt;li&gt;static Object newProxyInstance(ClassLoader loader, Class&amp;lt;?&amp;gt;[] interfaces, InvocationHandler h) 直接创建一个动态代理对象&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/DxoF6x.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;动态代理步骤&quot;&gt;动态代理步骤&lt;/h3&gt;

&lt;p&gt;1.创建一个实现接口InvocationHandler的类，它必须实现invoke方法，以完成代理的具体操作。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public Object invoke(Object theProxy, Method method, Object[] params) 
throws Throwable{
try{
Object retval = method.invoke(targetObj, params);
// Print out the result
System.out.println(retval);
return retval;
}catch (Exception exc){}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dxo9h9.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;2.创建被代理的类以及接口&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/DxIv0U.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;3.通过Proxy的静态方法&lt;/p&gt;

&lt;p&gt;newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个Subject接口代理&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;RealSubject target = new RealSubject();
// Create a proxy to wrap the original implementation
DebugProxy proxy = new DebugProxy(target);
// Get a reference to the proxy through the Subject interface
Subject sub = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(),new Class[] { Subject.class }, proxy);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;4.通过 Subject代理调用RealSubject实现类的方法&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;String info = sub.say(“Peter&quot;, 24);
System.out.println(info);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/*
要想实现动态代理，需要解决的问题？
问题一：如何根据加载到内存中的被代理类，动态的创建一个代理类及其对象。
问题二：当通过代理类的对象调用方法a时，如何动态的去调用被代理类中的同名方法a。


 */

class ProxyFactory{
    //调用此方法，返回一个代理类的对象。解决问题一
    public static Object getProxyInstance(Object obj){//obj:被代理类的对象
        MyInvocationHandler handler = new MyInvocationHandler();

        handler.bind(obj);

        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
    }

}

class MyInvocationHandler implements InvocationHandler{

    private Object obj;//需要使用被代理类的对象进行赋值

    public void bind(Object obj){
        this.obj = obj;
    }

    //当我们通过代理类的对象，调用方法a时，就会自动的调用如下的方法：invoke()
    //将被代理类要执行的方法a的功能就声明在invoke()中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        HumanUtil util = new HumanUtil();
        util.method1();

        //method:即为代理类对象调用的方法，此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        Object returnValue = method.invoke(obj,args);

        util.method2();

        //上述方法的返回值就作为当前类中的invoke()的返回值。
        return returnValue;

    }
}

public class ProxyTest {

    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        //proxyInstance:代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用方法时，会自动的调用被代理类中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat(&quot;四川麻辣烫&quot;);

        System.out.println(&quot;*****************************&quot;);

        NikeClothFactory nikeClothFactory = new NikeClothFactory();

        ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);

        proxyClothFactory.produceCloth();

    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;动态代理与aopaspect-orient-programming&quot;&gt;动态代理与AOP（Aspect Orient Programming)&lt;/h3&gt;

&lt;p&gt;前面介绍的Proxy和InvocationHandler，很难看出这种动态代理的优势，下面介绍一种更实用的动态代理机制&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/DxoptJ.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/DxoSk4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;改进后的说明：代码段1、代码段2、代码段3和深色代码段分离开了，但代码段1、2、3又和一个特定的方法A耦合了！最理想的效果是：代码块1、2、3既可以执行方法A，又无须在程序中以硬编码的方式直接调用深色代码的方法&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	public interface Dog{
	void info();
	void run();
	}
	
	public class HuntingDog implements Dog{
	public void info(){
	System.out.println(&quot;我是一只猎狗&quot;);
	}
	public void run(){
	System.out.println(&quot;我奔跑迅速&quot;);
	} }
	
	public class DogUtil{
	public void method1(){
	System.out.println(&quot;=====模拟通用方法一=====&quot;);
	}
	public void method2(){
	System.out.println(&quot;=====模拟通用方法二=====&quot;);
	} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class MyInvocationHandler implements InvocationHandler{
// 需要被代理的对象
private Object target;
public void setTarget(Object target){
this.target = target;}
// 执行动态代理对象的所有方法时，都会被替换成执行如下的invoke方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Exception{
DogUtil du = new DogUtil();
// 执行DogUtil对象中的method1。
du.method1();
// 以target作为主调来执行method方法
Object result = method.invoke(target , args);
// 执行DogUtil对象中的method2。
du.method2();
return result;}}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class MyInvocationHandler implements InvocationHandler{
// 需要被代理的对象
private Object target;
public void setTarget(Object target){
this.target = target;}
// 执行动态代理对象的所有方法时，都会被替换成执行如下的invoke方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Exception{
DogUtil du = new DogUtil();
// 执行DogUtil对象中的method1。
du.method1();
// 以target作为主调来执行method方法
Object result = method.invoke(target , args);
// 执行DogUtil对象中的method2。
du.method2();
return result;}} 

public class Test{
public static void main(String[] args) 
throws Exception{
// 创建一个原始的HuntingDog对象，作为target
Dog target = new HuntingDog();
// 以指定的target来创建动态代理
Dog dog = (Dog)MyProxyFactory.getProxy(target);
dog.info();
dog.run();
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;使用Proxy生成一个动态代理时，往往并不会凭空产生一个动态代理，这样没有太大的意义。通常都是为指定的目标对象生成动态代理&lt;/li&gt;
  &lt;li&gt;这种动态代理在AOP中被称为AOP代理，AOP代理可代替目标对象，AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异：AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/DxIx7F.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;</content><author><name>WuJunwei</name></author><category term="notes" /><category term="java" /><category term="反射" /><summary type="html">Java反射机制概述;理解Class类并获取Class实例;类的加载与ClassLoader的理解;创建运行时类的对象;获取运行时类的完整结构;调用运行时类的指定结构;反射的应用：动态代理</summary></entry><entry><title type="html">Java集合</title><link href="https://wujunwei99.github.io/notes/2020/11/25/java-Collection-class.html" rel="alternate" type="text/html" title="Java集合" /><published>2020-11-25T10:27:31+00:00</published><updated>2020-11-25T10:27:31+00:00</updated><id>https://wujunwei99.github.io/notes/2020/11/25/java-Collection-class</id><content type="html" xml:base="https://wujunwei99.github.io/notes/2020/11/25/java-Collection-class.html">&lt;p&gt;Java集合框架概述；Collection接口方法；Iterator迭代器接口；List、Set；Map接口；Collections工具类&lt;/p&gt;

&lt;h2 id=&quot;java集合框架概述&quot;&gt;Java集合框架概述&lt;/h2&gt;

&lt;p&gt;集合、数组都是对多个数据进行存储操作的结构，简称&lt;strong&gt;Java容器&lt;/strong&gt;。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;说明：此时的存储，主要指的是内存层面的存储，不涉及到持久化的存储（.txt,.jpg,.avi，数据库中）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一方面， 面向对象语言对事物的体现都是以对象的形式，为了方便对多个对象的操作，就要对对象进行存储。另一方面，使用Array存储对象方面具有一些弊端，而Java 集合就像一种容器，可以动态地把多个对象的引用放入容器中。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;数组在内存存储方面的特点：
    &lt;ul&gt;
      &lt;li&gt;数组初始化以后，长度就确定了。&lt;/li&gt;
      &lt;li&gt;数组声明的类型，就决定了进行元素初始化时的类型&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;数组在存储数据方面的弊端：
    &lt;ul&gt;
      &lt;li&gt;数组初始化以后，长度就不可变了，不便于扩展&lt;/li&gt;
      &lt;li&gt;数组中提供的属性和方法少，不便于进行添加、删除、插入等操作，且效率不高。同时无法直接获取存储元素的个数&lt;/li&gt;
      &lt;li&gt;获取数组中实际元素的个数的需求，数组没有现成的属性或方法可用&lt;/li&gt;
      &lt;li&gt;数组存储的数据是有序的、可以重复的。—-&amp;gt;存储数据的特点单一&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Java 集合类可以用于存储数量不等的多个对象，还可用于保存具有映射关系的关联数组&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4Gqg.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;分类&quot;&gt;分类&lt;/h3&gt;

&lt;p&gt;Java 集合可分为 Collection 和 Map 两种体系&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Collection接口：单列数据，定义了存取一组对象的方法的集合
    &lt;ul&gt;
      &lt;li&gt;List：元素有序、可重复的集合–&amp;gt;“动态”数组
        &lt;ul&gt;
          &lt;li&gt;ArrayList、LinkedList、Vector&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Set：元素无序、不可重复的集合–&amp;gt;高中讲的“集合”
        &lt;ul&gt;
          &lt;li&gt;HashSet、LinkedHashSet、TreeSet&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Map接口：双列数据，保存具有映射关系“key-value对”的集合–&amp;gt;高中函数：y = f(x)
    &lt;ul&gt;
      &lt;li&gt;HashMap、LinkedHashMap、TreeMap、Hashtable、Properties&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4YZQ.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv48sS.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;collection接口方法&quot;&gt;Collection接口方法&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Collection 接口是 List、Set 和 Queue 接口的父接口，该接口里定义的方法既可用于操作 Set 集合，也可用于操作 List 和 Queue 集合。&lt;/li&gt;
  &lt;li&gt;JDK不提供此接口的任何直接实现，而是提供更具体的子接口(如：Set和List)实现。&lt;/li&gt;
  &lt;li&gt;在 Java5 之前，Java 集合会丢失容器中所有对象的数据类型，把所有对象都当成 Object 类型处理；从 JDK 5.0 增加了泛型以后，Java 集合可以记住容器中对象的数据类型。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;接口方法&quot;&gt;接口方法&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;添加
    &lt;ul&gt;
      &lt;li&gt;add(Object obj)&lt;/li&gt;
      &lt;li&gt;addAll(Collection coll)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;获取有效元素的个数
    &lt;ul&gt;
      &lt;li&gt;int size()&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;清空集合
    &lt;ul&gt;
      &lt;li&gt;void clear()&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;是否是空集合
    &lt;ul&gt;
      &lt;li&gt;boolean isEmpty()&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;是否包含某个元素
    &lt;ul&gt;
      &lt;li&gt;boolean contains(Object obj)：是通过元素的equals方法来判断是否是同一个对象&lt;/li&gt;
      &lt;li&gt;boolean containsAll(Collection c)：也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;删除
    &lt;ul&gt;
      &lt;li&gt;boolean remove(Object obj) ：通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素&lt;/li&gt;
      &lt;li&gt;boolean removeAll(Collection coll)：取当前集合的差集&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;取两个集合的交集
    &lt;ul&gt;
      &lt;li&gt;boolean retainAll(Collection c)：把交集的结果存在当前集合中，不影响c&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;集合是否相等
    &lt;ul&gt;
      &lt;li&gt;boolean equals(Object obj)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;转成对象数组
    &lt;ul&gt;
      &lt;li&gt;Object[] toArray()&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;获取集合对象的哈希值
    &lt;ul&gt;
      &lt;li&gt;hashCode()&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;遍历
    &lt;ul&gt;
      &lt;li&gt;iterator()：返回迭代器对象，用于集合遍历&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;iterator迭代器接口&quot;&gt;Iterator迭代器接口&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Iterator对象称为迭代器(设计模式的一种)，主要用于遍历 Collection 集合中的元素。&lt;/li&gt;
  &lt;li&gt;GOF给迭代器模式的定义为：提供一种方法访问一个容器(container)对象中各个元素，而又不需暴露该对象的内部细节。迭代器模式，就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。&lt;/li&gt;
  &lt;li&gt;Collection接口继承了java.lang.Iterable接口，该接口有一个iterator()方法，那么所有实现了Collection接口的集合类都有一个iterator()方法，用以返回一个实现了Iterator接口的对象。&lt;/li&gt;
  &lt;li&gt;Iterator 仅用于遍历集合，Iterator 本身并不提供承装对象的能力。如果需要创建Iterator 对象，则必须有一个被迭代的集合。&lt;/li&gt;
  &lt;li&gt;集合对象每次调用iterator()方法都得到一个全新的迭代器对象，默认游标都在集合的第一个元素之前。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;iterator接口的方法&quot;&gt;Iterator接口的方法&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv43M8.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4lxf.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用，且下一条记录无效，直接调用it.next()会抛出NoSuchElementException异常。&lt;/p&gt;

&lt;h3 id=&quot;迭代器的执行原理&quot;&gt;迭代器的执行原理&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4QRP.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;iterator接口remove方法&quot;&gt;Iterator接口remove()方法&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Iterator iter = coll.iterator();//回到起点
while(iter.hasNext()){
Object obj = iter.next();
if(obj.equals(&quot;Tom&quot;)){
iter.remove();
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Iterator可以删除集合的元素，但是是遍历过程中通过迭代器对象的remove方法，不是集合对象的remove方法。&lt;/li&gt;
  &lt;li&gt;如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法，再调用remove都会报IllegalStateException。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;使用-foreach-循环遍历集合元素&quot;&gt;使用 foreach 循环遍历集合元素&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Java 5.0 提供了 foreach 循环迭代访问 Collection和数组。&lt;/li&gt;
  &lt;li&gt;遍历操作不需获取Collection或数组的长度，无需使用索引访问元素。&lt;/li&gt;
  &lt;li&gt;遍历集合的底层调用Iterator完成操作。&lt;/li&gt;
  &lt;li&gt;foreach还可以用来遍历数组&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4MGt.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;collection子接口一list&quot;&gt;Collection子接口一：List&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;鉴于Java中数组用来存储数据的局限性，我们通常使用List替代数组&lt;/li&gt;
  &lt;li&gt;List集合类中元素有序、且可重复，集合中的每个元素都有其对应的顺序索引。&lt;/li&gt;
  &lt;li&gt;List容器中的元素都对应一个整数型的序号记载其在容器中的位置，可以根据序号存取容器中的元素。&lt;/li&gt;
  &lt;li&gt;JDK API中List接口的实现类常用的有：ArrayList、LinkedList和Vector。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;list接口方法&quot;&gt;List接口方法&lt;/h3&gt;

&lt;p&gt;List除了从Collection集合继承的方法外，List 集合里添加了一些根据索引来操作集合元素的方法。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;void add(int index, Object ele):在index位置插入ele元素&lt;/li&gt;
  &lt;li&gt;boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来&lt;/li&gt;
  &lt;li&gt;Object get(int index):获取指定index位置的元素&lt;/li&gt;
  &lt;li&gt;int indexOf(Object obj):返回obj在集合中首次出现的位置&lt;/li&gt;
  &lt;li&gt;int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置&lt;/li&gt;
  &lt;li&gt;Object remove(int index):移除指定index位置的元素，并返回此元素&lt;/li&gt;
  &lt;li&gt;Object set(int index, Object ele):设置指定index位置的元素为ele&lt;/li&gt;
  &lt;li&gt;List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;list实现类之一arraylist&quot;&gt;List实现类之一：ArrayList&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;ArrayList 是 List 接口的典型实现类、主要实现类&lt;/li&gt;
  &lt;li&gt;本质上，ArrayList是对象引用的一个”变长”数组&lt;/li&gt;
  &lt;li&gt;ArrayList的JDK1.8之前与之后的实现区别？
    &lt;ul&gt;
      &lt;li&gt;JDK1.7：ArrayList像饿汉式，直接创建一个初始容量为10的数组&lt;/li&gt;
      &lt;li&gt;JDK1.8：ArrayList像懒汉式，一开始创建一个长度为0的数组，当添加第一个元素时再创建一个始容量为10的数组&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Arrays.asList(…) 方法返回的 List 集合，既不是 ArrayList 实例，也不是Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;源码分析&quot;&gt;源码分析&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;jdk 7情况下&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;list.add(123);//elementData[0] = new Integer(123);&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
  &lt;li&gt;list.add(11);//如果此次的添加导致底层elementData数组容量不够，则扩容。&lt;/li&gt;
  &lt;li&gt;默认情况下，扩容为原来的容量的1.5倍，同时需要将原有数组中的数据复制到新的数组中。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;结论：建议开发中使用带参的构造器：ArrayList list = new ArrayList(int capacity)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;jdk 8中ArrayList的变化&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;list.add(123);//第一次调用add()时，底层才创建了长度10的数组，并将数据123添加到elementData[0]&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
  &lt;li&gt;后续的添加和扩容操作与jdk 7 无异。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;jdk7中的ArrayList的对象的创建类似于单例的饿汉式，而jdk8中的ArrayList的对象的创建类似于单例的懒汉式，&lt;strong&gt;延迟了数组的创建，节省内存。&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;list实现类之二linkedlist&quot;&gt;List实现类之二：LinkedList&lt;/h3&gt;

&lt;p&gt;对于频繁的插入或删除元素的操作，建议使用LinkedList类，效率较高&lt;/p&gt;

&lt;p&gt;新增方法：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;void addFirst(Object obj)  void addLast(Object obj)  Object getFirst()&lt;/li&gt;
  &lt;li&gt;Object getLast()&lt;/li&gt;
  &lt;li&gt;Object removeFirst()&lt;/li&gt;
  &lt;li&gt;Object removeLast()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LinkedList：双向链表，内部没有声明数组，而是定义了Node类型的first和last，用于记录首末元素。同时，定义内部类Node，作为LinkedList中保存数据的基本结构。Node除了保存数据，还定义了两个变量：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;prev变量记录前一个元素的位置&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;next变量记录下一个元素的位置&lt;/p&gt;

    &lt;p&gt;private static class Node&lt;E&gt; {
  E item;
  Node&lt;E&gt; next;
  Node&lt;E&gt; prev;
  Node(Node&lt;E&gt; prev, E element, Node&lt;E&gt; next) {
  this.item = element;
  this.next = next;
  this.prev = prev;
  } }&lt;/E&gt;&lt;/E&gt;&lt;/E&gt;&lt;/E&gt;&lt;/E&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4KPI.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;源码分析-1&quot;&gt;源码分析&lt;/h4&gt;

&lt;p&gt;LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性，默认值为null&lt;/p&gt;

&lt;p&gt;list.add(123);//将123封装到Node中，创建了Node对象。&lt;/p&gt;

&lt;h3 id=&quot;list-实现类之三vector&quot;&gt;List 实现类之三：Vector&lt;/h3&gt;

&lt;p&gt;Vector 是一个古老的集合，JDK1.0就有了。大多数操作与ArrayList相同，区别之处在于Vector是线程安全的。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;在各种list中，最好把ArrayList作为缺省选择。当插入、删除频繁时，使用LinkedList；Vector总是比ArrayList慢，所以尽量避免使用。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;新增方法：&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;void addElement(Object obj)  void insertElementAt(Object obj,int index)&lt;/li&gt;
      &lt;li&gt;void setElementAt(Object obj,int index)&lt;/li&gt;
      &lt;li&gt;void removeElement(Object obj)  void removeAllElements()&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;源码分析-2&quot;&gt;源码分析&lt;/h4&gt;

&lt;p&gt;jdk7和jdk8中通过Vector()构造器创建对象时，底层都创建了长度为10的数组。&lt;/p&gt;

&lt;p&gt;在扩容方面，默认扩容为原来的数组长度的2倍。&lt;/p&gt;

&lt;h3 id=&quot;面试题&quot;&gt;面试题&lt;/h3&gt;

&lt;h4 id=&quot;arraylistlinkedlistvector的异同&quot;&gt;ArrayList/LinkedList/Vector的异同&lt;/h4&gt;

&lt;p&gt;三个类都是实现了List接口，存储数据的特点相同：存储有序的、可重复的数据&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ArrayList：作为List接口的主要实现类；线程不安全的，效率高；底层使用Object[] elementData存储&lt;/li&gt;
  &lt;li&gt;LinkedList：对于频繁的插入、删除操作，使用此类效率比ArrayList高；底层使用双向链表存储&lt;/li&gt;
  &lt;li&gt;Vector：作为List接口的古老实现类；线程安全的，效率低；底层使用Object[] elementData存储&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;collection子接口二set&quot;&gt;Collection子接口二：Set&lt;/h2&gt;

&lt;h3 id=&quot;set-接口概述&quot;&gt;Set 接口概述&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Set接口是Collection的子接口，&lt;strong&gt;set接口没有提供额外的方法&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Set集合不允许包含相同的元素，如果试把两个相同的元素加入同一个Set 集合中，则添加操作失败。&lt;/li&gt;
  &lt;li&gt;Set判断两个对象是否相同不是使用 == 运算符，而是根据 equals() 方法&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;set存储无序的不可重复的数据&quot;&gt;Set：存储无序的、不可重复的数据&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;无序性：不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加，而是根据数据的哈希值决定的。&lt;/li&gt;
  &lt;li&gt;不可重复性：保证添加的元素按照equals()判断时，不能返回true.即：相同的元素只能添加一个。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;set接口的框架&quot;&gt;Set接口的框架&lt;/h3&gt;

&lt;p&gt;Collection接口：单列集合，用来存储一个一个的对象&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Set接口：存储无序的、不可重复的数据   –&amp;gt;高中讲的“集合”
    &lt;ul&gt;
      &lt;li&gt;HashSet：作为Set接口的主要实现类；线程不安全的；可以存储null值
 		* LinkedHashSet：作为HashSet的子类；遍历其内部数据时，可以按照添加的顺序遍历对于频繁的遍历操作，LinkedHashSet效率高于HashSet.&lt;/li&gt;
      &lt;li&gt;TreeSet：可以按照添加对象的指定属性，进行排序。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;set实现类之一hashset&quot;&gt;Set实现类之一：HashSet&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;HashSet 是 Set 接口的典型实现，大多数时候使用 Set 集合时都使用这个实现类。&lt;/li&gt;
  &lt;li&gt;HashSet 按 Hash 算法来存储集合中的元素，因此具有很好的存取、查找、删除性能。&lt;/li&gt;
  &lt;li&gt;HashSet 具有以下特点：
    &lt;ul&gt;
      &lt;li&gt;不能保证元素的排列顺序&lt;/li&gt;
      &lt;li&gt;HashSet 不是线程安全的&lt;/li&gt;
      &lt;li&gt;集合元素可以是 null&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;HashSet 集合判断两个元素相等的标准：两个对象通过 hashCode() 方法比较相等，并且两个对象的 equals() 方法返回值也相等。&lt;/li&gt;
  &lt;li&gt;对于存放在Set容器中的对象，对应的类一定要重写equals()和hashCode(Object obj)方法，以实现对象相等规则。即：“相等的对象必须具有相等的散列码”。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;向hashset中添加元素的过程&quot;&gt;向HashSet中添加元素的过程&lt;/h4&gt;

&lt;p&gt;当向 HashSet 集合中存入一个元素时，HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值，然后根据 hashCode 值，通过某种散列函数决定该对象在HashSet 底层数组中的存储位置。（这个散列函数会与底层数组的长度相计算得到在数组中的下标，并且这种散列函数计算还尽可能保证能均匀存储元素，越是散列分布，该散列函数设计的越好）&lt;/p&gt;

&lt;p&gt;如果两个元素的hashCode()值相等，会再继续调用equals方法，如果equals方法结果为true，添加失败；如果为false，那么会保存该元素，但是该数组的位置已经有元素了，那么会通过链表的方式继续链接。&lt;/p&gt;

&lt;p&gt;如果两个元素的 equals() 方法返回 true，但它们的 hashCode() 返回值不相等，hashSet 将会把它们存储在不同的位置，但依然可以添加成功。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4nIA.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;重写-hashcode-方法的基本原则&quot;&gt;重写 hashCode() 方法的基本原则&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;在程序运行时，同一个对象多次调用 hashCode() 方法应该返回相同的值。&lt;/li&gt;
  &lt;li&gt;当两个对象的 equals() 方法比较返回 true 时，这两个对象的 hashCode() 方法的返回值也应相等。&lt;/li&gt;
  &lt;li&gt;对象中用作 equals() 方法比较的 Field(属性)，都应该用来计算 hashCode 值。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;重写-equals-方法的基本原则&quot;&gt;重写 equals() 方法的基本原则&lt;/h4&gt;

&lt;p&gt;以自定义的Customer类为例，何时需要重写equals()？&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候，总是要改写hashCode()，根据一个类的equals方法（改写后），两个截然不同的实例有可能在逻辑上是相等的，但是，根据Object.hashCode()方法，它们仅仅是两个对象。&lt;/li&gt;
  &lt;li&gt;因此，违反了“相等的对象必须具有相等的散列码”。&lt;/li&gt;
  &lt;li&gt;结论：复写equals方法的时候一般都需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;eclipseidea工具里hashcode的重写&quot;&gt;Eclipse/IDEA工具里hashCode()的重写&lt;/h4&gt;

&lt;p&gt;以Eclipse/IDEA为例，在自定义类中可以调用工具自动重写equals和hashCode。&lt;/p&gt;

&lt;p&gt;问题：为什么用Eclipse/IDEA复写hashCode方法，有31这个数字？&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大，所谓的“冲突”就越少，查找起来效率也会提高。（减少冲突）&lt;/li&gt;
  &lt;li&gt;并且31只占用5bits,相乘造成数据溢出的概率较小。&lt;/li&gt;
  &lt;li&gt;31可以 由i*31== (i«5)-1来表示,现在很多虚拟机里面都有做相关优化。（提高算法效率）&lt;/li&gt;
  &lt;li&gt;31是一个素数，素数作用就是如果我用一个数字来乘以这个素数，那么最终出来的结果只能被素数本身和被乘数还有1来整除！(减少冲突)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;set实现类之二linkedhashset&quot;&gt;Set实现类之二：LinkedHashSet&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;LinkedHashSet 是 HashSet 的子类&lt;/li&gt;
  &lt;li&gt;LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置，但它同时使用双向链表维护元素的次序，这使得元素看起来是以插入顺序保存的。&lt;/li&gt;
  &lt;li&gt;LinkedHashSet插入性能略低于 HashSet，但在迭代访问 Set 里的全部元素时有很好的性能。&lt;/li&gt;
  &lt;li&gt;LinkedHashSet 不允许集合元素重复。&lt;/li&gt;
  &lt;li&gt;优点：对于频繁的遍历操作，LinkedHashSet效率高于HashSet&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;linkedhashset底层结构&quot;&gt;LinkedHashSet底层结构&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4mad.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;set实现类之三treeset&quot;&gt;Set实现类之三：TreeSet&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;TreeSet 是 SortedSet 接口的实现类，TreeSet 可以确保集合元素处于排序状态。&lt;/li&gt;
  &lt;li&gt;TreeSet底层使用红黑树结构存储数据&lt;/li&gt;
  &lt;li&gt;新增的方法如下： (了解)
    &lt;ul&gt;
      &lt;li&gt;Comparator comparator()&lt;/li&gt;
      &lt;li&gt;Object first()&lt;/li&gt;
      &lt;li&gt;Object last()&lt;/li&gt;
      &lt;li&gt;Object lower(Object e)&lt;/li&gt;
      &lt;li&gt;Object higher(Object e)&lt;/li&gt;
      &lt;li&gt;SortedSet subSet(fromElement, toElement)&lt;/li&gt;
      &lt;li&gt;SortedSet headSet(toElement)&lt;/li&gt;
      &lt;li&gt;SortedSet tailSet(fromElement)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;TreeSet 两种排序方法：自然排序和定制排序。默认情况下，TreeSet 采用自然排序。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4eVH.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;排-序自然排序&quot;&gt;排 序—自然排序&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;自然排序：TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系，然后将集合元素按升序(默认情况)排列&lt;/li&gt;
  &lt;li&gt;如果试图把一个对象添加到 TreeSet 时，则该对象的类必须实现 Comparable 接口。
    &lt;ul&gt;
      &lt;li&gt;实现 Comparable 的类必须实现 compareTo(Object obj) 方法，两个对象即通过compareTo(Object obj) 方法的返回值来比较大小。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Comparable 的典型实现：
    &lt;ul&gt;
      &lt;li&gt;BigDecimal、BigInteger 以及所有的数值型对应的包装类：按它们对应的数值大小进行比较&lt;/li&gt;
      &lt;li&gt;Character：按字符的 unicode值来进行比较&lt;/li&gt;
      &lt;li&gt;Boolean：true 对应的包装类实例大于 false 对应的包装类实例&lt;/li&gt;
      &lt;li&gt;String：按字符串中字符的 unicode 值进行比较&lt;/li&gt;
      &lt;li&gt;Date、Time：后边的时间、日期比前面的时间、日期大&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;向 TreeSet 中添加元素时，只有第一个元素无须比较compareTo()方法，后面添加的所有元素都会调用compareTo()方法进行比较。&lt;/li&gt;
  &lt;li&gt;因为只有相同类的两个实例才会比较大小，所以&lt;strong&gt;向 TreeSet 中添加的应该是同一个类的对象&lt;/strong&gt;。&lt;/li&gt;
  &lt;li&gt;对于 TreeSet 集合而言，它判断两个对象是否相等的唯一标准是：两个对象通过 compareTo(Object obj) 方法比较返回值。&lt;/li&gt;
  &lt;li&gt;当需要把一个对象放入 TreeSet 中，重写该对象对应的 equals() 方法时，应保证该方法与 compareTo(Object obj) 方法有一致的结果：如果两个对象通过equals() 方法比较返回 true，则通过 compareTo(Object obj) 方法比较应返回 0。否则，让人难以理解。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;排-序定制排序&quot;&gt;排 序—定制排序&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;TreeSet的自然排序要求元素所属的类实现Comparable接口，如果元素所属的类没有实现Comparable接口，或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序，则考虑使用定制排序。定制排序，通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。&lt;/li&gt;
  &lt;li&gt;利用int compare(T o1,T o2)方法，比较o1和o2的大小：如果方法返回正整数，则表示o1大于o2；如果返回0，表示相等；返回负整数，表示o1小于o2。&lt;/li&gt;
  &lt;li&gt;要实现定制排序，需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。&lt;/li&gt;
  &lt;li&gt;此时，仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异常。&lt;/li&gt;
  &lt;li&gt;使用定制排序判断两个元素相等的标准是：通过Comparator比较两个元素返回了0。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;map接口&quot;&gt;Map接口&lt;/h2&gt;

&lt;h3 id=&quot;实现结构&quot;&gt;实现结构&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Map:双列数据，存储key-value对的数据   —类似于高中的函数：y = f(x)
    &lt;ul&gt;
      &lt;li&gt;HashMap:作为Map的主要实现类；线程不安全的，效率高；存储null的key和value
        &lt;ul&gt;
          &lt;li&gt;LinkedHashMap:保证在遍历map元素时，可以按照添加的顺序实现遍历。&lt;/li&gt;
          &lt;li&gt;原因：在原有的HashMap底层结构基础上，添加了一对指针，指向前一个和后一个元素。对于频繁的遍历操作，此类执行效率高于HashMap。&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;TreeMap:保证按照添加的key-value对进行排序，实现排序遍历。此时考虑key的自然排序或定制排序底层使用红黑树&lt;/li&gt;
      &lt;li&gt;Hashtable:作为古老的实现类；线程安全的，效率低；不能存储null的key和value
        &lt;ul&gt;
          &lt;li&gt;Properties:常用来处理配置文件。key和value都是String类型&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4Vqe.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;map接口概述&quot;&gt;Map接口概述&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Map与Collection并列存在。用于保存具有&lt;strong&gt;映射关系&lt;/strong&gt;的数据:key-value&lt;/li&gt;
  &lt;li&gt;Map 中的 key 和 value 都可以是任何引用类型的数据&lt;/li&gt;
  &lt;li&gt;Map 中的 &lt;strong&gt;key 用Set来存放，不允许重复&lt;/strong&gt;，即同一个 Map 对象所对应的类，须重写hashCode()和equals()方法&lt;/li&gt;
  &lt;li&gt;常用String类作为Map的“键”&lt;/li&gt;
  &lt;li&gt;key 和 value 之间存在单向一对一关系，即通过指定的 key 总能找到唯一的、确定的 value&lt;/li&gt;
  &lt;li&gt;Map接口的常用实现类：HashMap、TreeMap、LinkedHashMap和Properties。其中，&lt;strong&gt;HashMap是 Map 接口使用频率最高的实现类&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4AKO.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;hashmap的存储结构&quot;&gt;HashMap的存储结构&lt;/h3&gt;

&lt;p&gt;JDK 7及以前版本：HashMap是数组+链表结构(即为链地址法)&lt;/p&gt;

&lt;p&gt;JDK 8版本发布以后：HashMap是数组+链表+红黑树实现。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4NIs.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4taj.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;map结构的理解&quot;&gt;Map结构的理解：&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Map中的key:无序的、不可重复的，使用Set存储所有的key  —&amp;gt;key所在的类要重写equals()和hashCode() （以HashMap为例）&lt;/li&gt;
  &lt;li&gt;Map中的value:无序的、可重复的，使用Collection存储所有的value —&amp;gt;value所在的类要重写equals()&lt;/li&gt;
  &lt;li&gt;一个键值对：key-value构成了一个Entry对象。&lt;/li&gt;
  &lt;li&gt;Map中的entry:无序的、不可重复的，使用Set存储所有的entry&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;map接口常用方法&quot;&gt;Map接口：常用方法&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;添加、删除、修改操作：
    &lt;ul&gt;
      &lt;li&gt;Object put(Object key,Object value)：将指定key-value添加到(或修改)当前map对象中&lt;/li&gt;
      &lt;li&gt;void putAll(Map m):将m中的所有key-value对存放到当前map中&lt;/li&gt;
      &lt;li&gt;Object remove(Object key)：移除指定key的key-value对，并返回value&lt;/li&gt;
      &lt;li&gt;void clear()：清空当前map中的所有数据&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;元素查询的操作：
    &lt;ul&gt;
      &lt;li&gt;Object get(Object key)：获取指定key对应的value&lt;/li&gt;
      &lt;li&gt;boolean containsKey(Object key)：是否包含指定的key&lt;/li&gt;
      &lt;li&gt;boolean containsValue(Object value)：是否包含指定的value&lt;/li&gt;
      &lt;li&gt;int size()：返回map中key-value对的个数&lt;/li&gt;
      &lt;li&gt;boolean isEmpty()：判断当前map是否为空&lt;/li&gt;
      &lt;li&gt;boolean equals(Object obj)：判断当前map和参数对象obj是否相等&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;元视图操作的方法：
    &lt;ul&gt;
      &lt;li&gt;Set keySet()：返回所有key构成的Set集合&lt;/li&gt;
      &lt;li&gt;Collection values()：返回所有value构成的Collection集合&lt;/li&gt;
      &lt;li&gt;Set entrySet()：返回所有key-value对构成的Set集合&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;hashmap的底层实现原理&quot;&gt;HashMap的底层实现原理&lt;/h3&gt;

&lt;h4 id=&quot;jdk7为例&quot;&gt;jdk7为例&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;HashMap map = new HashMap():&lt;/li&gt;
  &lt;li&gt;在实例化以后，底层创建了长度是16的一维数组Entry[] table。&lt;/li&gt;
  &lt;li&gt;…可能已经执行过多次put…&lt;/li&gt;
  &lt;li&gt;map.put(key1,value1):&lt;/li&gt;
  &lt;li&gt;首先，调用key1所在类的hashCode()计算key1哈希值，此哈希值经过某种算法计算以后，得到在Entry数组中的存放位置。
    &lt;ul&gt;
      &lt;li&gt;如果此位置上的数据为空，此时的key1-value1添加成功。 —-情况1&lt;/li&gt;
      &lt;li&gt;如果此位置上的数据不为空，(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值：
        &lt;ul&gt;
          &lt;li&gt;如果key1的哈希值与已经存在的数据的哈希值都不相同，此时key1-value1添加成功。—-情况2&lt;/li&gt;
          &lt;li&gt;如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同，继续比较：调用key1所在类的equals(key2)方法，比较：
            &lt;ul&gt;
              &lt;li&gt;如果equals()返回false:此时key1-value1添加成功。—-情况3&lt;/li&gt;
              &lt;li&gt;如果equals()返回true:使用value1替换value2。&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;补充：关于情况2和情况3：此时key1-value1和原来的数据以链表的方式存储。&lt;/p&gt;

&lt;p&gt;在不断的添加过程中，会涉及到扩容问题，当超出临界值(且要存放的位置非空)时，扩容。默认的扩容方式：扩容为原来容量的2倍，并将原有的数据复制过来。&lt;/p&gt;

&lt;hr /&gt;

&lt;ul&gt;
  &lt;li&gt;HashMap的内部存储结构其实是数组和链表的结合。当实例化一个HashMap时，系统会创建一个长度为Capacity的Entry数组，这个长度在哈希表中被称为容量(Capacity)，在这个数组中可以存放元素的位置我们称之为“桶”(bucket)，每个bucket都有自己的索引，系统可以根据索引快速的查找bucket中的元素。&lt;/li&gt;
  &lt;li&gt;每个bucket中存储一个元素，即一个Entry对象，但每一个Entry对象可以带一个引用变量，用于指向下一个元素，因此，在一个桶中，就有可能生成一个Entry链。而且新添加的元素作为链表的head。&lt;/li&gt;
  &lt;li&gt;添加元素的过程：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;向HashMap中添加entry1(key，value)，需要首先计算entry1中key的哈希值(根据key所在类的hashCode()计算得到)，此哈希值经过处理以后，得到在底层Entry[]数组中要存储的位置i。如果位置i上没有元素，则entry1直接添加成功。如果位置i上已经存在entry2(或还有链表存在的entry3，entry4)，则需要通过循环的方法，依次比较entry1中key和其他的entry。如果彼此hash值不同，则直接添加成功。如果hash值不同，继续比较二者是否equals。如果返回值为true，则使用entry1的value去替换equals为true的entry的value。如果遍历一遍以后，发现所有的equals返回都为false,则entry1仍可添加成功。entry1指向原有的entry元素。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;HashMap的扩容&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;当HashMap中的元素越来越多的时候，hash冲突的几率也就越来越高，因为数组的长度是固定的。所以为了提高查询的效率，就要对HashMap的数组进行扩容，而在HashMap数组扩容之后，最消耗性能的点就出现了：原数组中的数据必须重新计算其在新数组中的位置，并放进去，这就是resize。那么HashMap什么时候进行扩容呢？ 当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)&lt;em&gt;loadFactor 时 ， 就 会 进 行 数 组 扩 容 ， loadFactor 的默认 值 (DEFAULT_LOAD_FACTOR)为0.75，这是一个折中的取值。也就是说，默认情况下，数组大小(DEFAULT_INITIAL_CAPACITY)为16，那么当HashMap中元素个数超过16&lt;/em&gt;0.75=12（这个值就是代码中的threshold值，也叫做临界值）的时候，就把数组的大小扩展为 2*16=32，即扩大一倍，然后重新计算每个元素在数组中的位置，而这是一个非常消耗性能的操作，所以如果我们已经预知HashMap中元素的个数，那么预设元素的个数能够有效的提高HashMap的性能&lt;/p&gt;

&lt;h4 id=&quot;jdk8-相较于jdk7在底层实现方面的不同&quot;&gt;jdk8 相较于jdk7在底层实现方面的不同&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;new HashMap():底层没有创建一个长度为16的数组&lt;/li&gt;
  &lt;li&gt;jdk 8底层的数组是：Node[],而非Entry[]&lt;/li&gt;
  &lt;li&gt;首次调用put()方法时，底层创建长度为16的数组&lt;/li&gt;
  &lt;li&gt;jdk7底层结构只有：数组+链表。jdk8中底层结构：数组+链表+红黑树。
    &lt;ol&gt;
      &lt;li&gt;形成链表时，七上八下（jdk7:新的元素指向旧的元素。jdk8：旧的元素指向新的元素）&lt;/li&gt;
      &lt;li&gt;当数组的某一个索引位置上的元素以链表形式存在的数据个数 &amp;gt; 8 且当前数组的长度 &amp;gt; 64时，此时此索引位置上的所数据改为使用红黑树存储。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
  &lt;li&gt;DEFAULT_INITIAL_CAPACITY : HashMap的默认容量，16&lt;/li&gt;
  &lt;li&gt;DEFAULT_LOAD_FACTOR：HashMap的默认加载因子：0.75&lt;/li&gt;
  &lt;li&gt;threshold：扩容的临界值，=容量*填充因子：16 * 0.75 =&amp;gt; 12&lt;/li&gt;
  &lt;li&gt;TREEIFY_THRESHOLD：Bucket中链表长度大于该默认值，转化为红黑树:8&lt;/li&gt;
  &lt;li&gt;MIN_TREEIFY_CAPACITY：桶中的Node被树化时最小的hash表容量:64&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;ul&gt;
  &lt;li&gt;HashMap的内部存储结构其实是&lt;strong&gt;数组+链表+树&lt;/strong&gt;的结合。当实例化一个HashMap时，会初始化initialCapacityloadFactor，在put第一对映射关系时，系统会创建一个长度为initialCapacity的Node数组，这个长度在哈希表中被称为容量(Capacity)，在这个数组中可以存放元素的位置我们称之为“桶”(bucket)，每个bucket都有自己的索引，系统可以根据索引快速的查找bucket中的元素。&lt;/li&gt;
  &lt;li&gt;每个bucket中存储一个元素，即一个Node对象，但每一个Node对象可以带一个引用变量next，用于指向下一个元素，因此，在一个桶中，就有可能生成一个Node链。也可能是一个一个TreeNode对象，每一个TreeNode对象可以有两个叶子结点left和right，因此，在一个桶中，就有可能生成一个TreeNode树。而新添加的元素作为链表的last，或树的叶子结点。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;那么HashMap什么时候进行扩容和树形化呢？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)&lt;em&gt;loadFactor 时 ， 就会进行数组扩容 ，loadFactor 的默认 值 (DEFAULT_LOAD_FACTOR)为0.75，这是一个折中的取值。也就是说，默认情况下，数组大小(DEFAULT_INITIAL_CAPACITY)为16，那么当HashMap中元素个数超过16&lt;/em&gt;0.75=12（这个值就是代码中的threshold值，也叫做临界值）的时候，就把数组的大小扩展为 2*16=32，即扩大一倍，然后重新计算每个元素在数组中的位置，而这是一个非常消耗性能的操作，所以如果我们已经预知HashMap中元素的个数，那么预设元素的个数能够有效的提高HashMap的性能。&lt;/p&gt;

&lt;p&gt;当HashMap中的其中一个链的对象个数如果达到了8个，此时如果capacity没有达到64，那么HashMap会先扩容解决，如果已经达到了64，那么这个链会变成树，结点类型由Node变成TreeNode类型。当然，如果当映射关系被移除后，下次resize方法时判断树的结点个数低于6个，也会把树再转为链表&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;关于映射关系的key是否可以修改？&lt;/strong&gt;answer：不要修改&lt;/p&gt;

&lt;p&gt;映射关系存储到HashMap中会存储key的hash值，这样就不用在每次查找时重新计算每一个Entry或Node（TreeNode）的hash值了，因此如果已经put到Map中的映射关系，再修改key的属性，而这个属性又参与hashcode值的计算，那么会导致匹配不上。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;总结：JDK1.8相较于之前的变化：&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;HashMap map = new HashMap();//默认情况下，先不创建长度为16的数组&lt;/li&gt;
  &lt;li&gt;当首次调用map.put()时，再创建长度为16的数组&lt;/li&gt;
  &lt;li&gt;数组为Node类型，在jdk7中称为Entry类型&lt;/li&gt;
  &lt;li&gt;形成链表结构时，新添加的key-value对在链表的尾部（七上八下）&lt;/li&gt;
  &lt;li&gt;当数组指定索引位置的链表长度&amp;gt;8时，且map中的数组的长度&amp;gt; 64时，此索引位置上的所有key-value对使用红黑树进行存储。&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;hashmap源码中的重要常量&quot;&gt;HashMap源码中的重要常量&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;DEFAULT_INITIAL_CAPACITY : HashMap的默认容量，16&lt;/li&gt;
  &lt;li&gt;MAXIMUM_CAPACITY ： HashMap的最大支持容量，2^30&lt;/li&gt;
  &lt;li&gt;DEFAULT_LOAD_FACTOR：HashMap的默认加载因子&lt;/li&gt;
  &lt;li&gt;TREEIFY_THRESHOLD：Bucket中链表长度大于该默认值，转化为红黑树&lt;/li&gt;
  &lt;li&gt;UNTREEIFY_THRESHOLD：Bucket中红黑树存储的Node小于该默认值，转化为链表&lt;/li&gt;
  &lt;li&gt;MIN_TREEIFY_CAPACITY：桶中的Node被树化时最小的hash表容量。（当桶中Node的&lt;/li&gt;
  &lt;li&gt;数量大到需要变红黑树时，若hash表容量小于MIN_TREEIFY_CAPACITY时，此时应执行&lt;/li&gt;
  &lt;li&gt;resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。）&lt;/li&gt;
  &lt;li&gt;table：存储元素的数组，总是2的n次幂&lt;/li&gt;
  &lt;li&gt;entrySet：存储具体元素的集&lt;/li&gt;
  &lt;li&gt;size：HashMap中存储的键值对的数量&lt;/li&gt;
  &lt;li&gt;modCount：HashMap扩容和结构改变的次数。&lt;/li&gt;
  &lt;li&gt;threshold：扩容的临界值，=容量*填充因子&lt;/li&gt;
  &lt;li&gt;loadFactor：填充因子&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;负载因子值的大小，对HashMap有什么影响&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;负载因子的大小决定了HashMap的数据密度。&lt;/li&gt;
  &lt;li&gt;负载因子越大密度越大，发生碰撞的几率越高，数组中的链表越容易长,造成查询或插入时的比较次数增多，性能会下降。&lt;/li&gt;
  &lt;li&gt;负载因子越小，就越容易触发扩容，数据密度也越小，意味着发生碰撞的几率越小，数组中的链表也就越短，查询和插入时比较的次数也越小，性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能，建议初始化预设大一点的空间。&lt;/li&gt;
  &lt;li&gt;按照其他语言的参考及研究经验，会考虑将负载因子设置为0.7~0.75，此时平均检索长度接近于常数&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;map实现类之二linkedhashmap&quot;&gt;Map实现类之二：LinkedHashMap&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;LinkedHashMap 是 HashMap 的子类&lt;/li&gt;
  &lt;li&gt;在HashMap存储结构的基础上，使用了一对双向链表来记录添加元素的顺序&lt;/li&gt;
  &lt;li&gt;与LinkedHashSet类似，LinkedHashMap 可以维护 Map 的迭代顺序：迭代顺序与 Key-Value 对的插入顺序一致&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;hashmap中的内部类node&quot;&gt;HashMap中的内部类：Node&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;static class Node&amp;lt;K,V&amp;gt; implements Map.Entry&amp;lt;K,V&amp;gt; {
final int hash;
final K key;
V value;
Node&amp;lt;K,V&amp;gt; next; }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;linkedhashmap中的内部类entry&quot;&gt;LinkedHashMap中的内部类：Entry&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;static class Entry&amp;lt;K,V&amp;gt; extends HashMap.Node&amp;lt;K,V&amp;gt; {
Entry&amp;lt;K,V&amp;gt; before, after;
Entry(int hash, K key, V value, Node&amp;lt;K,V&amp;gt; next) {
super(hash, key, value, next);
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;map实现类之三treemap&quot;&gt;Map实现类之三：TreeMap&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;TreeMap存储 Key-Value 对时，需要根据 key-value 对进行排序。&lt;/li&gt;
  &lt;li&gt;TreeMap 可以保证所有的 Key-Value 对处于有序状态。&lt;/li&gt;
  &lt;li&gt;TreeSet底层使用红黑树结构存储数据&lt;/li&gt;
  &lt;li&gt;TreeMap 的 Key 的排序：
    &lt;ul&gt;
      &lt;li&gt;自然排序：TreeMap 的所有的 Key 必须实现 Comparable 接口，而且所有的 Key 应该是同一个类的对象，否则将会抛出 ClasssCastException&lt;/li&gt;
      &lt;li&gt;定制排序：创建 TreeMap 时，传入一个 Comparator 对象，该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;TreeMap判断两个key相等的标准：两个key通过compareTo()方法或者compare()方法返回0&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;map实现类之四hashtable&quot;&gt;Map实现类之四：Hashtable&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Hashtable是个古老的 Map 实现类，JDK1.0就提供了。不同于HashMap，Hashtable是线程安全的。&lt;/li&gt;
  &lt;li&gt;Hashtable实现原理和HashMap相同，功能相同。底层都使用哈希表结构，查询速度快，很多情况下可以互用。  与HashMap不同，Hashtable 不允许使用 null 作为 key 和value&lt;/li&gt;
  &lt;li&gt;与HashMap一样，Hashtable 也不能保证其中 Key-Value 对的顺序&lt;/li&gt;
  &lt;li&gt;Hashtable判断两个key相等、两个value相等的标准，与HashMap一致。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;map实现类之五properties&quot;&gt;Map实现类之五：Properties&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Properties 类是 Hashtable 的子类，该对象用于处理属性文件&lt;/li&gt;
  &lt;li&gt;由于属性文件里的 key、value 都是字符串类型，所以 Properties 里的 key和 value 都是字符串类型&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;存取数据时，建议使用setProperty(String key,String value)方法和getProperty(String key)方法&lt;/p&gt;

    &lt;p&gt;Properties pros = new Properties();
  pros.load(new FileInputStream(“jdbc.properties”));
  String user = pros.getProperty(“user”);
  System.out.println(user);&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;collections工具类&quot;&gt;Collections工具类&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Collections 是一个操作 Set、List 和 Map 等集合的工具类&lt;/li&gt;
  &lt;li&gt;Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作，还提供了对集合对象设置不可变、对集合对象实现同步控制等方法&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;collections常用方法&quot;&gt;Collections常用方法&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;排序操作：（均为static方法）
    &lt;ul&gt;
      &lt;li&gt;reverse(List)：反转 List 中元素的顺序&lt;/li&gt;
      &lt;li&gt;shuffle(List)：对 List 集合元素进行随机排序&lt;/li&gt;
      &lt;li&gt;sort(List)：根据元素的自然顺序对指定 List 集合元素按升序排序&lt;/li&gt;
      &lt;li&gt;sort(List，Comparator)：根据指定的 Comparator 产生的顺序对 List 集合元素进行排序&lt;/li&gt;
      &lt;li&gt;swap(List，int， int)：将指定 list 集合中的 i 处元素和 j 处元素进行交换&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;查找、替换
    &lt;ul&gt;
      &lt;li&gt;Object max(Collection)：根据元素的自然顺序，返回给定集合中的最大元素&lt;/li&gt;
      &lt;li&gt;Object max(Collection，Comparator)：根据 Comparator 指定的顺序，返回给定集合中的最大元素&lt;/li&gt;
      &lt;li&gt;Object min(Collection)&lt;/li&gt;
      &lt;li&gt;Object min(Collection，Comparator)&lt;/li&gt;
      &lt;li&gt;int frequency(Collection，Object)：返回指定集合中指定元素的出现次数&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;void copy(List dest,List src)：将src中的内容复制到dest中 boolean replaceAll(List list，Object oldVal，Object newVal)：使用新值替换List 对象的所有旧值&lt;/p&gt;

        &lt;p&gt;//报异常：IndexOutOfBoundsException(“Source does not fit in dest”)
  //        List dest = new ArrayList();
  //        Collections.copy(dest,list);
  //正确的：
  List dest = Arrays.asList(new Object[list.size()]);
  System.out.println(dest.size());//list.size();
  Collections.copy(dest,list);&lt;/p&gt;

        &lt;p&gt;System.out.println(dest);&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;collections常用方法同步控制&quot;&gt;Collections常用方法：同步控制&lt;/h3&gt;

&lt;p&gt;Collections 类中提供了多个 synchronizedXxx() 方法，该方法可使将指定集合包装成线程同步的集合，从而可以解决多线程并发访问集合时的线程安全问题&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    //返回的list1即为线程安全的List
    List list1 = Collections.synchronizedList(list);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4rsU.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;补充enumeration&quot;&gt;补充：Enumeration&lt;/h3&gt;

&lt;p&gt;Enumeration 接口是 Iterator 迭代器的 “古老版本”&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/07/Dv4DMT.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Enumeration stringEnum = new StringTokenizer(“a-b*c-d-e-g”, “-“);
while(stringEnum.hasMoreElements()){
Object obj = stringEnum.nextElement();
System.out.println(obj); 
}&lt;/p&gt;</content><author><name>WuJunwei</name></author><category term="notes" /><category term="java" /><summary type="html">Java集合框架概述；Collection接口方法；Iterator迭代器接口；List、Set；Map接口；Collections工具类</summary></entry><entry><title type="html">Java泛型</title><link href="https://wujunwei99.github.io/notes/2020/11/23/java-Generic.html" rel="alternate" type="text/html" title="Java泛型" /><published>2020-11-23T05:39:41+00:00</published><updated>2020-11-23T05:39:41+00:00</updated><id>https://wujunwei99.github.io/notes/2020/11/23/java-Generic</id><content type="html" xml:base="https://wujunwei99.github.io/notes/2020/11/23/java-Generic.html">&lt;p&gt;出现泛型的原因；在集合中使用泛型；自定义泛型结构；泛型在继承上的体现；通配符的使用；泛型应用举例&lt;/p&gt;

&lt;h2 id=&quot;出现泛型的原因&quot;&gt;出现泛型的原因&lt;/h2&gt;

&lt;p&gt;泛型：标签&lt;/p&gt;

&lt;p&gt;举例：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;中药店，每个抽屉外面贴着标签&lt;/li&gt;
  &lt;li&gt;超市购物架上很多瓶子，每个瓶子装的是什么，有标签&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;泛型的设计背景&quot;&gt;泛型的设计背景&lt;/h4&gt;

&lt;p&gt;集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象，所以在JDK1.5之前只能把元素类型设计为Object，JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定，其他的部分是确定的，例如关于这个元素如何保存，如何管理等是确定的，因此此时&lt;strong&gt;把元素的类型设计成一个参数，这个类型参数叫做泛型&lt;/strong&gt;。Collection&lt;E&gt;，List&lt;E&gt;，ArrayList&lt;E&gt; 这个&lt;E&gt;就是类型参数，即泛型&lt;/E&gt;&lt;/E&gt;&lt;/E&gt;&lt;/E&gt;&lt;/p&gt;

&lt;h3 id=&quot;泛型的概念&quot;&gt;泛型的概念&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;所谓泛型，就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时（例如，继承或实现这个接口，用这个类型声明变量、创建对象时）确定（即传入实际的类型参数，也称为类型实参）。&lt;/li&gt;
  &lt;li&gt;从JDK1.5以后，Java引入了“参数化类型（Parameterized type）”的概念，允许我们在创建集合时再指定集合元素的类型，正如：List&lt;String&gt;，这表明该List只能保存字符串类型的对象。&lt;/String&gt;&lt;/li&gt;
  &lt;li&gt;JDK1.5改写了集合框架中的全部接口和类，为这些接口、类增加了泛型支持，从而可以在声明集合变量、创建集合对象时传入类型实参。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;那么为什么要有泛型呢直接object不是也可以存储数据吗&quot;&gt;那么为什么要有泛型呢，直接Object不是也可以存储数据吗？&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;解决元素存储的安全性问题，好比商品、药品标签，不会弄错。&lt;/li&gt;
  &lt;li&gt;解决获取数据元素时，需要类型强制转换的问题，好比不用每回拿商品、药品都要辨别&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/08/rSNnr6.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/08/rSNmKx.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Java泛型可以保证如果程序在编译时没有发出警告，运行时就不会产生ClassCastException异常。同时，代码更加简洁、健壮。&lt;/p&gt;

&lt;h2 id=&quot;在集合中使用泛型&quot;&gt;在集合中使用泛型&lt;/h2&gt;

&lt;p&gt;总结：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。&lt;/li&gt;
  &lt;li&gt;② 在实例化集合类时，可以指明具体的泛型类型&lt;/li&gt;
  &lt;li&gt;③ 指明完以后，在集合类或接口中凡是定义类或接口时，内部结构（比如：方法、构造器、属性等）使用到类的泛型的位置，都指定为实例化的泛型类型。比如：add(E e)  —&amp;gt;实例化以后：add(Integer e)&lt;/li&gt;
  &lt;li&gt;④ 注意点：泛型的类型必须是类，不能是基本数据类型。需要用到基本数据类型的位置，拿包装类替换&lt;/li&gt;
  &lt;li&gt;⑤ 如果实例化时，没有指明泛型的类型。默认类型为java.lang.Object类型。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ArrayList&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();//类型推断
list.add(78);
list.add(88);
list.add(77);
list.add(66);
//遍历方式一：
//for(Integer i : list){
//不需要强转
//System.out.println(i);
//}
//遍历方式二：
Iterator&amp;lt;Integer&amp;gt; iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Map&amp;lt;String,Integer&amp;gt; map = new HashMap&amp;lt;String,Integer&amp;gt;();
map.put(&quot;Tom1&quot;,34);
map.put(&quot;Tom2&quot;,44);
map.put(&quot;Tom3&quot;,33);
map.put(&quot;Tom4&quot;,32);
//添加失败
//map.put(33, &quot;Tom&quot;);
Set&amp;lt;Entry&amp;lt;String,Integer&amp;gt;&amp;gt; entrySet = map.entrySet();
Iterator&amp;lt;Entry&amp;lt;String,Integer&amp;gt;&amp;gt; iterator = entrySet.iterator();
while(iterator.hasNext()){
Entry&amp;lt;String,Integer&amp;gt; entry = iterator.next();
System.out.println(entry.getKey() + &quot;---&amp;gt;&quot; + entry.getValue());
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;自定义泛型结构&quot;&gt;自定义泛型结构&lt;/h2&gt;

&lt;p&gt;1.泛型的声明&lt;/p&gt;

&lt;p&gt;interface List&lt;T&gt; 和 class GenTest&amp;lt;K,V&amp;gt;&lt;/T&gt;&lt;/p&gt;

&lt;p&gt;其中，T,K,V不代表值，而是表示类型。这里使用任意字母都可以。&lt;/p&gt;

&lt;p&gt;常用T表示，是Type的缩写。&lt;/p&gt;

&lt;p&gt;2.泛型的实例化：&lt;/p&gt;

&lt;p&gt;一定要在类名后面指定类型参数的值（类型）。如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; strList = new ArrayList&amp;lt;String&amp;gt;();

Iterator&amp;lt;Customer&amp;gt; iterator = customers.iterator();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;T只能是类，不能用基本数据类型填充。但可以使用包装类填充&lt;/li&gt;
  &lt;li&gt;把一个集合中的内容限制为一个特定的数据类型，这就是generics背后的核心思想&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JDK 1.5 之前:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Comparable c = new Date();
System.out.println(c.compareTo(&quot;red&quot;));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;JDK 1.5:&lt;/p&gt;

&lt;p&gt;Comparable&lt;Date&gt; c = new Date();
System.out.println(c.compareTo(&quot;red&quot;));&lt;/Date&gt;&lt;/p&gt;

&lt;p&gt;体会：使用泛型的主要优点是能够在编译时而不是在运行时检测错误。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;泛型类可能有多个参数，此时应将多个参数一起放在尖括号内。比如：&amp;lt;E1,E2,E3&amp;gt;&lt;/li&gt;
  &lt;li&gt;泛型类的构造器如下：public GenericClass(){}。而下面是错误的：public GenericClass&lt;E&gt;(){}&lt;/E&gt;&lt;/li&gt;
  &lt;li&gt;实例化后，操作原来泛型位置的结构必须与指定的泛型类型一致。&lt;/li&gt;
  &lt;li&gt;泛型不同的引用不能相互赋值。尽管在编译时ArrayList&lt;String&gt;和ArrayList&lt;Integer&gt;是两种类型，但是，在运行时只有一个ArrayList被加载到JVM中。&lt;/Integer&gt;&lt;/String&gt;&lt;/li&gt;
  &lt;li&gt;泛型如果不指定，将被擦除，泛型对应的类型均按照Object处理，但不等价于Object。经验：泛型要使用一路都用。要不用，一路都不要用。&lt;/li&gt;
  &lt;li&gt;如果泛型结构是一个接口或抽象类，则不可创建泛型类的对象。&lt;/li&gt;
  &lt;li&gt;jdk1.7，泛型的简化操作：ArrayList&lt;Fruit&gt; flist = new ArrayList&amp;lt;&amp;gt;();&lt;/Fruit&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;泛型的指定中不能使用基本数据类型，可以使用包装类替换。&lt;/p&gt;

    &lt;p&gt;class GenericTest {
 public static void main(String[] args) {
 // 1、使用时：类似于Object，不等同于Object
 ArrayList list = new ArrayList();
 // list.add(new Date());//有风险
 list.add(“hello”);
 test(list);// 泛型擦除，编译不会类型检查
 // ArrayList&lt;object&gt; list2 = new ArrayList&lt;object&gt;();
 // test(list2);//一旦指定Object，编译会类型检查，必须按照Object处理
 }
 public static void test(ArrayList&lt;String&gt; list) {
 String str = &quot;&quot;;
 for (String s : list) {
 str += s + &quot;,&quot;; }
 System.out.println(&quot;元素:&quot; + str);
 } }&lt;/String&gt;&lt;/object&gt;&lt;/object&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;在类/接口上声明的泛型，在本类或本接口中即代表某种类型，可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。&lt;/li&gt;
  &lt;li&gt;异常类不能是泛型的&lt;/li&gt;
  &lt;li&gt;不能使用new E[]。但是可以：E[] elements = (E[])new Object[capacity];
参考：ArrayList源码中声明：Object[] elementData，而非泛型参数类型数组。&lt;/li&gt;
  &lt;li&gt;父类有泛型，子类可以选择保留泛型也可以选择指定泛型类型：
    &lt;ol&gt;
      &lt;li&gt;子类不保留父类的泛型：按需实现
        &lt;ol&gt;
          &lt;li&gt;没有类型 擦除&lt;/li&gt;
          &lt;li&gt;具体类型&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
      &lt;li&gt;子类保留父类的泛型：泛型子类
        &lt;ol&gt;
          &lt;li&gt;全部保留&lt;/li&gt;
          &lt;li&gt;部分保留&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;结论：子类必须是“富二代”，子类除了指定或保留父类的泛型，还可以增加自己的泛型&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Father&amp;lt;T1, T2&amp;gt; {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father&amp;lt;Object,Object&amp;gt;{
}
// 2)具体类型
class Son2 extends Father&amp;lt;Integer, String&amp;gt; {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3&amp;lt;T1, T2&amp;gt; extends Father&amp;lt;T1, T2&amp;gt; {
}
// 2)部分保留
class Son4&amp;lt;T2&amp;gt; extends Father&amp;lt;Integer, T2&amp;gt; {
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Father&amp;lt;T1, T2&amp;gt; {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son&amp;lt;A, B&amp;gt; extends Father{//等价于class Son extends Father&amp;lt;Object,Object&amp;gt;{
}
// 2)具体类型
class Son2&amp;lt;A, B&amp;gt; extends Father&amp;lt;Integer, String&amp;gt; {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3&amp;lt;T1, T2, A, B&amp;gt; extends Father&amp;lt;T1, T2&amp;gt; {
}
// 2)部分保留
class Son4&amp;lt;T2, A, B&amp;gt; extends Father&amp;lt;Integer, T2&amp;gt; {
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Person&amp;lt;T&amp;gt; {
// 使用T类型定义变量
private T info;
// 使用T类型定义一般方法
public T getInfo() {
return info; }
public void setInfo(T info) {
this.info = info; }
// 使用T类型定义构造器
public Person() {
}
public Person(T info) {
this.info = info; }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// static的方法中不能声明泛型
//public static void show(T t) {
//
//}
// 不能在try-catch中使用泛型定义
//public void test() {
//try {
//
//} catch (MyException&amp;lt;T&amp;gt; ex) {
//
//}
//}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;泛型方法&quot;&gt;泛型方法&lt;/h3&gt;

&lt;p&gt;方法，也可以被泛型化，不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数，此时，参数的类型就是传入数据的类型。&lt;/p&gt;

&lt;h4 id=&quot;泛型方法的格式&quot;&gt;泛型方法的格式：&lt;/h4&gt;

&lt;p&gt;[访问权限] &lt;泛型&gt; 返回类型 方法名([泛型标识 参数名称]) 抛出的异常&lt;/泛型&gt;&lt;/p&gt;

&lt;p&gt;泛型方法声明泛型时也可以指定上限&lt;/p&gt;

&lt;p&gt;泛型方法，可以声明为静态的。原因：泛型参数是在调用方法时确定的。并非在实例化类时确定。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class DAO {
public &amp;lt;E&amp;gt; E get(int id, E e) {
E result = null;
return result; } }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public static &amp;lt;T&amp;gt; void fromArrayToCollection(T[] a, Collection&amp;lt;T&amp;gt; c) {
for (T o : a) {
c.add(o);
} }
public static void main(String[] args) {
Object[] ao = new Object[100];
Collection&amp;lt;Object&amp;gt; co = new ArrayList&amp;lt;Object&amp;gt;();
fromArrayToCollection(ao, co);
String[] sa = new String[20];
Collection&amp;lt;String&amp;gt; cs = new ArrayList&amp;lt;&amp;gt;();
fromArrayToCollection(sa, cs);
Collection&amp;lt;Double&amp;gt; cd = new ArrayList&amp;lt;&amp;gt;();
// 下面代码中T是Double类，但sa是String类型，编译错误。
// fromArrayToCollection(sa, cd);
// 下面代码中T是Object类型，sa是String类型，可以赋值成功。
fromArrayToCollection(sa, co);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Creature{}
class Person extends Creature{}
class Man extends Person{}
class PersonTest {
public static &amp;lt;T extends Person&amp;gt; void test(T t){
System.out.println(t);
}
public static void main(String[] args) {
test(new Person());
test(new Man());
//The method test(T) in the type PersonTest is not 
//applicable for the arguments (Creature)
test(new Creature());
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;泛型在继承上的体现&quot;&gt;泛型在继承上的体现&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;虽然类A是类B的父类，但是G&amp;lt;A&amp;gt; 和G&amp;lt;B&amp;gt;二者不具备子父类关系，二者是并列关系。

补充：类A是类B的父类，A&amp;lt;G&amp;gt; 是 B&amp;lt;G&amp;gt; 的父类

    List&amp;lt;Object&amp;gt; list1 = null;
    List&amp;lt;String&amp;gt; list2 = new ArrayList&amp;lt;String&amp;gt;();
    //此时的list1和list2的类型不具有子父类关系
    //编译不通过
	//        list1 = list2;
    /*
    反证法：
    假设list1 = list2;
       list1.add(123);导致混入非String的数据。出错。

     */

    show(list1);
    show1(list2);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

   		AbstractList&lt;String&gt; list1 = null;
&lt;/String&gt;&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    List&amp;lt;String&amp;gt; list2 = null;
    ArrayList&amp;lt;String&amp;gt; list3 = null;

    list1 = list3;
    list2 = list3;

    List&amp;lt;String&amp;gt; list4 = new ArrayList&amp;lt;&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;通配符的使用&quot;&gt;通配符的使用&lt;/h2&gt;

&lt;p&gt;1.使用类型通配符：？&lt;/p&gt;

&lt;p&gt;比如：List&amp;lt;?&amp;gt; ，Map&amp;lt;?,?&amp;gt;&lt;/p&gt;

&lt;p&gt;List&amp;lt;?&amp;gt;是List&lt;String&gt;、List&lt;object&gt;等各种泛型List的父类。&lt;/object&gt;&lt;/String&gt;&lt;/p&gt;

&lt;p&gt;2.读取List&amp;lt;?&amp;gt;的对象list中的元素时，永远是安全的，因为不管list的真实类型是什么，它包含的都是Object。&lt;/p&gt;

&lt;p&gt;3.写入list中的元素时，不行。因为我们不知道c的元素类型，我们不能向其中添加对象。&lt;/p&gt;

&lt;p&gt;唯一的例外是null，它是所有类型的成员。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;将任意元素加入到其中不是类型安全的：&lt;/p&gt;

    &lt;p&gt;Collection&amp;lt;?&amp;gt; c = new ArrayList&lt;String&gt;();&lt;/String&gt;&lt;/p&gt;

    &lt;p&gt;c.add(new Object()); // 编译时错误&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因为我们不知道c的元素类型，我们不能向其中添加对象。add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型，所以我们无法传任何东西进去。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;唯一的例外的是null，它是所有类型的成员。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;另一方面，我们可以调用get()方法并使用其返回值。返回值是一个未知的类型，但是我们知道，它总是一个Object&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  public static void main(String[] args) {
  List&amp;lt;?&amp;gt; list = null;
  list = new ArrayList&amp;lt;String&amp;gt;();
  list = new ArrayList&amp;lt;Double&amp;gt;();
  // list.add(3);//编译不通过
  list.add(null);
  List&amp;lt;String&amp;gt; l1 = new ArrayList&amp;lt;String&amp;gt;();
  List&amp;lt;Integer&amp;gt; l2 = new ArrayList&amp;lt;Integer&amp;gt;();
  l1.add(&quot;尚硅谷&quot;);
  l2.add(15);
  read(l1);
  read(l2);
  }
  public static void read(List&amp;lt;?&amp;gt; list) {
  for (Object o : list) {
  System.out.println(o);
  } }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;//注意点1：编译错误：不能用在泛型方法声明上，返回值类型前面&amp;lt;&amp;gt;不能使用?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public static &amp;lt;?&amp;gt; void test(ArrayList&amp;lt;?&amp;gt; list){
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;//注意点2：编译错误：不能用在泛型类的声明上&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class GenericTypeClass&amp;lt;?&amp;gt;{
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;//注意点3：编译错误：不能用在创建对象上，右边属于创建集合对象&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ArrayList&amp;lt;?&amp;gt; list2 = new ArrayList&amp;lt;?&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;有限制的通配符&quot;&gt;有限制的通配符&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&amp;lt;?&amp;gt;
    &lt;ul&gt;
      &lt;li&gt;允许所有泛型的引用调用&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;通配符指定上限
    &lt;ul&gt;
      &lt;li&gt;上限extends：使用时指定的类型必须是继承某个类，或者实现某个接口，即&amp;lt;=&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;通配符指定下限
    &lt;ul&gt;
      &lt;li&gt;下限super：使用时指定的类型不能小于操作的类，即&amp;gt;=&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;举例：
    &lt;ul&gt;
      &lt;li&gt;&amp;lt;? extends Number&amp;gt; (无穷小 , Number]只允许泛型为Number及Number子类的引用调用&lt;/li&gt;
      &lt;li&gt;&amp;lt;? super Number&amp;gt; [Number , 无穷大) 只允许泛型为Number及Number父类的引用调用&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;&amp;lt;? extends Comparable&amp;gt;只允许泛型为实现Comparable接口的实现类的引用调用&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  public static void printCollection3(Collection&amp;lt;? extends Person&amp;gt; coll) {
  //Iterator只能用Iterator&amp;lt;?&amp;gt;或Iterator&amp;lt;? extends Person&amp;gt;.why?
  Iterator&amp;lt;?&amp;gt; iterator = coll.iterator();
  while (iterator.hasNext()) {
  System.out.println(iterator.next());
  } }
  public static void printCollection4(Collection&amp;lt;? super Person&amp;gt; coll) {
  //Iterator只能用Iterator&amp;lt;?&amp;gt;或Iterator&amp;lt;? super Person&amp;gt;.why?
  Iterator&amp;lt;?&amp;gt; iterator = coll.iterator();
  while (iterator.hasNext()) {
  System.out.println(iterator.next());
  } }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;泛型应用举例&quot;&gt;泛型应用举例&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public static void main(String[] args) {
HashMap&amp;lt;String, ArrayList&amp;lt;Citizen&amp;gt;&amp;gt; map = new HashMap&amp;lt;String, ArrayList&amp;lt;Citizen&amp;gt;&amp;gt;();
ArrayList&amp;lt;Citizen&amp;gt; list = new ArrayList&amp;lt;Citizen&amp;gt;();
list.add(new Citizen(&quot;刘恺威&quot;));
list.add(new Citizen(&quot;杨幂&quot;));
list.add(new Citizen(&quot;小糯米&quot;));
map.put(&quot;刘恺威&quot;, list);
Set&amp;lt;Entry&amp;lt;String, ArrayList&amp;lt;Citizen&amp;gt;&amp;gt;&amp;gt; entrySet = map.entrySet();
Iterator&amp;lt;Entry&amp;lt;String, ArrayList&amp;lt;Citizen&amp;gt;&amp;gt;&amp;gt; iterator = entrySet.iterator();
while (iterator.hasNext()) {
Entry&amp;lt;String, ArrayList&amp;lt;Citizen&amp;gt;&amp;gt; entry = iterator.next();
String key = entry.getKey();
ArrayList&amp;lt;Citizen&amp;gt; value = entry.getValue();
System.out.println(&quot;户主：&quot; + key);
System.out.println(&quot;家庭成员：&quot; + value);
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;用户在设计类的时候往往会使用类的关联关系，例如，一个人中可以定义一个信息的属性，但是一个人可能有各种各样的信息（如联系方式、基本信息等），所以此信息属性的类型就可以通过泛型进行声明，然后只要设计相应的信息类即可&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/12/08/rSNMVO.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;</content><author><name>WuJunwei</name></author><category term="notes" /><category term="java" /><summary type="html">出现泛型的原因；在集合中使用泛型；自定义泛型结构；泛型在继承上的体现；通配符的使用；泛型应用举例</summary></entry><entry><title type="html">抽象、接口</title><link href="https://wujunwei99.github.io/notes/2020/11/21/java-interface.html" rel="alternate" type="text/html" title="抽象、接口" /><published>2020-11-21T01:12:25+00:00</published><updated>2020-11-21T01:12:25+00:00</updated><id>https://wujunwei99.github.io/notes/2020/11/21/java-interface</id><content type="html" xml:base="https://wujunwei99.github.io/notes/2020/11/21/java-interface.html">&lt;p&gt;代码块、抽象类与抽象方法、接口(interface)、内部类&lt;/p&gt;

&lt;h2 id=&quot;代码块&quot;&gt;代码块&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;代码块(或初始化块)的作用：
    &lt;ul&gt;
      &lt;li&gt;对Java类或对象进行初始化&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;代码块(或初始化块)的分类：
    &lt;ul&gt;
      &lt;li&gt;一个类中代码块若有修饰符，则只能被static修饰，称为静态代码块(static block)，没有使用static修饰的，为非静态代码块。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;static代码块通常用于初始化static的属性&lt;/p&gt;

    &lt;p&gt;class Person {
  public static int total;
  static {
  total = 100;//为total赋初值
  }
  …… //其它属性或方法声明
  }&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;静态代码块：用static修饰的代码块&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;可以有输出语句。&lt;/li&gt;
  &lt;li&gt;可以对类的属性、类的声明进行初始化操作。&lt;/li&gt;
  &lt;li&gt;不可以对非静态的属性初始化。即：不可以调用非静态的属性和方法。&lt;/li&gt;
  &lt;li&gt;若有多个静态的代码块，那么按照从上到下的顺序依次执行。&lt;/li&gt;
  &lt;li&gt;静态代码块的执行要先于非静态代码块。&lt;/li&gt;
  &lt;li&gt;静态代码块随着类的加载而加载，且只执行一次。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;非静态代码块：没有static修饰的代码块&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;可以有输出语句。&lt;/li&gt;
  &lt;li&gt;可以对类的属性、类的声明进行初始化操作。&lt;/li&gt;
  &lt;li&gt;除了调用非静态的结构外，还可以调用静态的变量或方法。&lt;/li&gt;
  &lt;li&gt;若有多个非静态的代码块，那么按照从上到下的顺序依次执行。&lt;/li&gt;
  &lt;li&gt;每次创建对象的时候，都会执行一次。且先于构造器执行。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;程序中成员变量赋值的执行顺序&quot;&gt;程序中成员变量赋值的执行顺序&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/25/DaISC6.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;抽象类与抽象方法&quot;&gt;抽象类与抽象方法&lt;/h2&gt;

&lt;p&gt;随着继承层次中一个个新子类的定义，类变得越来越具体，而父类则更一般，更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象，以至于它没有具体的实例，这样的类叫做抽象类。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;用abstract关键字来修饰一个类，这个类叫做抽象类。
    &lt;ul&gt;
      &lt;li&gt;此类不能实例化&lt;/li&gt;
      &lt;li&gt;抽象类中一定有构造器，便于子类实例化时调用（涉及：子类对象实例化的全过程）&lt;/li&gt;
      &lt;li&gt;开发中，都会提供抽象类的子类，让子类对象实例化，完成相关的操作&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;用abstract来修饰一个方法，该方法叫做抽象方法。
    &lt;ul&gt;
      &lt;li&gt;抽象方法：只有方法的声明，没有方法的实现。以分号结束：&lt;/li&gt;
      &lt;li&gt;比如：public abstract void talk();&lt;/li&gt;
      &lt;li&gt;包含抽象方法的类，一定是一个抽象类。反之，抽象类中可以没有抽象方法的。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;含有抽象方法的类必须被声明为抽象类。&lt;/li&gt;
  &lt;li&gt;抽象类不能被实例化。抽象类是用来被继承的，抽象类的子类必须重写父类的抽象方法，并提供方法体。若没有重写全部的抽象方法，仍为抽象类。&lt;/li&gt;
  &lt;li&gt;不能用abstract修饰变量、代码块、构造器；&lt;/li&gt;
  &lt;li&gt;不能用abstract修饰私有方法、静态方法、final的方法、final的类&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;抽象类应用&quot;&gt;抽象类应用&lt;/h3&gt;

&lt;p&gt;抽象类是用来模型化那些父类无法确定全部实现，而是由其子类提供具体实现的对象的类。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/25/DaICvD.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;解决方案&quot;&gt;解决方案&lt;/h4&gt;

&lt;p&gt;Java允许类设计者指定：超类声明一个方法但不提供实现，该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。&lt;/p&gt;

&lt;p&gt;Vehicle是一个抽象类，有两个抽象方法。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public abstract class Vehicle{
public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
public abstract double calcTripDistance(); //计算行驶距离的抽象方法
}
public class Truck extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算卡车的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算卡车行驶距离的具体方法 } }
public class RiverBarge extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算驳船的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算驳船行驶距离的具体方法} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注：抽象类不能实例化，new Vihicle()是非法的&lt;/p&gt;

&lt;h3 id=&quot;多态的应用模板方法设计模式templatemethod&quot;&gt;多态的应用：模板方法设计模式(TemplateMethod)&lt;/h3&gt;

&lt;p&gt;抽象类体现的就是一种模板模式的设计，抽象类作为多个子类的通用模板，子类在抽象类的基础上进行扩展、改造，但子类总体上会保留抽象类的行为方式。&lt;/p&gt;

&lt;p&gt;解决的问题：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;当功能内部一部分实现是确定的，一部分实现是不确定的。这时可以把不确定的部分暴露出去，让子类去实现。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;换句话说，在软件开发中实现一个算法时，整体步骤很固定、通用，这些步骤已经在父类中写好了。但是某些部分易变，易变部分可以抽象出来，供不同子类实现。这就是一种模板模式。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  abstract class Template {
  public final void getTime() {
  long start = System.currentTimeMillis();
  code();
  long end = System.currentTimeMillis();
  System.out.println(&quot;执行时间是：&quot; + (end - start));
  }
  public abstract void code();
  }
  class SubTemplate extends Template {
  public void code() {
  for (int i = 0; i &amp;lt; 10000; i++) {
  System.out.println(i);
  } } }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;应用实例&quot;&gt;应用实例&lt;/h4&gt;

&lt;p&gt;模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子，比如常见的有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;数据库访问的封装&lt;/li&gt;
  &lt;li&gt;Junit单元测试&lt;/li&gt;
  &lt;li&gt;JavaWeb的Servlet中关于doGet/doPost方法调用&lt;/li&gt;
  &lt;li&gt;Hibernate中模板程序&lt;/li&gt;
  &lt;li&gt;Spring中JDBCTemlate、HibernateTemplate等&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;接口interface&quot;&gt;接口(interface)&lt;/h2&gt;

&lt;h3 id=&quot;提出问题&quot;&gt;提出问题&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;一方面，有时必须从几个类中派生出一个子类，继承它们所有的属性和方法。但是，Java不支持多重继承。有了接口，就可以得到多重继承的效果。&lt;/li&gt;
  &lt;li&gt;另一方面，有时必须从几个类中抽取出一些共同的行为特征，而它们之间又没有is-a的关系，仅仅是具有相同的行为特征而已。例如：鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。&lt;/li&gt;
  &lt;li&gt;接口就是规范，定义的是一组规则，体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个”是不是”的关系，而接口实现则是 “能不能”的关系。&lt;/li&gt;
  &lt;li&gt;接口的本质是契约，标准，规范，就像我们的法律一样。制定好后大家都要遵守。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;概念与特点&quot;&gt;概念与特点&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;接口(interface)是抽象方法和常量值定义的集合。&lt;/li&gt;
  &lt;li&gt;接口的特点：
    &lt;ul&gt;
      &lt;li&gt;用interface来定义。&lt;/li&gt;
      &lt;li&gt;接口中的所有成员变量都默认是由public static final修饰的。&lt;/li&gt;
      &lt;li&gt;接口中的所有抽象方法都默认是由public abstract修饰的。&lt;/li&gt;
      &lt;li&gt;接口中没有构造器。&lt;/li&gt;
      &lt;li&gt;接口采用多继承机制。&lt;/li&gt;
      &lt;li&gt;接口定义举例&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/25/DaIp8K.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;定义Java类的语法格式：先写extends，后写implements
    &lt;ul&gt;
      &lt;li&gt;class SubClass extends SuperClass implements InterfaceA{ }&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;一个类可以实现多个接口，接口也可以继承其它接口。&lt;/li&gt;
  &lt;li&gt;实现接口的类中必须提供接口中所有方法的具体实现内容，方可实例化。否则，仍为抽象类。&lt;/li&gt;
  &lt;li&gt;接口的主要用途就是被实现类实现。（面向接口编程）&lt;/li&gt;
  &lt;li&gt;与继承关系类似，接口与实现类之间存在多态性&lt;/li&gt;
  &lt;li&gt;接口和类是并列关系，或者可以理解为一种特殊的类。从本质上讲，接口是一种特殊的抽象类，这种抽象类中只包含常量和方法的定义(JDK7.0及之前)，而没有变量和方法的实现&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/25/DaI9gO.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;代理模式&quot;&gt;代理模式&lt;/h3&gt;

&lt;p&gt;代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/25/Da5x4x.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	interface Network {
	public void browse();
	}

	// 被代理类
	class RealServer implements Network { @Override
	public void browse() {
	System.out.println(&quot;真实服务器上
	网浏览信息&quot;);
	} }

	// 代理类
	class ProxyServer implements Network {
	private Network network;
	public ProxyServer(Network network) {
	this.network = network; }
	public void check() {
	System.out.println(&quot;检查网络连接等操作&quot;);
	}

	public void browse() {
	check();
	network.browse();
	} }

	public class ProxyDemo {
	public static void main(String[] args) {
	Network net = new ProxyServer(newRealServer());
	net.browse();
	} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;应用场景&quot;&gt;应用场景&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;安全代理：屏蔽对真实角色的直接访问。  远程代理：通过代理类处理远程方法调用（RMI）&lt;/li&gt;
  &lt;li&gt;延迟加载：先加载轻量级的代理对象，真正需要再加载真实对象&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;比如你要开发一个大文档查看软件，大文档中有大的图片，有可能一个图片有100MB，在打开文件时，不可能将所有的图片都显示出来，这样就可以使用代理模式，当需要查看图片时，用proxy来进行大图片的打开。&lt;/p&gt;

&lt;h4 id=&quot;分类&quot;&gt;分类&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;静态代理（静态定义代理类）&lt;/li&gt;
  &lt;li&gt;动态代理（动态生成代理类）
    &lt;ul&gt;
      &lt;li&gt;JDK自带的动态代理，需要反射等知识&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;工厂模式&quot;&gt;工厂模式&lt;/h3&gt;

&lt;p&gt;工厂模式：实现了创建者与调用者的分离，即将创建对象的具体过程屏蔽隔离起来，达到提高灵活性的目的。&lt;/p&gt;

&lt;h4 id=&quot;分类-1&quot;&gt;分类&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;简单工厂模式：用来生产同一等级结构中的任意产品。（对于增加新的产品，需要修改已有代码）&lt;/li&gt;
  &lt;li&gt;工厂方法模式：用来生产同一等级结构中的固定产品。（支持增加任意产品）&lt;/li&gt;
  &lt;li&gt;抽象工厂模式：用来生产不同产品族的全部产品。（对于增加新的产品，无能为力；支持增加产品族）&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;核心本质&quot;&gt;核心本质：&lt;/h4&gt;

&lt;p&gt;实例化对象，用工厂方法代替 new 操作。&lt;/p&gt;

&lt;p&gt;将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。&lt;/p&gt;

&lt;h4 id=&quot;无工厂模式&quot;&gt;无工厂模式&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;interface Car{
void run();
}
class Audi implements Car{
public void run() {
System.out.println(&quot;奥迪在跑&quot;);
} }
class BYD implements Car{
public void run() {
System.out.println(&quot;比亚迪在跑&quot;);
} }
public class Client01 {
public static void main(String[] args) {
Car a = new Audi();
Car b = new BYD();
a.run();
b.run();
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;简单工厂模式&quot;&gt;简单工厂模式&lt;/h4&gt;

&lt;p&gt;简单工厂模式，从命名上就可以看出这个模式一定很简单。它存在的目的很简单：定义一个用于创建对象的工厂类&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;interface Car {
void run();
}
class Audi implements Car {
public void run() {
System.out.println(&quot;奥迪在跑&quot;);
} }
class BYD implements Car {
public void run() {
System.out.println(&quot;比亚迪在跑&quot;);
} }

//工厂类
class CarFactory {

//方式一
public static Car getCar(String type) {
if (&quot;奥迪&quot;.equals(type)) {
return new Audi();
} else if (&quot;比亚迪&quot;.equals(type)) {
return new BYD();
} else {
return null; } }

//方式二
// public static Car getAudi() {
// return new Audi();
// }
//
// public static Car getByd() {
// return new BYD();
// } }

public class Client02 {
public static void main(String[] args) {
Car a = CarFactory.getCar(&quot;奥迪&quot;);
a.run();
Car b = CarFactory.getCar(&quot;比亚迪&quot;);
b.run();
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调用者只要知道他要什么，从哪里拿，如何创建，不需要知道。分工，多出了一个专门生产 Car 的实现类对象的工厂类。把调用者与创建者分离。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;小结&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;简单工厂模式也叫静态工厂模式，就是工厂类一般是使用静态方法，通过接收的参数的不同来返回不同的实例对象。&lt;/p&gt;

&lt;p&gt;缺点：对于增加新产品，不修改代码的话，是无法扩展的。违反了开闭原则（对扩展开放；对修改封闭）。&lt;/p&gt;

&lt;h4 id=&quot;工厂方法模式&quot;&gt;工厂方法模式&lt;/h4&gt;

&lt;p&gt;为了避免简单工厂模式的缺点，不完全满足 OCP（对扩展开放，对修改关闭）。工厂方法模式和简单工厂模式最大的不同在于，简单工厂模式只有一个（对于一个项目或者一个独立的模块而言）工厂类，而工厂方法模式有一组实现了相同接口的工厂类。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;interface Car{
void run();
}

//两个实现类
class Audi implements Car{
public void run() {
System.out.println(&quot;奥迪在跑&quot;);
} }
class BYD implements Car{
public void run() {
System.out.println(&quot;比亚迪在跑&quot;);
} }

//工厂接口
interface Factory{
Car getCar();
}

//两个工厂类
class AudiFactory implements Factory{
public Audi getCar(){
return new Audi();
} }

class BydFactory implements Factory{
public BYD getCar(){
return new BYD();
} }

public class Client {
public static void main(String[] args) {
Car a = new AudiFactory().getCar();
Car b = new BydFactory().getCar();
a.run();
b.run();
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;总结：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;简单工厂模式与工厂方法模式真正的避免了代码的改动了？&lt;/p&gt;

&lt;p&gt;没有。在简单工厂模式中，新产品的加入要修改工厂角色中的判断语句；而在工厂方法模式中，要么将判断逻辑留在抽象工厂角色中，要么在客户程序中将具体工厂角色写死（就像上面的例子一样）。而且产品对象创建条件的改变必然会引起工厂角色的修改。面对这种情况，&lt;strong&gt;Java 的反射机制与配置文件的巧妙结合突破了限制——这在Spring 中完美的体现了出来。&lt;/strong&gt;&lt;/p&gt;

&lt;h4 id=&quot;抽象工厂模式&quot;&gt;抽象工厂模式&lt;/h4&gt;

&lt;p&gt;抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且抽象工厂模式是三个里面最为抽象、最具一般性的。抽象工厂模式的用意为：给客户端提供一个接口，可以创建多个产品族中的产品对象。&lt;/p&gt;

&lt;p&gt;而且使用抽象工厂模式还要满足一下条件：&lt;/p&gt;

&lt;p&gt;1) 系统中有多个产品族，而系统一次只可能消费其中一族产品。
2) 同属于同一个产品族的产品以其使用。&lt;/p&gt;

&lt;h3 id=&quot;java-8中关于接口的改进&quot;&gt;Java 8中关于接口的改进&lt;/h3&gt;

&lt;p&gt;Java 8中，你可以为接口添加静态方法和默认方法。从技术角度来说，这是完全合法的，只是它看起来违反了接口作为一个抽象定义的理念。&lt;/p&gt;

&lt;p&gt;静态方法：使用 static 关键字修饰。可以通过接口直接调用静态方法，并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。&lt;/p&gt;

&lt;p&gt;默认方法：默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时，还保持了与旧版本代码的兼容性。&lt;/p&gt;

&lt;p&gt;比如：java 8 API中对Collection、List、Comparator等接口提供了丰富的默认
方法&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	public interface AA {
	double PI = 3.14;
	public default void method() {
	System.out.println(&quot;北京&quot;);
	}
	default String method1() {
	return &quot;上海&quot;;
	}
	public static void method2() {
	System.out.println(“hello lambda!&quot;);
	} }

	
	public static void main(String[] args) {
			SubClass s = new SubClass();
			
	//		s.method1();
	//		SubClass.method1();
			//知识点1：接口中定义的静态方法，只能通过接口来调用。
			CompareA.method1();
			//知识点2：通过实现类的对象，可以调用接口中的默认方法。
			//如果实现类重写了接口中的默认方法，调用时，仍然调用的是重写以后的方法
			s.method2();
			//知识点3：如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法，
			//那么子类在没有重写此方法的情况下，默认调用的是父类中的同名同参数的方法。--&amp;gt;类优先原则
			//知识点4：如果实现类实现了多个接口，而这多个接口中定义了同名同参数的默认方法，
			//那么在实现类没有重写此方法的情况下，报错。--&amp;gt;接口冲突。
			//这就需要我们必须在实现类中重写此方法
			s.method3();
			
		}
		
	}

	//知识点5：如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
		public void myMethod(){
			method3();//调用自己定义的重写的方法
			super.method3();//调用的是父类中声明的
			//调用接口中的默认方法
			CompareA.super.method3();
			CompareB.super.method3();
		}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;内部类&quot;&gt;内部类&lt;/h3&gt;

&lt;p&gt;当一个事物的内部，还有一个部分需要一个完整的结构进行描述，而这个内部的完整的结构又只为外部事物提供服务，那么整个内部的完整结构最好使用内部类。&lt;/p&gt;

&lt;p&gt;在Java中，允许一个类的定义位于另一个类的内部，前者称为内部类，后者称为外部类。&lt;/p&gt;

&lt;p&gt;Inner class一般用在定义它的类或语句块之内，在外部引用它时必须给出完整的名称。&lt;/p&gt;

&lt;p&gt;Inner class的名字不能与包含它的外部类类名相同；&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;分类：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;成员内部类（static成员内部类和非static成员内部类）&lt;/li&gt;
  &lt;li&gt;局部内部类（不谈修饰符）、匿名内部类&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;成员内部类作为类的成员的角色：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;和外部类不同，Inner class还可以声明为private或protected；&lt;/li&gt;
  &lt;li&gt;可以调用外部类的结构&lt;/li&gt;
  &lt;li&gt;Inner class 可以声明为static的，但此时就不能再使用外层类的非static的成员变量；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;成员内部类作为类的角色：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;可以在内部定义属性、方法、构造器等结构&lt;/li&gt;
  &lt;li&gt;可以声明为abstract类 ，因此可以被其它的内部类继承&lt;/li&gt;
  &lt;li&gt;可以声明为final的  编译以后生成OuterClass$InnerClass.class字节码文件（也适用于局部内部类）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;【注意】&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;非static的成员内部类中的成员不能声明为static的，只有在外部类或static的成员内部类中才可声明static成员。&lt;/li&gt;
  &lt;li&gt;外部类访问成员内部类的成员，需要“内部类.成员”或“内部类对象.成员”的方式&lt;/li&gt;
  &lt;li&gt;成员内部类可以直接使用外部类的所有成员，包括私有的数据&lt;/li&gt;
  &lt;li&gt;当想要在外部类的静态成员部分使用内部类时，可以考虑内部类声明为静态的&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;举例&quot;&gt;举例&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	class Outer {
	private int s;
	public class Inner {
	public void mb() {
	s = 100;
	System.out.println(&quot;在内部类Inner中s=&quot; + s);
	} }
	public void ma() {
	Inner i = new Inner();
	i.mb();
	} }
	public class InnerTest {
	public static void main(String args[]) {
	Outer o = new Outer();
	o.ma();
	} }


	public class Outer {
	private int s = 111;
	public class Inner {
	private int s = 222;
	public void mb(int s) {
	System.out.println(s); // 局部变量s
	System.out.println(this.s); // 内部类对象的属性s
	System.out.println(Outer.this.s); // 外部类对象属性s } }
	public static void main(String args[]) {
	Outer a = new Outer();
	Outer.Inner b = a.new Inner();
	b.mb(333);
	} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;如何声明局部内部类&quot;&gt;如何声明局部内部类&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	class 外部类{
	方法(){
	class 局部内部类{ } }{
	class 局部内部类{ } } }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;如何使用局部内部类&quot;&gt;如何使用局部内部类&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;只能在声明它的方法或代码块中使用，而且是先声明后使用。除此之外的任何地方都不能使用该类&lt;/li&gt;
  &lt;li&gt;但是它的对象可以通过外部方法的返回值返回使用，返回值类型只能是局部内部类的父类或父接口类型&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;局部内部类的特点&quot;&gt;局部内部类的特点&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;内部类仍然是一个独立的类，在编译之后内部类会被编译成独立的.class文件，但是前面冠以外部类的类名和$符号，以及数字编号。&lt;/li&gt;
  &lt;li&gt;只能在声明它的方法或代码块中使用，而且是先声明后使用。除此之外的任何地方都不能使用该类。&lt;/li&gt;
  &lt;li&gt;局部内部类可以使用外部类的成员，包括私有的。&lt;/li&gt;
  &lt;li&gt;局部内部类可以使用外部方法的局部变量，但是必须是final的。由局部内部类和局部变量的声明周期不同所致。&lt;/li&gt;
  &lt;li&gt;局部内部类和局部变量地位类似，不能使用public,protected,缺省,private&lt;/li&gt;
  &lt;li&gt;局部内部类不能使用static修饰，因此也不能包含静态成员&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;匿名内部类&quot;&gt;匿名内部类&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;匿名内部类不能定义任何静态成员、方法和类，只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面，用其隐含实现一个接口或实现一个类。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;格式：&lt;/p&gt;

    &lt;p&gt;new 父类构造器（实参列表）|实现接口(){
  //匿名内部类的类体部分
  }&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;匿名内部类的特点
    &lt;ul&gt;
      &lt;li&gt;匿名内部类必须继承父类或实现接口&lt;/li&gt;
      &lt;li&gt;匿名内部类只能有一个对象&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;匿名内部类对象只能使用多态形式引用&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  interface A{
  public abstract void fun1();
  }
  public class Outer{
  public static void main(String[] args) {
  new Outer().callInner(new A(){
  //接口是不能new但此处比较特殊是子类对象实现接口，只不过没有为对象取名
  public void fun1() {
  System.out.println(“implement for fun1&quot;);
  }
  });// 两步写成一步了
  }
  public void callInner(A a) {
  a.fun1();
  }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;</content><author><name>WuJunwei</name></author><category term="notes" /><category term="java" /><summary type="html">代码块、抽象类与抽象方法、接口(interface)、内部类</summary></entry><entry><title type="html">多线程</title><link href="https://wujunwei99.github.io/notes/2020/11/20/java-Thread.html" rel="alternate" type="text/html" title="多线程" /><published>2020-11-20T02:45:33+00:00</published><updated>2020-11-20T02:45:33+00:00</updated><id>https://wujunwei99.github.io/notes/2020/11/20/java-Thread</id><content type="html" xml:base="https://wujunwei99.github.io/notes/2020/11/20/java-Thread.html">&lt;p&gt;线程的创建与使用，生命周期，线程的同步，线程的通信，JDK5.0新增创建方式&lt;/p&gt;

&lt;h2 id=&quot;基本概念程序进程线程&quot;&gt;基本概念：程序、进程、线程&lt;/h2&gt;

&lt;h3 id=&quot;概念&quot;&gt;概念&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;程序(program)&lt;/strong&gt;是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码，静态对象。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;进程(process)&lt;/strong&gt;是程序的一次执行过程，或是正在运行的一个程序。是一个动态的过程：有它自身的产生、存在和消亡的过程。——生命周期&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如：运行中的QQ，运行中的MP3播放器&lt;/li&gt;
  &lt;li&gt;程序是静态的，进程是动态的&lt;/li&gt;
  &lt;li&gt;进程作为资源分配的单位，系统在运行时会为每个进程分配不同的内存区域&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;线程(thread)&lt;/strong&gt;，进程可进一步细化为线程，是一个程序内部的一条执行路径。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;若一个进程同一时间并行执行多个线程，就是支持多线程的&lt;/li&gt;
  &lt;li&gt;线程作为调度和执行的单位，每个线程拥有独立的运行栈和程序计数器(pc)，线程切换的开销小&lt;/li&gt;
  &lt;li&gt;一个进程中的多个线程共享相同的内存单元/内存地址空间→它们从同一堆中分配对象，可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;单核cpu和多核cpu的理解&quot;&gt;单核CPU和多核CPU的理解&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;单核CPU&lt;/strong&gt;，其实是一种假的多线程，因为在一个时间单元内，也只能执行一个线程的任务。例如：虽然有多车道，但是收费站只有一个工作人员在收费，只有收了费才能通过，那么CPU就好比收费人员。如果有某个人不想交钱，那么收费人员可以把他“挂起”（晾着他，等他想通了，准备好了钱，再去收费）。&lt;strong&gt;但是因为CPU时间单元特别短，因此感觉不出来。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;如果是&lt;strong&gt;多核&lt;/strong&gt;的话，才能更好的发挥多线程的效率。（现在的服务器都是多核的）&lt;/p&gt;

&lt;p&gt;一个Java应用程序java.exe，其实至少有三个线程：main()主线程，gc()
垃圾回收线程，异常处理线程。当然如果发生异常，会影响主线程。&lt;/p&gt;

&lt;h3 id=&quot;并行与并发&quot;&gt;并行与并发&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;并行：多个CPU同时执行多个任务。比如：多个人同时做不同的事。&lt;/li&gt;
  &lt;li&gt;并发：一个CPU(采用时间片)同时执行多个任务。比如：秒杀、多个人做同一件事。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;使用多线程的优点&quot;&gt;使用多线程的优点&lt;/h3&gt;

&lt;p&gt;以单核CPU为例，只使用单个线程先后完成多个任务（调用多个方法），肯定比用多个线程来完成用的时间更短，为何仍需多线程呢？&lt;/p&gt;

&lt;p&gt;多线程程序的优点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;提高应用程序的响应。对图形化界面更有意义，可增强用户体验。&lt;/li&gt;
  &lt;li&gt;提高计算机系统CPU的利用率&lt;/li&gt;
  &lt;li&gt;改善程序结构。将既长又复杂的进程分为多个线程，独立运行，利于理解和修改&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;何时需要多线程&quot;&gt;何时需要多线程&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;程序需要同时执行两个或多个任务。&lt;/li&gt;
  &lt;li&gt;程序需要实现一些需要等待的任务时，如用户输入、文件读写操作、网络操作、搜索等。&lt;/li&gt;
  &lt;li&gt;需要一些后台运行的程序时。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;线程的创建和使用&quot;&gt;线程的创建和使用&lt;/h2&gt;

&lt;p&gt;Java语言的JVM允许程序运行多个线程，它通过&lt;strong&gt;java.lang.Thread&lt;/strong&gt;类来体现&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thread类的特性&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;每个线程都是通过某个特定Thread对象的run()方法来完成操作的，经常把run()方法的主体称为线程体&lt;/li&gt;
  &lt;li&gt;通过该Thread对象的start()方法来启动这个线程，而非直接调用run()&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;thread类&quot;&gt;Thread类&lt;/h3&gt;

&lt;h4 id=&quot;构造器&quot;&gt;构造器&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Thread()：创建新的Thread对象&lt;/li&gt;
  &lt;li&gt;Thread(String threadname)：创建线程并指定线程实例名&lt;/li&gt;
  &lt;li&gt;Thread(Runnable target)：指定创建线程的目标对象，它实现了Runnable接口中的run方法&lt;/li&gt;
  &lt;li&gt;Thread(Runnable target, String name)：创建新的Thread对象&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;api中创建线程的两种方式&quot;&gt;API中创建线程的两种方式&lt;/h3&gt;

&lt;p&gt;JDK1.5之前创建新执行线程有两种方法：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;继承Thread类的方式&lt;/li&gt;
  &lt;li&gt;实现Runnable接口的方式&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;继承thread类&quot;&gt;继承Thread类&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;定义子类继承Thread类。&lt;/li&gt;
  &lt;li&gt;子类中重写Thread类中的run方法。&lt;/li&gt;
  &lt;li&gt;创建Thread子类对象，即创建了线程对象。&lt;/li&gt;
  &lt;li&gt;调用线程对象start方法：启动线程，调用run方法。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/21/D12EuQ.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果自己手动调用run()方法，那么就只是普通方法，没有启动多线程模式。&lt;/li&gt;
  &lt;li&gt;run()方法由JVM调用，什么时候调用，执行的过程控制都有操作系统的CPU
调度决定。&lt;/li&gt;
  &lt;li&gt;想要启动多线程，必须调用start方法。&lt;/li&gt;
  &lt;li&gt;一个线程对象只能调用一次start()方法启动，如果重复调用了，则将抛出以上
的异常“IllegalThreadStateException”。&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;实现runnable接口&quot;&gt;实现Runnable接口&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;定义子类，实现Runnable接口。&lt;/li&gt;
  &lt;li&gt;子类中重写Runnable接口中的run方法。&lt;/li&gt;
  &lt;li&gt;通过Thread类含参构造器创建线程对象。&lt;/li&gt;
  &lt;li&gt;将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。&lt;/li&gt;
  &lt;li&gt;调用Thread类的start方法：开启线程，调用Runnable子类接口的run方法。&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;继承方式和实现方式的联系与区别&quot;&gt;继承方式和实现方式的联系与区别&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;联系&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;两种方法都需要重写run()方法，将线程要执行的逻辑放在run方法中&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;区别：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;继承Thread：线程代码存放Thread子类run方法中。&lt;/li&gt;
  &lt;li&gt;实现Runnable：线程代码存在接口的子类的run方法&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;实现方式的好处&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;避免了单继承的局限性&lt;/li&gt;
  &lt;li&gt;多个线程可以共享同一个接口实现类的对象，非常适合多个相同线程来处理同一份资源。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;thread类的有关方法&quot;&gt;Thread类的有关方法&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;void start(): 启动线程，并执行对象的run()方法&lt;/li&gt;
  &lt;li&gt;run(): 线程在被调度时执行的操作&lt;/li&gt;
  &lt;li&gt;String getName(): 返回线程的名称&lt;/li&gt;
  &lt;li&gt;void setName(String name):设置该线程名称&lt;/li&gt;
  &lt;li&gt;static Thread currentThread(): 返回当前线程。在Thread子类中就是this，通常用于主线程和Runnable实现类&lt;/li&gt;
  &lt;li&gt;static void yield()：线程让步
    &lt;ul&gt;
      &lt;li&gt;暂停当前正在执行的线程，把执行机会让给优先级相同或更高的线程&lt;/li&gt;
      &lt;li&gt;若队列中没有同优先级的线程，忽略此方法&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;join() ：当某个程序执行流中调用其他线程的 join() 方法时，调用线程将被阻塞，直到 join() 方法加入的 join 线程执行完为止
    &lt;ul&gt;
      &lt;li&gt;低优先级的线程也可以获得执行&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;static void sleep(long millis)：(指定时间:毫秒)
    &lt;ul&gt;
      &lt;li&gt;令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。&lt;/li&gt;
      &lt;li&gt;抛出InterruptedException异常&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;stop(): 强制线程生命期结束，不推荐使用&lt;/li&gt;
  &lt;li&gt;boolean isAlive()：返回boolean，判断线程是否还活着&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;线程的调度&quot;&gt;线程的调度&lt;/h3&gt;

&lt;p&gt;调度策略：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;时间片&lt;/li&gt;
  &lt;li&gt;抢占式：高优先级的线程抢占CPU&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;java的调度方法&quot;&gt;Java的调度方法&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;同优先级线程组成先进先出队列（先到先服务），使用时间片策略&lt;/li&gt;
  &lt;li&gt;对高优先级，使用优先调度的抢占式策略&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;线程的优先级&quot;&gt;线程的优先级&lt;/h4&gt;

&lt;p&gt;线程的&lt;strong&gt;优先级等级&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;MAX_PRIORITY：10&lt;/li&gt;
  &lt;li&gt;MIN _PRIORITY：1&lt;/li&gt;
  &lt;li&gt;NORM_PRIORITY：5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;涉及的方法&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;getPriority() ：返回线程优先值&lt;/li&gt;
  &lt;li&gt;setPriority(int newPriority) ：改变线程的优先级&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;说明&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;线程创建时继承父线程的优先级&lt;/li&gt;
  &lt;li&gt;低优先级只是获得调度的概率低，并非一定是在高优先级线程之后才被调用&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;线程的分类&quot;&gt;线程的分类&lt;/h4&gt;

&lt;p&gt;Java中的线程分为两类：一种是守护线程，一种是用户线程。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;它们在几乎每个方面都是相同的，唯一的区别是判断JVM何时离开。&lt;/li&gt;
  &lt;li&gt;守护线程是用来服务用户线程的，通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。&lt;/li&gt;
  &lt;li&gt;Java垃圾回收就是一个典型的守护线程。&lt;/li&gt;
  &lt;li&gt;若JVM中都是守护线程，当前JVM将退出。&lt;/li&gt;
  &lt;li&gt;形象理解：兔死狗烹，鸟尽弓藏&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;线程的生命周期&quot;&gt;线程的生命周期&lt;/h2&gt;

&lt;h3 id=&quot;jdk中用threadstate类定义了线程的几种状态&quot;&gt;JDK中用Thread.State类定义了线程的几种状态&lt;/h3&gt;

&lt;p&gt;要想实现多线程，必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程，在它的一个完整的生命周期中通常要经历如下的五种状态：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;新建： 当一个Thread类或其子类的对象被声明并创建时，新生的线程对象处于新建状态&lt;/li&gt;
  &lt;li&gt;就绪：处于新建状态的线程被start()后，将进入线程队列等待CPU时间片，此时它已具备了运行的条件，只是没分配到CPU资源&lt;/li&gt;
  &lt;li&gt;运行：当就绪的线程被调度并获得CPU资源时,便进入运行状态，run()方法定义了线程的操作和功能&lt;/li&gt;
  &lt;li&gt;阻塞：在某种特殊情况下，被人为挂起或执行输入输出操作时，让出 CPU 并临时中止自己的执行，进入阻塞状态&lt;/li&gt;
  &lt;li&gt;死亡：线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/21/D12kjg.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/21/D12FgS.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;线程的同步&quot;&gt;线程的同步&lt;/h2&gt;

&lt;h3 id=&quot;问题的提出&quot;&gt;问题的提出&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/21/D12P9f.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;private int tick = 100;
public void run(){
while(true){
if(tick&amp;gt;0){
try{
Thread.sleep(10);
}catch(InterruptedException e){ e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+“售出车票，tick号为： &quot;+tick--);
} } }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;多线程出现了安全问题&lt;/li&gt;
  &lt;li&gt;问题的原因：
当多条语句在操作同一个线程共享数据时，一个线程对多条语句只执行了一部分，还没有执行完，另一个线程参与进来执行。导致共享数据的错误。&lt;/li&gt;
  &lt;li&gt;解决办法：
对多条操作共享数据的语句，只能让一个线程都执行完，在执行过程中，其他线程不可以参与执行。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;synchronized的使用方法&quot;&gt;Synchronized的使用方法&lt;/h3&gt;

&lt;p&gt;Java对于多线程的安全问题提供了专业的解决方式：同步机制&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;同步代码块：&lt;/p&gt;

    &lt;p&gt;synchronized (对象){
 // 需要被同步的代码；
 }&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;synchronized还可以放在方法声明中，表示整个方法为&lt;strong&gt;同步方法&lt;/strong&gt;。&lt;/p&gt;

    &lt;p&gt;例如：
 public synchronized void show (String name){ 
 ….
 }&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/21/D1294P.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;同步机制中的锁&quot;&gt;同步机制中的锁&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;同步锁机制：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在《Thinking in Java》中，是这么说的：对于并发工作，你需要某种方式来防止两个任务访问相同的资源（其实就是共享资源竞争）。 防止这种冲突的方法就是当资源被一个任务使用时，在其上加锁。第一个访问某项资源的任务必须锁定这项资源，使其他任务在其被解锁之前，就无法访问它了，而在其被解锁之时，另一个任务就可以锁定并使用它了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;synchronized的锁是什么？&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;任意对象都可以作为同步锁。所有对象都自动含有单一的锁（监视器）。&lt;/li&gt;
  &lt;li&gt;同步方法的锁：静态方法（类名.class）、非静态方法（this）&lt;/li&gt;
  &lt;li&gt;同步代码块：自己指定，很多时候也是指定为this或类名.class&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;必须确保使用同一个资源的&lt;strong&gt;多个线程共用一把锁&lt;/strong&gt;，这个非常重要，否则就无法保证共享资源的安全&lt;/li&gt;
  &lt;li&gt;一个线程类中的所有静态方法共用同一把锁（类名.class），所有非静态方法共用同一把锁（this），同步代码块（指定需谨慎）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;同步的范围&quot;&gt;同步的范围&lt;/h3&gt;

&lt;p&gt;1、如何找问题，即代码是否存在线程安全？（非常重要）&lt;/p&gt;

&lt;p&gt;（1）明确哪些代码是多线程运行的代码&lt;/p&gt;

&lt;p&gt;（2）明确多个线程是否有共享数据&lt;/p&gt;

&lt;p&gt;（3）明确多线程运行代码中是否有多条语句操作共享数据&lt;/p&gt;

&lt;p&gt;2、如何解决呢？（非常重要）&lt;/p&gt;

&lt;p&gt;对多条操作共享数据的语句，只能让一个线程都执行完，在执行过程中，其他线程不可以参与执行。&lt;/p&gt;

&lt;p&gt;即所有操作共享数据的这些语句都要放在同步范围中&lt;/p&gt;

&lt;p&gt;3、切记：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;范围太小：没锁住所有有安全问题的代码&lt;/li&gt;
  &lt;li&gt;范围太大：没发挥多线程的功能&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;释放锁的操作&quot;&gt;释放锁的操作&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;当前线程的同步方法、同步代码块执行结束。&lt;/li&gt;
  &lt;li&gt;当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。&lt;/li&gt;
  &lt;li&gt;当前线程在同步代码块、同步方法中出现了未处理的Error或Exception，导致异常结束。&lt;/li&gt;
  &lt;li&gt;当前线程在同步代码块、同步方法中执行了线程对象的wait()方法，当前线程暂停，并释放锁&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;不会释放锁的操作&quot;&gt;不会释放锁的操作&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;线程执行同步代码块或同步方法时，程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行&lt;/li&gt;
  &lt;li&gt;线程执行同步代码块时，其他线程调用了该线程的suspend()方法将该线程挂起，该线程不会释放锁（同步监视器）。
    &lt;ul&gt;
      &lt;li&gt;应尽量避免使用suspend()和resume()来控制线程&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;单例设计模式之懒汉式线程安全&quot;&gt;单例设计模式之懒汉式(线程安全)&lt;/h3&gt;

&lt;p&gt;同步代码块，同步方法解决线程安全问题&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/21/D12i38.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;效率更高的修改方法，创建结束后线程访问时，可以不用等待判断：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance == null){
instance=new Singleton();
} } }
return instance;
} }
public class SingletonTest{
public static void main(String[] args){
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2);
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;线程的死锁问题&quot;&gt;线程的死锁问题&lt;/h3&gt;

&lt;h4 id=&quot;死锁&quot;&gt;死锁&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;不同的线程分别占用对方需要的同步资源不放弃，都在等待对方放弃自己需要的同步资源，就形成了线程的死锁&lt;/li&gt;
  &lt;li&gt;出现死锁后，不会出现异常，不会出现提示，只是所有的线程都处于阻塞状态，无法继续&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下例可能出现死锁，如果添加Thread.sleep()，死锁的概率将会大大增加&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/21/D12SAI.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;专门的算法、原则&lt;/li&gt;
  &lt;li&gt;尽量减少同步资源的定义&lt;/li&gt;
  &lt;li&gt;尽量避免嵌套同步&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;lock锁&quot;&gt;Lock(锁)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;从JDK 5.0开始，Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。&lt;/li&gt;
  &lt;li&gt;java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问，每次只能有一个线程对Lock对象加锁，线程开始访问共享资源之前应先获得Lock对象。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;ReentrantLock 类实现了 Lock ，它拥有与 synchronized 相同的并发性和内存语义，在实现线程安全的控制中，比较常用的是ReentrantLock，可以显式加锁、释放锁&lt;/p&gt;

    &lt;p&gt;class A{
  private final ReentrantLock lock = new ReenTrantLock();
  public void m(){
  lock.lock();
  try{
  //保证线程安全的代码; }
  finally{
  lock.unlock(); 
  } } }&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注意：如果同步代码有异常，要将unlock()写入&lt;strong&gt;finally语句块&lt;/strong&gt;&lt;/p&gt;

&lt;h4 id=&quot;synchronized与lock的对比&quot;&gt;synchronized与Lock的对比&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;Lock是显式锁（手动开启和关闭锁，别忘记关闭锁），synchronized是隐式锁，出了作用域自动释放&lt;/li&gt;
  &lt;li&gt;Lock只有代码块锁，synchronized有代码块锁和方法锁&lt;/li&gt;
  &lt;li&gt;使用Lock锁，JVM将花费较少的时间来调度线程，性能更好。并且具有
更好的扩展性（提供更多的子类）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;优先使用顺序：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lock→同步代码块（已经进入了方法体，分配了相应资源）→同步方法（在方法体之外）&lt;/p&gt;

&lt;h3 id=&quot;练-习&quot;&gt;练 习&lt;/h3&gt;

&lt;p&gt;银行有一个账户。&lt;/p&gt;

&lt;p&gt;有两个储户分别向同一个账户存3000元，每次存1000，存3次。每次存完打印账户余额&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Account{
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    //存钱
    public synchronized void deposit(double amt){
        if(amt &amp;gt; 0){
            balance += amt;

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + &quot;:存钱成功。余额为：&quot; + balance);
        }
    }
}

class Customer extends  Thread{

    private Account acct;

    public Customer(Account acct) {
        this.acct = acct;
    }

    @Override
    public void run() {

        for (int i = 0; i &amp;lt; 3; i++) {
            acct.deposit(1000);
        }

    }
}


public class AccountTest {

    public static void main(String[] args) {
        Account acct = new Account(0);
        Customer c1 = new Customer(acct);
        Customer c2 = new Customer(acct);

        c1.setName(&quot;甲&quot;);
        c2.setName(&quot;乙&quot;);

        c1.start();
        c2.start();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;线程的通信&quot;&gt;线程的通信&lt;/h2&gt;

&lt;p&gt;使用两个线程打印 1-100。线程1, 线程2 交替打印&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Communication implements Runnable {
int i = 1;
public void run() {
while (true) {
synchronized (this) {
notify();
if (i &amp;lt;= 100) {
System.out.println(Thread.currentThread().getName() + 
&quot;:&quot; + i++);
} else
break;
try {
wait();
} catch (InterruptedException e) { e.printStackTrace();
} } } } }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;wait-与-notify-和-notifyall&quot;&gt;wait() 与 notify() 和 notifyAll()&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;wait()：令当前线程挂起并放弃CPU、同步资源并等待，使别的线程可访问并修改共享资源，而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒，唤醒后等待重新获得对监视器的所有权后才能继续执行。&lt;/li&gt;
  &lt;li&gt;notify()：唤醒正在排队等待同步资源的线程中优先级最高者结束等待&lt;/li&gt;
  &lt;li&gt;notifyAll ()：唤醒正在排队等待资源的所有线程结束等待&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这三个方法只有在synchronized方法或synchronized代码块中才能使用，否则会报java.lang.IllegalMonitorStateException异常&lt;/p&gt;

&lt;p&gt;因为这三个方法必须有锁对象调用，而任意对象都可以作为synchronized的同步锁，因此这三个方法只能在&lt;strong&gt;java.lang.Object类&lt;/strong&gt;中声明&lt;/p&gt;

&lt;h2 id=&quot;sleep-和-wait的异同&quot;&gt;sleep() 和 wait()的异同？&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;1.相同点：一旦执行方法，都可以使得当前的线程进入阻塞状态。&lt;/li&gt;
  &lt;li&gt;2.不同点：
    &lt;ul&gt;
      &lt;li&gt;两个方法声明的位置不同：Thread类中声明sleep() , Object类中声明wait()&lt;/li&gt;
      &lt;li&gt;调用的要求不同：sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中&lt;/li&gt;
      &lt;li&gt;关于是否释放同步监视器：如果两个方法都使用在同步代码块或同步方法中，sleep()不会释放锁，wait()会释放锁。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;wait-方法&quot;&gt;wait() 方法&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;在当前线程中调用方法： 对象名.wait()&lt;/li&gt;
  &lt;li&gt;使当前线程进入等待（某对象）状态 ，直到另一线程对该对象发出 notify (或notifyAll) 为止。&lt;/li&gt;
  &lt;li&gt;调用方法的必要条件：当前线程必须具有对该对象的监控权（加锁）&lt;/li&gt;
  &lt;li&gt;调用此方法后，当前线程将释放对象监控权 ，然后进入等待&lt;/li&gt;
  &lt;li&gt;在当前线程被notify后，要重新获得监控权，然后从断点处继续代码的执行。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;notifynotifyall&quot;&gt;notify()/notifyAll()&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;在当前线程中调用方法： 对象名.notify()&lt;/li&gt;
  &lt;li&gt;功能：唤醒等待该对象监控权的一个/所有线程。&lt;/li&gt;
  &lt;li&gt;调用方法的必要条件：当前线程必须具有对该对象的监控权（加锁）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;经典例题生产者消费者问题&quot;&gt;经典例题：生产者/消费者问题&lt;/h3&gt;

&lt;p&gt;取走产品，店员一次只能持有固定数量的产品(比如:20），如果生产者试图生产更多的产品，店员会叫生产者停一下，如果店中有空位放产品了再通知生产者继续生产；如果店中没有产品了，店员会告诉消费者等一下，如果店中有产品了再通知消费者来取走产品。&lt;/p&gt;

&lt;p&gt;这里可能出现两个问题：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;生产者比消费者快时，消费者会漏掉一些数据没有取到。&lt;/li&gt;
  &lt;li&gt;消费者比生产者快时，消费者会取相同的数据。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/21/D12pNt.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/21/D1gxHA.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/21/D1RPaR.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/21/D12MCV.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;jdk50新增线程创建方式&quot;&gt;JDK5.0新增线程创建方式&lt;/h2&gt;

&lt;h3 id=&quot;新增方式一实现callable接口&quot;&gt;新增方式一：实现Callable接口&lt;/h3&gt;

&lt;p&gt;与使用Runnable相比， Callable功能更强大些&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;相比run()方法，可以有返回值&lt;/li&gt;
  &lt;li&gt;方法可以抛出异常&lt;/li&gt;
  &lt;li&gt;支持泛型的返回值&lt;/li&gt;
  &lt;li&gt;需要借助FutureTask类，比如获取返回结果&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;future接口&quot;&gt;Future接口&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。&lt;/li&gt;
  &lt;li&gt;FutrueTask是Futrue接口的唯一的实现类&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行，又可以作为Future得到Callable的返回值&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  //1.创建一个实现Callable的实现类
  class NumThread implements Callable{
      //2.实现call方法，将此线程需要执行的操作声明在call()中
      @Override
      public Object call() throws Exception {
          int sum = 0;
          for (int i = 1; i &amp;lt;= 100; i++) {
              if(i % 2 == 0){
                  System.out.println(i);
                  sum += i;
              }
          }
          return sum;
      }
  }


  public class ThreadNew {
      public static void main(String[] args) {
          //3.创建Callable接口实现类的对象
          NumThread numThread = new NumThread();
          //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中，创建FutureTask的对象
          FutureTask futureTask = new FutureTask(numThread);
          //5.将FutureTask的对象作为参数传递到Thread类的构造器中，创建Thread对象，并调用start()
          new Thread(futureTask).start();
	
          try {
              //6.获取Callable中call方法的返回值
              //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
              Object sum = futureTask.get();
              System.out.println(&quot;总和为：&quot; + sum);
          } catch (InterruptedException e) {
              e.printStackTrace();
          } catch (ExecutionException e) {
              e.printStackTrace();
          }
      }
	
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;新增方式二使用线程池&quot;&gt;新增方式二：使用线程池&lt;/h3&gt;

&lt;h4 id=&quot;背景&quot;&gt;背景：&lt;/h4&gt;

&lt;p&gt;经常创建和销毁、使用量特别大的资源，比如并发情况下的线程，对性能影响很大。&lt;/p&gt;

&lt;h4 id=&quot;思路&quot;&gt;思路：&lt;/h4&gt;

&lt;p&gt;提前创建好多个线程，放入线程池中，使用时直接获取，使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。&lt;/p&gt;

&lt;h4 id=&quot;好处&quot;&gt;好处：&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;提高响应速度（减少了创建新线程的时间）&lt;/li&gt;
  &lt;li&gt;降低资源消耗（重复利用线程池中线程，不需要每次都创建）&lt;/li&gt;
  &lt;li&gt;便于线程管理
    &lt;ul&gt;
      &lt;li&gt;corePoolSize：核心池的大小&lt;/li&gt;
      &lt;li&gt;maximumPoolSize：最大线程数&lt;/li&gt;
      &lt;li&gt;keepAliveTime：线程没有任务时最多保持多长时间后会终止&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;线程池相关api&quot;&gt;线程池相关API&lt;/h4&gt;

&lt;p&gt;JDK 5.0起提供了线程池相关API：ExecutorService 和 Executors&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ExecutorService&lt;/strong&gt;：真正的线程池接口。常见子类ThreadPoolExecutor&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;void execute(Runnable command) ：执行任务/命令，没有返回值，一般用来执行Runnable&lt;/li&gt;
  &lt;li&gt;
    &lt;T&gt; Future&lt;T&gt; submit(Callable&lt;T&gt; task)：执行任务，有返回值，一般又来执行Callable
&lt;/T&gt;&lt;/T&gt;&lt;/T&gt;
  &lt;/li&gt;
  &lt;li&gt;void shutdown() ：关闭连接池&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Executors&lt;/strong&gt;：工具类、线程池的工厂类，用于创建并返回不同类型的线程池&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Executors.newCachedThreadPool()：创建一个可根据需要创建新线程的线程池&lt;/li&gt;
  &lt;li&gt;Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池&lt;/li&gt;
  &lt;li&gt;Executors.newSingleThreadExecutor() ：创建一个只有一个线程的线程池&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Executors.newScheduledThreadPool(n)：创建一个线程池，它可安排在给定延迟后运
行命令或者定期地执行。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  class NumberThread implements Runnable{
	
      @Override
      public void run() {
          for(int i = 0;i &amp;lt;= 100;i++){
              if(i % 2 == 0){
                  System.out.println(Thread.currentThread().getName() + &quot;: &quot; + i);
              }
          }
      }
  }
	
  class NumberThread1 implements Runnable{
	
      @Override
      public void run() {
          for(int i = 0;i &amp;lt;= 100;i++){
              if(i % 2 != 0){
                  System.out.println(Thread.currentThread().getName() + &quot;: &quot; + i);
              }
          }
      }
  }
	
  public class ThreadPool {
	
      public static void main(String[] args) {
          //1. 提供指定线程数量的线程池
          ExecutorService service = Executors.newFixedThreadPool(10);
          ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
          //设置线程池的属性
  //        System.out.println(service.getClass());
  //        service1.setCorePoolSize(15);
  //        service1.setKeepAliveTime();
	
	
          //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
          service.execute(new NumberThread());//适合适用于Runnable
          service.execute(new NumberThread1());//适合适用于Runnable
	
  //        service.submit(Callable callable);//适合使用于Callable
          //3.关闭连接池
          service.shutdown();
      }
	
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;</content><author><name>WuJunwei</name></author><category term="notes" /><category term="java" /><summary type="html">线程的创建与使用，生命周期，线程的同步，线程的通信，JDK5.0新增创建方式</summary></entry><entry><title type="html">关键字和包装类的使用</title><link href="https://wujunwei99.github.io/notes/2020/08/30/java-keyword.html" rel="alternate" type="text/html" title="关键字和包装类的使用" /><published>2020-08-30T04:28:52+00:00</published><updated>2020-08-30T04:28:52+00:00</updated><id>https://wujunwei99.github.io/notes/2020/08/30/java-keyword</id><content type="html" xml:base="https://wujunwei99.github.io/notes/2020/08/30/java-keyword.html">&lt;p&gt;this的使用、package、super、import的使用、Object类的使用、包装类的使用、static、final、main方法&lt;/p&gt;

&lt;h2 id=&quot;this的使用&quot;&gt;this的使用&lt;/h2&gt;

&lt;p&gt;在Java中，this关键字比较难理解，它的作用和其词义很接近&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;它在方法内部使用，即这个方法所属对象的引用；&lt;/li&gt;
  &lt;li&gt;它在构造器内部使用，表示该构造器正在初始化的对象。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this可以调用类的属性、方法和构造器&lt;/p&gt;

&lt;p&gt;当在方法内需要用到调用该方法的对象时，就用this。&lt;/p&gt;

&lt;p&gt;具体的：我们可以用this来区分属性和局部变量。&lt;/p&gt;

&lt;p&gt;比如：this.name = name;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/29/dHJ9I0.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/29/dHJSZn.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;注意：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;可以在类的构造器中使用”this(形参列表)”的方式，调用本类中重载的其他构造器&lt;/li&gt;
  &lt;li&gt;明确：构造器中不能通过”this(形参列表)”的方式调用自身构造器&lt;/li&gt;
  &lt;li&gt;如果一个类中声明了n个构造器，则最多有 n - 1个构造器中使用了”this(形参列表)”&lt;/li&gt;
  &lt;li&gt;“this(形参列表)”必须声明在类的构造器的首行！&lt;/li&gt;
  &lt;li&gt;在类的一个构造器中，最多只能声明一个”this(形参列表)”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;packageimport的使用&quot;&gt;package、import的使用&lt;/h2&gt;

&lt;h3 id=&quot;package&quot;&gt;package&lt;/h3&gt;

&lt;p&gt;package语句作为Java源文件的第一条语句，指明该文件中定义的类所在的包。(若缺省该语句，则指定为无名包)。它的格式为：&lt;/p&gt;

&lt;p&gt;package 顶层包名.子包名 ;&lt;/p&gt;

&lt;p&gt;包对应于文件系统的目录，package语句中，用 “.” 来指明包(目录)的层次；&lt;/p&gt;

&lt;p&gt;包通常用小写单词标识。通常使用所在公司域名的倒置：com.atguigu.xxx&lt;/p&gt;

&lt;h4 id=&quot;作用&quot;&gt;作用&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;包帮助管理大型软件系统：将功能相近的类划分到同一个包中。比如：MVC的设计模式&lt;/li&gt;
  &lt;li&gt;包可以包含类和子包，划分项目层次，便于管理&lt;/li&gt;
  &lt;li&gt;解决类命名冲突的问题&lt;/li&gt;
  &lt;li&gt;控制访问权限&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/29/dHJpaq.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;jdk中主要的包介绍&quot;&gt;JDK中主要的包介绍&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;java.lang—-包含一些Java语言的核心类，如String、Math、Integer、 System和Thread，提供常用功能&lt;/li&gt;
  &lt;li&gt;java.net—-包含执行与网络相关的操作的类和接口&lt;/li&gt;
  &lt;li&gt;java.io —-包含能提供多种输入/输出功能的类&lt;/li&gt;
  &lt;li&gt;java.util—-包含一些实用工具类，如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。&lt;/li&gt;
  &lt;li&gt;java.text—-包含了一些java格式化相关的类‘&lt;/li&gt;
  &lt;li&gt;java.sql—-包含了java进行JDBC数据库编程的相关类/接口&lt;/li&gt;
  &lt;li&gt;java.awt—-包含了构成抽象窗口工具集（abstract window toolkits）的多个类，这些类被用来构建和管理应用程序的图形用户界面(GUI)。 B/S C/S&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;import&quot;&gt;import&lt;/h3&gt;

&lt;p&gt;为使用定义在不同包中的Java类，需用import语句来引入指定包层次下所需要的类或全部类(.*)。import语句告诉编译器到哪里去寻找类&lt;/p&gt;

&lt;p&gt;注意：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;在源文件中使用import显式的导入指定包下的类或接口&lt;/li&gt;
  &lt;li&gt;声明在包的声明和类的声明之间。&lt;/li&gt;
  &lt;li&gt;如果需要导入多个类或接口，那么就并列显式多个import语句即可&lt;/li&gt;
  &lt;li&gt;举例：可以使用java.util.*的方式，一次性导入util包下所有的类或接口。&lt;/li&gt;
  &lt;li&gt;如果导入的类或接口是java.lang包下的，或者是当前包下的，则可以省略此import语句。&lt;/li&gt;
  &lt;li&gt;如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的
是哪个类。&lt;/li&gt;
  &lt;li&gt;如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话，仍然需要导入。&lt;/li&gt;
  &lt;li&gt;import static组合的使用：调用指定类或接口下的静态的属性或方法&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;super&quot;&gt;super&lt;/h2&gt;

&lt;p&gt;在Java类中使用super来调用父类中的指定操作：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;super可用于访问父类中定义的属性&lt;/li&gt;
  &lt;li&gt;super可用于调用父类中定义的成员方法&lt;/li&gt;
  &lt;li&gt;super可用于在子类构造器中调用父类的构造器&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注意：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;尤其当子父类出现同名成员时，可以用super表明调用的是父类中的成员&lt;/li&gt;
  &lt;li&gt;super的追溯不仅限于直接父类&lt;/li&gt;
  &lt;li&gt;super和this的用法相像，this代表本类对象的引用，super代表父类的内存空间的标识&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/09/11/wUFV8U.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;调用父类的构造器&quot;&gt;调用父类的构造器&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;子类中所有的构造器默认都会访问父类中空参数的构造器&lt;/li&gt;
  &lt;li&gt;当父类中没有空参数的构造器时，子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器。同时，只能”二选一”，且必须放在构造器的首行&lt;/li&gt;
  &lt;li&gt;如果子类构造器中既未显式调用父类或本类的构造器，且父类中又没有无参的构造器，则编译出错&lt;/li&gt;
  &lt;li&gt;在类的多个构造器中，至少有一个类的构造器中使用了”super(形参列表)”，调用父类中的构造器&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;this和super的区别&quot;&gt;this和super的区别&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/09/11/wUFECT.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;object类的使用&quot;&gt;Object类的使用&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/09/11/wUizvQ.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;object类中的主要结构&quot;&gt;Object类中的主要结构&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/09/11/wUiv8S.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;操作符与equals方法&quot;&gt;==操作符与equals方法&lt;/h3&gt;

&lt;p&gt;基本类型比较值:只要两个变量的值相等，即为true&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int a=5; if(a==6){…} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;引用类型比较引用(是否指向同一个对象)：只有指向同一个对象时，==才返回true。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Person p1=new Person();
Person p2=new Person();
if (p1==p2){…}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;用“==”进行比较时，符号两边的数据类型必须兼容(可自动转换的基本数据类型除外)，否则编译出错&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;equals()：所有类都继承了Object，也就获得了equals()方法。还可以重写。
    &lt;ul&gt;
      &lt;li&gt;只能比较引用类型，其作用与“==”相同,比较是否指向同一个对象。&lt;/li&gt;
      &lt;li&gt;格式:obj1.equals(obj2)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;特例：当用equals()方法进行比较时，对类File、String、Date及包装类（Wrapper Class）来说，是比较类型及内容而不考虑引用的是否是同一个对象；
    &lt;ul&gt;
      &lt;li&gt;原因：在这些类中重写了Object类的equals()方法。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;当自定义使用equals()时，可以重写。用于比较两个对象的“内容”是否都相等&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;重写equals方法的原则&quot;&gt;重写equals()方法的原则&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;对称性：如果x.equals(y)返回是“true”，那么y.equals(x)也应该返回是“true”。&lt;/li&gt;
  &lt;li&gt;自反性：x.equals(x)必须返回是“true”。&lt;/li&gt;
  &lt;li&gt;传递性：如果x.equals(y)返回是“true”，而且y.equals(z)返回是“true”，那么z.equals(x)也应该返回是“true”。&lt;/li&gt;
  &lt;li&gt;一致性：如果x.equals(y)返回是“true”，只要x和y内容一直不变，不管你重复x.equals(y)多少次，返回都是“true”。&lt;/li&gt;
  &lt;li&gt;任何情况下，x.equals(null)，永远返回是“false”； x.equals(和x不同类型的对象)永远返回是“false”。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;和equals的区别&quot;&gt;==和equals的区别&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;== 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值，对于引用类型就是比较内存地址&lt;/li&gt;
  &lt;li&gt;equals的话，它是属于java.lang.Object类里面的方法，如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的，而且String类在日常开发中用的比较多，久而久之，形成了equals是比较值的错误观点。&lt;/li&gt;
  &lt;li&gt;具体要看自定义类里有没有重写Object的equals方法来判断。&lt;/li&gt;
  &lt;li&gt;通常情况下，重写equals方法，会比较类中的相应属性是否都相等。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/09/11/wUiO4f.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;tostring-方法&quot;&gt;toString() 方法&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;toString()方法在Object类中定义，其返回值是String类型，返回类名和它的引用地址。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;在进行String与其它类型数据的连接操作时，自动调用toString()方法&lt;/p&gt;

    &lt;p&gt;Date now=new Date();
  System.out.println(“now=”+now); 相当于
  System.out.println(“now=”+now.toString());&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;可以根据需要在用户自定义类型中重写toString()方法&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如String 类重写了toString()方法，返回字符串的值。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;s1=“hello”;
System.out.println(s1);//相当于System.out.println(s1.toString()); 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;基本类型数据转换为String类型时，调用了对应包装类的toString()方法
    &lt;ul&gt;
      &lt;li&gt;int a=10; System.out.println(“a=”+a);&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;包装类的使用&quot;&gt;包装类的使用&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;针对八种基本数据类型定义相应的引用类型—包装类（封装类）&lt;/li&gt;
  &lt;li&gt;有了类的特点，就可以调用类中的方法，Java才是真正的面向对象&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/09/11/wUiqEt.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;基本数据类型包装成包装类的实例 —装箱&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;通过包装类的构造器实现：
    &lt;ul&gt;
      &lt;li&gt;int i = 500; Integer t = new Integer(i);&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;还可以通过字符串参数构造包装类对象：
    &lt;ul&gt;
      &lt;li&gt;Float f = new Float(“4.56”);&lt;/li&gt;
      &lt;li&gt;Long l = new Long(“asdf”); //NumberFormatException&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;获得包装类对象中包装的基本类型变量 —拆箱
    &lt;ul&gt;
      &lt;li&gt;调用包装类的.xxxValue()方法：boolean b = bObj.booleanValue()&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;JDK1.5之后，支持自动装箱，自动拆箱。但类型必须匹配&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;字符串转换成基本数据类型&quot;&gt;字符串转换成基本数据类型&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;通过包装类的构造器实现：int i = new Integer(“12”);&lt;/li&gt;
  &lt;li&gt;通过包装类的parseXxx(String s)静态方法：Float f = Float.parseFloat(“12.1”);&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;基本数据类型转换成字符串&quot;&gt;基本数据类型转换成字符串&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;调用字符串重载的valueOf()方法：String fstr = String.valueOf(2.34f);&lt;/li&gt;
  &lt;li&gt;更直接的方式：String intStr = 5 + “”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;基本类型包装类与string类间的转换&quot;&gt;基本类型、包装类与String类间的转换&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/09/11/wUixgg.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/09/11/wUijC8.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/09/11/wUiLUP.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;static&quot;&gt;static&lt;/h2&gt;

&lt;h3 id=&quot;设计思想&quot;&gt;设计思想&lt;/h3&gt;

&lt;p&gt;类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些属性不因对象的不同而改变，将这些属性设置为类属性。相应的方法设置为类方法。&lt;/p&gt;

&lt;p&gt;如果方法与调用者无关，则这样的方法通常被声明为类方法，由于不需要创建对象就可以调用类方法，从而简化了方法的调用。&lt;/p&gt;

&lt;h4 id=&quot;适用范围&quot;&gt;适用范围&lt;/h4&gt;

&lt;p&gt;在java类中，可以用static修饰属性、方法、代码块、内部类&lt;/p&gt;

&lt;h4 id=&quot;被修饰后的特点&quot;&gt;被修饰后的特点&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;随着类的加载而加载&lt;/li&gt;
  &lt;li&gt;优先于对象存在&lt;/li&gt;
  &lt;li&gt;修饰的成员，被所有对象所共享&lt;/li&gt;
  &lt;li&gt;访问权限允许时，可不创建对象，直接被类调用&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/23/DJf0r6.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/23/DJfax1.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;没有对象的实例时，可以用类名.方法名()的形式访问由static修饰的类方法&lt;/p&gt;

&lt;p&gt;在static方法内部只能访问类的static修饰的属性或方法，不能访问类的非static的结构&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/23/DJfwKx.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;this是一个指针，用来指向堆中实例的对象。而static是一个静态修饰符，用来修饰方法。被static修饰的方法即被称作静态方法。JVM 在内存划分时，会把静态方法划分到一个独立的区（方法区），只有类能够调用，所以又名类方法。因此，静态方法中不能调用非静态方法，除非实例化出对象。因此，如果在static方法中使用this指针，指针会因为无法指向对象而报错。super指针则同理。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/23/DJfU2R.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;设计模式单例模式&quot;&gt;设计模式——单例模式&lt;/h2&gt;

&lt;p&gt;设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、
以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱，不同的棋局，我们用不同的棋谱。”套路”&lt;/p&gt;

&lt;p&gt;所谓类的单例设计模式，就是采取一定的方法保证在整个的软件系统中，对某个类&lt;strong&gt;只能存在一个对象实例&lt;/strong&gt;，并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象，我们首先必须将类的构造器的访问权限设置为private，这样，就不能用new操作符在类的外部产生类的对象了，但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象，只能调用该类的某个静态方法以返回类内部创建的对象，静态方法只能访问类中的静态成员变量，所以，指向类内部产生的该类对象的变量也必须定义成静态的。&lt;/p&gt;

&lt;h3 id=&quot;饿汉式&quot;&gt;饿汉式&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Singleton {
	// 1.私有化构造器
	private Singleton() {
	}
	// 2.内部提供一个当前类的实例
	// 4.此实例也必须静态化
	private static Singleton single = new Singleton();
	// 3.提供公共的静态的方法，返回当前类的对象
	public static Singleton getInstance() {
		return single;
	 } 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;懒汉式&quot;&gt;懒汉式&lt;/h3&gt;

&lt;p&gt;懒汉式暂时还存在线程安全问题，使用多线程可修复&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Singleton {
	// 1.私有化构造器
	private Singleton() {
	}
	// 2.内部提供一个当前类的实例
	// 4.此实例也必须静态化
	private static Singleton single;
	// 3.提供公共的静态的方法，返回当前类的对象
	public static Singleton getInstance() {
    	if(single == null) {
    		single = new Singleton();
    	}
	return single;
	 } 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;区别&quot;&gt;区别&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;饿汉式：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;好处：对象加载时间过长&lt;/li&gt;
  &lt;li&gt;坏处：饿汉式是线程安全的&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;懒汉式：&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;好处：延迟对象的创建&lt;/li&gt;
  &lt;li&gt;坏处：存在线程安全问题&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;优点&quot;&gt;优点&lt;/h3&gt;

&lt;p&gt;由于单例模式只生成一个实例，&lt;strong&gt;减少了系统性能开销&lt;/strong&gt;，当一个对象的产生需要比较多的资源时，如读取配置、产生其他依赖对象时，则可以通过在应用启动时直接产生一个单例对象，然后永久驻留内存的方式来解决。&lt;/p&gt;

&lt;h3 id=&quot;适用场景&quot;&gt;适用场景&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/23/DJhRkF.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;网站的计数器，一般也是单例模式实现，否则难以同步。&lt;/li&gt;
  &lt;li&gt;应用程序的日志应用，一般都使用单例模式实现，这一般是由于共享的日志文件一直处于打开状态，因为只能有一个实例去操作，否则内容不好追加。&lt;/li&gt;
  &lt;li&gt;数据库连接池的设计一般也是采用单例模式，因为数据库连接是一种数据库资源。&lt;/li&gt;
  &lt;li&gt;项目中，读取配置文件的类，一般也只有一个对象。没有必要每次使用配置文件数据，都生成一个对象去读取。&lt;/li&gt;
  &lt;li&gt;Application 也是单例的典型应用&lt;/li&gt;
  &lt;li&gt;Windows的Task Manager (任务管理器)就是很典型的单例模式&lt;/li&gt;
  &lt;li&gt;Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程
中，回收站一直维护着仅有的一个实例&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;理解main方法的语法&quot;&gt;理解main方法的语法&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;由于Java虚拟机需要调用类的main()方法，所以该方法的访问权限必须是public，又因为Java虚拟机在执行main()方法时不必创建对象，所以该方法必须是static的，该方法接收一个String类型的数组参数，该数组中保存执行Java命令时传递给所运行的类的参数。&lt;/li&gt;
  &lt;li&gt;又因为main() 方法是静态的，我们不能直接访问该类中的非静态成员，必须创建该类的一个实例对象后，才能通过这个对象去访问类中的非静态成员，这种情况，我们在之前的例子中多次碰到。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;关键字final&quot;&gt;关键字：final&lt;/h2&gt;

&lt;p&gt;在Java中声明类、变量和方法时，可使用关键字final来修饰,表示“最终的”。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;final标记的类不能被继承。提高安全性，提高程序的可读性。
    &lt;ul&gt;
      &lt;li&gt;String类、System类、StringBuffer类&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;final标记的方法不能被子类重写。
    &lt;ul&gt;
      &lt;li&gt;比如：Object类中的getClass()。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;final标记的变量(成员变量或局部变量)即称为常量。名称大写，且只能被赋值一次。
    &lt;ul&gt;
      &lt;li&gt;final标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋值，然后才能使用。&lt;/li&gt;
      &lt;li&gt;final double MY_PI = 3.14;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;final修饰类&quot;&gt;final修饰类&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;final class A{
}
class B extends A{ //错误，不能被继承。
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;final修饰方法&quot;&gt;final修饰方法&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class A {
public final void print() {
System.out.println(&quot;A&quot;);
} }
class B extends A {
public void print() { // 错误，不能被重写。
System.out.println(&quot;尚硅谷&quot;);
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;final修饰变量常量&quot;&gt;final修饰变量——常量&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class A {
private final String INFO = &quot;atguigu&quot;; //声明常量
public void print() {
//The final field A.INFO cannot be assigned
//INFO = &quot;尚硅谷&quot;;
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;常量名要大写，内容不可修改。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;static final：全局常量&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;关键字final应用举例&quot;&gt;关键字final应用举例&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public final class Test {
public static int totalNumber = 5;
public final int ID;
public Test() {
ID = ++totalNumber; // 可在构造器中给final修饰的“变量”赋值
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.ID);
final int I = 10;
final int J; J = 20;
J = 30; // 非法
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s3.ax1x.com/2020/11/23/DJhgTU.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;</content><author><name>WuJunwei</name></author><category term="notes" /><category term="java" /><summary type="html">this的使用、package、super、import的使用、Object类的使用、包装类的使用、static、final、main方法</summary></entry><entry><title type="html">Java类与成员类</title><link href="https://wujunwei99.github.io/notes/2020/08/05/java-Class-and-Object.html" rel="alternate" type="text/html" title="Java类与成员类" /><published>2020-08-05T02:45:21+00:00</published><updated>2020-08-05T02:45:21+00:00</updated><id>https://wujunwei99.github.io/notes/2020/08/05/java-Class-and-Object</id><content type="html" xml:base="https://wujunwei99.github.io/notes/2020/08/05/java-Class-and-Object.html">&lt;p&gt;面向过程与面向对象；类与对象；类的成员之属性和方法&lt;/p&gt;

&lt;h2 id=&quot;面向过程与面向对象&quot;&gt;面向过程与面向对象&lt;/h2&gt;

&lt;h3 id=&quot;面向过程pop-与-面向对象oop&quot;&gt;面向过程(POP) 与 面向对象(OOP)&lt;/h3&gt;

&lt;p&gt;二者都是一种思想，面向对象是相对于面向过程而言的。面向过程，强调的是功能行为，以函数为最小单位，考虑怎么做。面向对象，将功能封装进对象，强调具备了功能的对象，以类/对象为最小单位，考虑谁来做。&lt;/p&gt;

&lt;p&gt;面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则，如抽象、分类、继承、聚合、多态等。&lt;/p&gt;

&lt;h3 id=&quot;三大特征&quot;&gt;三大特征&lt;/h3&gt;

&lt;p&gt;封装、继承、多态&lt;/p&gt;

&lt;h3 id=&quot;面向对象的思想概述&quot;&gt;面向对象的思想概述&lt;/h3&gt;

&lt;p&gt;程序员从面向过程的执行者转化成了面向对象的指挥者&lt;/p&gt;

&lt;p&gt;面向对象分析方法分析问题的思路和步骤：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;根据问题需要，选择问题所针对的现实世界中的实体。&lt;/li&gt;
  &lt;li&gt;从实体中寻找解决问题相关的属性和功能，这些属性和功能就形成了概念世界中的类。&lt;/li&gt;
  &lt;li&gt;把抽象的实体用计算机语言进行描述，形成计算机世界中类的定义。即借助某种程序语言，把类构造成计算机能够识别和处理的数据结构。&lt;/li&gt;
  &lt;li&gt;将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;类与对象&quot;&gt;类与对象&lt;/h2&gt;

&lt;h3 id=&quot;思想概述&quot;&gt;思想概述&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;类&lt;/strong&gt;(Class)和&lt;strong&gt;对象&lt;/strong&gt;(Object)是面向对象的核心概念。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;类是对一类事物的描述，是&lt;strong&gt;抽象&lt;/strong&gt;的、概念上的定义&lt;/li&gt;
  &lt;li&gt;对象是&lt;strong&gt;实际存在&lt;/strong&gt;的该类事物的每个个体，因而也称为&lt;strong&gt;实例&lt;/strong&gt;(instance)。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以理解为：类 = 抽象概念的人；对象 = 实实在在的某个人&lt;/p&gt;

&lt;p&gt;面向对象程序设计的重点是&lt;strong&gt;类的设计&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;类的设计，其实就是&lt;strong&gt;类的成员的设计&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;java类及类的成员&quot;&gt;Java类及类的成员&lt;/h3&gt;

&lt;p&gt;常见的类的成员有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;属 性：对应类中的成员变量&lt;/li&gt;
  &lt;li&gt;行 为：对应类中的成员方法&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Field = 属性 = 成员变量，Method = (成员)方法 = 函数&lt;/p&gt;

&lt;h3 id=&quot;创建java自定义类&quot;&gt;创建Java自定义类&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;定义类（考虑修饰符、类名）&lt;/li&gt;
  &lt;li&gt;编写类的属性（考虑修饰符、属性类型、属性名、初始化值）&lt;/li&gt;
  &lt;li&gt;编写类的方法（考虑修饰符、返回值类型、方法名、形参等）&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;对象的创建和使用&quot;&gt;对象的创建和使用&lt;/h2&gt;

&lt;p&gt;创建对象语法： 类名 对象名 = new 类名();&lt;/p&gt;

&lt;p&gt;使用“对象名.对象成员”的方式访问对象成员（包括属性和方法）&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIXxqx.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIXvs1.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;类的访问机制&quot;&gt;类的访问机制&lt;/h3&gt;

&lt;p&gt;在一个类中的访问机制：类中的方法可以直接访问类中的成员变量。 （例外static方法访问非static，编译不通过。）&lt;/p&gt;

&lt;p&gt;在不同类中的访问机制：先创建要访问类的对象，再用对象访问类中定义的成员&lt;/p&gt;

&lt;h3 id=&quot;对象的产生&quot;&gt;对象的产生&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIXjMR.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;内存解析&quot;&gt;内存解析&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjpdK.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjSZ6.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;类的成员之属性&quot;&gt;类的成员之属性&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIj9IO.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjiJe.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjPiD.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;成员变量vs局部变量的内存位置&quot;&gt;成员变量vs局部变量的内存位置&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjFRH.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;对象属性的默认初始化赋值&quot;&gt;对象属性的默认初始化赋值&lt;/h3&gt;

&lt;p&gt;当一个对象被创建时，会对其中各种类型的成员变量自动进行初始化赋值。除了基本数据类型之外的变量类型都是引用类型，如上面的Person及前面讲过的数组。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjkzd.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;类的成员之方法&quot;&gt;类的成员之方法&lt;/h2&gt;

&lt;h3 id=&quot;什么是方法method函数&quot;&gt;什么是方法(method、函数)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;方法是类或对象行为特征的抽象，用来完成某个功能操作。在某些语言中也称为函数或过程。&lt;/li&gt;
  &lt;li&gt;将功能封装为方法的目的是，可以实现代码重用，简化代码&lt;/li&gt;
  &lt;li&gt;Java里的方法不能独立存在，所有的方法必须定义在类里。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjVsI.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjEQA.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;方法的调用&quot;&gt;方法的调用&lt;/h3&gt;

&lt;p&gt;方法通过方法名被调用，且只有被调用才会执行。&lt;/p&gt;

&lt;p&gt;注 意：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;方法被调用一次，就会执行一次&lt;/li&gt;
  &lt;li&gt;没有具体返回值的情况，返回值类型用关键字void表示，那么方法体中可以不必使用return语句。如果使用，仅用来结束方法。&lt;/li&gt;
  &lt;li&gt;定义方法时，方法的结果应该返回给调用者，交由调用者处理。&lt;/li&gt;
  &lt;li&gt;方法中只能调用方法或属性，不可以在方法内部定义方法&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;方法的重载&quot;&gt;方法的重载&lt;/h3&gt;

&lt;h4 id=&quot;概念&quot;&gt;概念&lt;/h4&gt;

&lt;p&gt;在同一个类中，允许存在一个以上的同名方法，只要它们的参数个数或者参数类型不同即可。&lt;/p&gt;

&lt;h4 id=&quot;特点&quot;&gt;特点&lt;/h4&gt;

&lt;p&gt;与返回值类型无关，只看参数列表，且参数列表必须不同。(参数个数或参数类型)。调用时，根据方法参数列表的不同来区别。&lt;/p&gt;

&lt;h4 id=&quot;示例&quot;&gt;示例&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//返回两个整数的和
int add(int x,int y){return x+y;}
//返回三个整数的和
int add(int x,int y,int z){return x+y+z;}
//返回两个小数的和
double add(double x,double y){return x+y;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIxK2Q.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;可变个数的形参&quot;&gt;可变个数的形参&lt;/h3&gt;

&lt;p&gt;JavaSE 5.0 中提供了Varargs(variable number of arguments)机制，允许直接定义能和多个实参相匹配的形参。从而，可以用一种更简单的方式，来传递个数可变的实参。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//JDK 5.0以前：采用数组形参来定义方法，传入多个同一类型变量
public static void test(int a ,String[] books);
//JDK5.0：采用可变个数形参来定义方法，传入多个同一类型变量
public static void test(int a ,String…books)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;说明：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;声明格式：方法名(参数的类型名 …参数名)&lt;/li&gt;
  &lt;li&gt;可变参数：方法参数部分指定类型的参数个数是可变多个：0个，1个或多个&lt;/li&gt;
  &lt;li&gt;可变个数形参的方法与同名的方法之间，彼此构成重载&lt;/li&gt;
  &lt;li&gt;可变参数方法的使用与方法参数部分使用数组是一致的&lt;/li&gt;
  &lt;li&gt;方法的参数部分有可变形参，需要放在形参声明的最后&lt;/li&gt;
  &lt;li&gt;在一个方法的形参位置，最多只能声明一个可变个数&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;方法参数的值传递机制&quot;&gt;方法参数的值传递机制&lt;/h3&gt;

&lt;p&gt;方法，必须由其所在类或对象调用才有意义。若方法含有参数：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;形参：方法声明时的参数&lt;/li&gt;
  &lt;li&gt;实参：方法调用时实际传给形参的参数值&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Java里方法的参数传递方式只有一种：值传递。 即将实际参数值的副本（复制品）传入方法内，而参数本身不受影响。&lt;/p&gt;

&lt;h4 id=&quot;实参值的传入&quot;&gt;实参值的传入&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;形参是基本数据类型：将实参基本数据类型变量的“数据值”传递给形参&lt;/li&gt;
  &lt;li&gt;形参是引用数据类型：将实参引用数据类型变量的“地址值”传递给形参&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjmeP.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjQJg.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjuo8.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;方法的参数传递示意图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjMFS.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://s1.ax1x.com/2020/08/08/aIjlWQ.md.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;递归recursion方法&quot;&gt;递归(recursion)方法&lt;/h3&gt;

&lt;p&gt;递归方法：一个方法体内调用它自身。&lt;/p&gt;

&lt;p&gt;方法递归包含了一种隐式的循环，它会重复执行某段代码，但这种重复执行无须循环控制。&lt;/p&gt;

&lt;p&gt;递归一定要向已知方向递归，否则这种递归就变成了无穷递归，类似于死循环。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//计算1-100之间所有自然数的和
public int sum(int num){
if(num == 1){
return 1;
}else{
return num + sum(num - 1);
} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>WuJunwei</name></author><category term="notes" /><category term="java" /><summary type="html">面向过程与面向对象；类与对象；类的成员之属性和方法</summary></entry></feed>