Java基础知识回顾(超详细)

总结目录

本文说明

工作一段时间了,发现很多Java的基础理论不是很清楚,根据自身的需要,查阅了各种资料论坛与博客,经过本人的整理与总结,写了这一篇基础提升的博客,整理的比较全面(可以当做面试宝典),面试之前看看,回顾提升自己。文中素材和图片部分借鉴优质博客与论坛。

(一)Java基础

1.1.Java历史简述

  • 1991 年Sun公司的James Gosling詹姆斯•高斯林)等人开始开发名称为 Oak
    的语言,希望专攻计算机在家电产品上的嵌入式应用(如电视机顶盒、面包烤箱、移动电话等)。
  • 1995年将Oak语言更名为Java;
  • 2009年,甲骨文公司宣布收购Sun公司。

1.2. Java语言的特点

  1. 面向对象(封装,继承,多态);
  2. 平台无关性( Java 虚拟机实现平台无关性,一次编译,到处运行);
  3. 简单易学(与C语言的面向过程相比,Java的面向对象更接近人的语言习惯);
  4. 安全性,可靠性(Java中没有指针,程序员无法直接操作内存,而是把操作权限交给Java虚拟机,使程序不容易出现不容易出现内存泄漏和内存溢出问题。);
  5. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 java的lang包提供一个Thread类本身就支持多线程);
  6. 编译与解释并存(Java编译生成字节码文件,交给Java虚拟机解释);

1.3. 面向对象与面向过程对比。

我们都知道Java的核心思想是面向对象,Java中万事万物皆对象,那么面向对象与面向过程有什么不同呢?

(1).面向过程

优点: 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点: 没有面向对象易维护、易复用、易扩展。

(2).面向对象

优点: 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
缺点: 性能比面向过程低。

1.4. Java平台的三个版本J2EE、J2SE、J2ME。

JavaSE:即Java标准版,主要用于开发和部署桌面、例如,Java应用程序开发平台Eclipse(常说的C\S架构)。
JavaEE:即Java企业版,主要针对企业应用的开发。例如,电商网站(常说的B\S架构)。
JavaME:即Java微型版,主要针对移动设备和嵌入式设备。例如,手机、PDA、电视机顶盒等等。

注:从JDK 5.0开始 J2EE 改名为 java EE,J2SE 改名为 java SE,J2ME 改名成 java ME。

1.5. JDK、JRE、JVM之间的区别于关系。

LZ身边很多Java程序员,虽然写了很久的代码,但问他们jre 和 jdk 之间有什么关系,jvm 又是什么东西,很多人都讲的不是特别清楚,作为一个合格的Java程序员了解这方面的基础理论知识是很必要的。

(1).三者之间的区别:

JDK:(Java Development Kit)即java的开发与运行环境,他除了包含完整的JRE之外,还包含了供开发者使用的工具包。
JRE:(Java Runtime Environment)即Java运行环境,非开发者只需要安装 JRE来运行程序, 它包含java运行的所需的类库+JVM(java虚拟机)。
JVM: (Java Virtual Machine) 即Java虚拟机, 当我们运行一个程序时,JVM 负责将字节码转换为特定机器代码,JVM 提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是 java 程序可以一次编写多处执行的原因。

(2).三者之间的关系

  1. 作为程序员,就必须安装JDK,因为其中包含Java开发工具包,同时也包含了JRE。
  2. 作为使用者,运行已经开发好的Java程序,只需要安装JRE。
  3. JVM和JRE的关系:JRE包含了JVM,JVM是运行Java程序的核心虚拟机,同时也包含了Java程序所需的环境支持
  4. 总结:JDK>JRE>JVM
    在这里插入图片描述

1.6. 什么是Java环境变量?

  1. 环境变量的意义
    让java bin目录下的工具,可以在任意目录下运行,原理是将该工具所在目录告诉了系统,当使用该工具时,由系统帮我们去找指定的目录。
  2. JAVA_HOME
    1. 它指向jdk的安装目录,引用%JAVA_HOME%即可,避免每次引用都输入很长的路径串,方便第三方软件引用约定好的JAVA_HOME变量,保证程序正常运行。
  3. Path环境变量
    设置Path环境变量之后就可以在任何目录下执行javac/java等工具命令了。 系统默认先去当前路径下找要执行的程序,如果没有,再去path中设置的路径下找。
  4. ClassPath
    如果指定了classpath,那么会在指定的目录下查找要运行的类文件(JDK1.5后不需要配置

1.7. javac命令和java命令做什么事情呢?

java运行分两部分:一个是编译,一个是运行。
javac:负责的是编译的部分,当执行javac时,会启动java的编译器程序。对指定扩展名的.java文件进行编译。编译后生成class文件。
java:负责运行的部分.会启动jvm虚拟机,加载运行时所需的类库,并对class文件进行执行.

一个文件要被执行,必须要有一个执行的起始点,这个起始点就是main函数.

1.8. 什么是字节码,采用字节码的好处是什么。

首先我们来谈谈Java文件类型,一共有两种:

  1. 扩展名为Java,Java的源文件,编译之前的纯文本文件,用来储存Java源代码。

  2. 扩展名为class,Java 的类文件,编译之后的二进制文件,存储的是字节码

    也就是说编译后的.class文件存储就是字节码*。

    采用字节码的最大好处: 可以实现一次编译到处运行,也就是java的与平台无关性,它依靠不同平台的Java虚拟机将编译后的字节码解释成具体平台上的机器指令执行。

1.9. import java和javax有什么区别

刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。
所以,实际上java和javax没有区别。这都是一个名字。

1.10. Java和C++的区别

都是面向对象的语言,都支持封装、继承和多态
Java 不提供指针来直接访问内存,程序内存更加安全
Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
Java 有自动内存管理机制,不需要程序员手动释放无用内存

1.11.Java数据类型

  1. 基本数据类型
    在这里插入图片描述
  2. 引用类型
    类、接口类型、数组类型、枚举类型、注解类型。
  3. 区别
    基本数据类型 在被创建时,在栈上给其划分一块内存,将数值直接存储在栈上。

引用数据类型 在被创建时,首先要在栈上给其引用(句柄)分配一块内存,而对象的具体信息都存储在堆内存上,然后由栈上面的引用指向堆中对象的地址。

1.12.Java访问修饰符

在这里插入图片描述

1.13. 字符型常量和字符串常量的区别

  1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符。
  2. 含义上: 字符常量相当于一个整形值( ASCII含义上: 字符常量相当于一个整形值( ASCII 值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)。
  3. 占内存大小 字符常量只占2个字节 字符串常量占若干个字节(至少一个字符结束标志) (注意: char在Java中占两个字节)
    在这里插入图片描述

1.14. Java 面向对象编程三大特性:封装、继承、多态

封装
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。

继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码 同时继承也为实现多态做了铺垫。

关于继承

  1. 子类拥有父类非 private 的属性和方法。
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

多态
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

  1. 体现: 父类引用或者接口的引用指向了自己的子类对象

1.15. 代码中如何实现多态?

实现多态主要有以下三种方式:

  1. 接口实现
  2. 继承父类重写方法
  3. 同一类中进行方法重载

1.16. 多态有什么好处?

允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用)。主要有以下优点:

  1. 可替换性:多态对已存在代码具有可替换性

  2. 可扩充性:增加新的子类不影响已经存在的类结构

  3. 接口性:多态是超类通过方法签名,向子类提供一个公共接口,由子类来完善或者重写它来实现的。

1.17. 接口和抽象类的区别是什么

  1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法。
  2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定。
  3. 一个类可以实现多个接口,但最多只能实现一个抽象类。
  4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定。
  5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
比较抽象类接口
默认方法抽象类可以有默认的方法实现java 8之前,接口中不存在方法的实现.
实现方式子类使用extends关键字来继承抽象类.如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现.子类使用implements来实现接口,需要提供接口中所有声明的实现.
构造器抽象类中可以有构造器,接口中不能
和正常类区别抽象类不能被实例化接口则是完全不同的类型
访问修饰符抽象方法可以有public,protected和default等修饰接口默认是public,不能使用其他修饰符
多继承一个子类只能存在一个父类一个子类可以存在多个接口
添加新方法想抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码如果往接口中添加新方法,则子类中需要实现该方法.
-

1.18.接口的意义抽象类的意义?

接口的意义
接口的意义用三个词就可以概括:规范,扩展,回调。
抽象类的意义
抽象类的意义可以用三句话来概括:

  1. 为其他子类提供一个公共的类型

  2. 封装子类中重复定义的内容

  3. 定义抽象方法,子类虽然有不同的实现,但是定义时一致的

1.19. 重载和重写的区别

重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。

1.20. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 为什么

主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。

1.21. 构造方法有哪些特性

  1. 名字与类名相同;
  2. 没有返回值,但不能用void声明构造函数;
  3. 生成类的对象时自动执行,无需调用。

1.22. 构造器 Constructor 是否可被 override

在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。

1.23. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?

帮助子类做初始化工作。

1.24. 在 Java 中定义一个不做事且没有参数的构造方法的作用

Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

1.25. 在一个静态方法内调用一个非静态成员为什么是非法的,静态方法和实例方法有何不同?

为什么非法
由于静态方法可以不通过对象进行调用,类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问;非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。
有何不同

  1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
  2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制.

在一个类的静态成员中去访问其非静态成员之所以会出错是
因为在类的非静态成员不存在的时候类的静态成员就已经存在了,访问一个内存中不存在的东西当然会出错:

1.26. 成员变量与局部变量的区别有那些

  1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰;
  2. 从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存。
  3. 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
  4. 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不会自动赋值。

1.27. 什么是不可变对象?

不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。

1.28. java 创建对象的几种方式

  1. 采用new(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)

  2. 通过反射

  3. 采用clone

  4. 通过序列化机制
    前2者都需要显式地调用构造方法。造成耦合性最高的恰好是第一种,因此你发现无论什么框架,只要涉及到解耦必先减少new的使用。

1.29. Object中有哪些公共方法?

  1. equals()

  2. clone()

  3. getClass()

  4. notify(),notifyAll(),wait()

  5. toString

  6. finalize()

1.30. java当中的四种引用

强引用,软引用,弱引用,虚引用。不同的引用类型主要体现在GC上:

  1. 强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
    如obj.equels(new Object());
    而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放

  2. 软引用(SoftReference):在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。

SoftReference<String> softRef = new SoftReference<String>(s);     // 再创建一个软引用关联该对象
s = null;        // 消除强引用,现在只剩下软引用与其关联,该String对象为软可达状态
s = softRef.get();  // 重新关联上强引用 
  1. 弱引用(WeakReference):具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。

  2. 虚引用(PhantomReference):顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。

1.31. WeakReference与SoftReference的区别?

这点在四种引用类型中已经做了解释,这里简单说明一下即可:
虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。

1.32. 为什么要有不同的引用类型

不像C语言,我们可以控制内存的申请和释放,在Java中有时候我们需要适当的控制对象被回收的时机,因此就诞生了不同的引用类型,可以说不同的引用类型实则是对GC回收时机不可控的妥协。有以下几个使用场景可以充分的说明:

  1. 利用软引用和弱引用解决OOM问题:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题.

  2. 通过软引用实现Java对象的高速缓存:比如我们创建了一Person的类,如果每次需要查询一个人的信息,哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这将引起大量Person对象的消耗,并且由于这些对象的生命周期相对较短,会引起多次GC影响性能。此时,通过软引用和 HashMap 的结合可以构建高速缓存,提供性能。

1.33. 对象的相等与指向他们的引用相等,两者有什么不同?

对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。

1.34. 什么是方法的返回值?返回值在类的方法里的作用是什么?

方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!

1.35. String 和 StringBuffer、StringBuilder 的区别是什么 String 为什么是不可变的

可变性

简单的来说:String 类中使用 final 关键字字符数组保存字符串,private final char value[],所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[]value 但是没有用 final 关键字修饰,所以这两种对象都是可变的
线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。   
性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:

  1. 操作少量的数据 = String
  2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
  3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer

1.36. == 与 equals(重要)

== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型“= =”比较的是值,引用数据类型 = = 比较的是内存地址).
equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
说明:

String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。

1.37. hashCode 与 equals(重要)

面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”

hashCode()介绍
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)。
为什么要有 hashCode
我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

hashCode()与equals()的相关规定

  1. 如果两个对象相等,则hashcode一定也是相同的。
  2. 两个对象相等,对两个对象分别调用equals方法都返回true。
  3. 两个对象有相同的hashcode值,它们也不一定是相等的。
  4. 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖,hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等。

1.38. 简述线程,程序、进程的基本概念。以及他们之间关系是什么?

1.39. 线程有哪些基本状态?这些状态是如何定义的?

  1. 新建(new):新创建了一个线程对象。
  2. 可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。
  3. 运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。
  4. **阻塞(block):**阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种:
    (一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waitting queue)中。
    (二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
    (三). 其他阻塞: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
  5. 死亡(dead):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
    在这里插入图片描述

1.40.final, finally, finalize的区别

final

  1. 修饰类 表示该类不能被继承
  2. 修饰方法 表示该方法不能被重写
  3. 修饰基本类型变量 表示该变量只能被赋值一次,如果修饰引用,那么表示引用不可变,引用指向的内容可变。
  4. 被final修饰的方法,JVM会尝试将其内联,以提高运行效率
  5. 被final修饰的常量,在编译阶段会存入常量池中。
    finally
    finally 是用于异常处理的场面,无论是否有异常抛出,都会执行
    finalize
    finalize是Object的方法,所有类都继承了该方法。 当一个对象满足垃圾回收的条件,并且被回收的时候,其finalize()方法就会被调用

1.41. Java 中的异常处理

在这里插入图片描述
祖先java.lang包中的 Throwable类。Throwable: 有两个重要的子类:Exception(异常) 和 Error(错误) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。

  1. Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

  2. Exception(异常):是程序本身可以处理的异常。 Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由Java虚拟机抛出。NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException(算术运算异常,一个整数除以0时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)。

  3. Throwable类常用方法
    1. public string getMessage():返回异常发生时的详细信息
    2. public string toString():返回异常发生时的简要描述
    3. public void printStackTrace():在控制台上打印Throwable对象封装的异常信息

  4. 异常处理总结
    try 块: 用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
    catch 块:用于处理try捕获到的异常。
    finally 块: 无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行

  5. 在以下4种特殊情况下,finally块不会被执行:

    1. 在finally语句块中发生了异常。
    2. 在前面的代码中用了System.exit()退出程序。
    3. 程序所在的线程死亡。
    4. 关闭CPU。

1.42. 说出最常见到的runtime exception与Error

在这里插入图片描述

  1. NullPointerException (空指针异常)
  2. ArrayIndexOutOfBoundsException(数组下标越界)
  3. IllegalArgumentException (参数错误)
  4. ArithmeticException 算术异常,比如除数为零
  5. ClassCastException 类型转换异常

error

1. OutOfMemoryError (堆内存溢出)
2. StackOverflowError(栈内存溢出)

1.43 java中有几种类型的流?

Java中所有的流都是基于字节流,所以最基本的流是 字节流

  1. 输入输出字节流
    InputStream OutputStream
  2. 字符流
    在字节流的基础上,封装了字符流
    Reader Writer
  3. 缓存流
    进一步,又封装了缓存流
    BufferedReader PrintWriter
  4. 数据流
    DataInputStream DataOutputStream
  5. 对象流
    ObjectInputStream ObjectOutputStream

1.44. 获取用键盘输入常用的的两种方法

方法1:通过 Scanner

Scanner input = new Scanner(System.in);
String s  = input.nextLine();
input.close();

方法2:通过 BufferedReader

BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); 
String s = input.readLine(); 

1.45. Java反射

  1. java反射是什么
    编译期和运行期,编译期就是编译器帮你把源代码翻译成机器能识别的代码,比如编译器把java代码编译成jvm识别的字节码文件,而运行期指的是将可执行文件交给操作系统去执行,JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制
    2. java反射(Reflection)的底层实现原理
    Object 类,是所有Java 类的继承根源,其内声明了数个应该在所有Java 类中被改写的方法:其中getClass()返回一个Class 对象

而这个Class 类十分特殊。它和一般类一样继承自Object,当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class 对象。
这边列了下Class类其中的方法

获取公共构造器 getConstructors()
获取所有构造器 getDeclaredConstructors()
获取该类对象 newInstance()
获取类名包含包路径 getName()
获取类名不包含包路径 getSimpleName()
获取类公共类型的所有属性 getFields()
获取类的所有属性 getDeclaredFields()
获取类公共类型的指定属性 getField(String name)
获取类全部类型的指定属性 getDeclaredField(String name)
获取类公共类型的方法 getMethods()
获取类的所有方法 getDeclaredMethods()
获得类的特定公共类型方法: getMethod(String name, Class[] parameterTypes)
获取内部类 getDeclaredClasses()
获取外部类 getDeclaringClass()
获取修饰符 getModifiers()
获取所在包 getPackage()
获取所实现的接口 getInterfaces()

  1. 创建反射实例的三种方式。
    1. 直接通过类名点.class获取
    2. 通过Object类的getClass方法来获取 通过Object类的getClass方法来获取
    3. 通过全类名获取用的比较多推荐使用 例如:Class.forName(“com.mysql.jdbc.Driver”);

1.46. 反射中,Class.forName和classloader的区别。

java中class.forName()和classLoader都可用来对类进行加载。
class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

1.47. 什么是序列化?序列化有什么好处。

1. 序列化是干什么的?
简单说就是将内存中的对象保存下来(将对象的内容进行流化),可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间,并且可以把保存的对象状态再读出来。实现java序列化的手段是让该类实现接口 Serializable,这个接口是一个标识性接口,没有任何方法,仅仅用于表示该类可以序列化。

2. 什么情况下需要序列化
  当你想把的内存中的对象保存到一个文件中或者数据库中时候;
  当你想用序列化在网络上传送对象的时候;
  当你想通过RMI传输对象的时候;

  1. **相关注意事项 **
    a)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
     把一个对象完全转成字节序列,方便传输。
    就像你寄一箱饼干,因为体积太大,就全压成粉末紧紧地一包寄出去,这就是序列化的作用。
    只不过JAVA的序列化是可以完全还原的。

1.48. Java序列话中如果有些字段不想进行序列化 怎么办?

对于不想进行序列化的变量,使用transient关键字修饰。

transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。

1.49. java拷贝

浅拷贝与深拷贝
看了很多文章,有些说浅拷贝只是拷贝引用地址,深拷贝才拷贝具体应用的对象,但经过LZ的核实发现有些文章个人感觉对拷贝的理解不是很正确。
总结了一下
浅拷贝
只复制一个对象,对象内部存在的指向其他对象数组或者引用则不复制
深拷贝
深拷贝:对象,对象内部的引用均复制
拷贝的几种方法

  1. System.arraycopy(浅拷贝)
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);   
通过源代码我们可以看到,关键字native说明它不是用java语言写的,而是调用其他语言的代码。 
  1. Arrays.copyOf(浅拷贝)
    实际上它调用的就是System.arraycopy.
  2. Object.clone
    clone()比较特殊,对于对象而言,它是深拷贝,但是对于数组而言,它是浅拷贝。

(二)JavaEE总结

2.1. Servlet总结

在Java Web程序中,Servlet主要负责接收用户请求HttpServletRequest,在doGet(),doPost()中做相应的处理,并将回应HttpServletResponse反馈给用户。Servlet可以设置初始化参数,供Servlet内部使用。一个Servlet类只会有一个实例,在它初始化时调用init()方法,销毁时调用destroy()方法。Servlet需要在web.xml中配置(MyEclipse中创建Servlet会自动配置),一个Servlet可以设置多个URL访问。Servlet不是线程安全,因此要谨慎使用类变量。

2.2. 阐述Servlet和CGI的区别?

CGI的不足之处:

  1. 需要为每个请求启动一个操作CGI程序的系统进程。如果请求频繁,这将会带来很大的开销。
  2. 需要为每个请求加载和运行一个CGI程序,这将带来很大的开销。
  3. 需要重复编写处理网络协议的代码以及编码,这些工作都是非常耗时的。。

Servlet的优点:

  1. 只需要启动一个操作系统进程以及加载一个JVM,大大降低了系统的开销。
  2. 如果多个请求需要做同样处理的时候,这时候只需要加载一个类,这也大大降低了开销。
  3. 所有动态加载的类可以实现对网络协议以及请求解码的共享,大大降低了工作量。
  4. Servlet能直接和Web服务器交互,而普通的CGI程序不能。Servlet还能在各个程序之间共享数据,使数据库连接池之类的功能很容易实现。
    补充: Sun Microsystems公司在1996年发布Servlet技术就是为了和CGI进行竞争,Servlet是一个特殊的Java程序,一个基于Java的Web应用通常包含一个或多个Servlet类。Servlet不能够自行创建并执行,它是在Servlet容器中运行的,容器将用户的请求传递给Servlet程序,并将Servlet的响应回传给用户。通常一个Servlet会关联一个或多个JSP页面。以前CGI经常因为性能开销上的问题被诟病,然而Fast CGI早就已经解决了CGI效率上的问题,所以面试的时候大可不必信口开河的诟病CGI,事实上有很多你熟悉的网站都使用了CGI技术。

2.3. Servlet接口中有哪些方法及Servlet生命周期探秘。

Servlet接口定义了5个方法,其中前三个方法与Servlet生命周期相关:

  1. void init(ServletConfig config) throws ServletException
  2. void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException
  3. void destory()
  4. java.lang.String getServletInfo()
  5. ServletConfig getServletConfig()
    生命周期(重要):
    一个Servlet的生命周期由 实例化,初始化,提供服务,销毁,被回收 几个步骤组成
    在这里插入图片描述
    Web容器加载Servlet并将其实例化后,Servlet生命周期开始,容器运行其init()方法进行Servlet的初始化;请求到达时调用Servlet的service()方法,service()方法会根据需要调用与请求对应的doGet或doPost等方法;当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的destroy()方法。init方法和destory方法只会执行一次,service方法客户端每次请求Servlet都会执行。Servlet中有时会用到一些需要初始化与销毁的资源,因此可以把初始化资源的代码放入init方法中,销毁资源的代码放入destroy方法中,这样就不需要每次处理客户端的请求都要初始化与销毁资源。
    在这里插入图片描述
1.web客户向Servlet容器发出HTTP请求;

2.Servlet容器解析web的HTTP请求.

3.Servlet容器创建一个HttpRequest对象,在这个对象中封装了http请求信息;

4.Servlet容器创建一个HttpResponse对象;

5. Servlet容器(如果访问的该servlet不是在服务器启动时创建的,则先创建servlet实例并调用init()方法初始化对象)调用HttpServlet的service()方法,把HttpRequest和HttpResponse对象为service方法的参数传给HttpServlet对象;

6.HttpServlet调用HttpRequest的有关方法,获取HTTP请求信息;

7.HttpServlet调用HttpResponse的有关方法,生成响应数据;

8.Servlet容器把HttpServlet的响应结果传给web客户.  

2.4. get和post请求的区别

①get请求用来从服务器上获得资源,而post是用来向服务器提交数据;

②get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用"?“连接,而各个变量之间使用”&"连接;post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL;

③get传输的数据要受到URL长度限制(1024字节即256个字符);而post可以传输大量的数据,上传文件通常要使用post方式;

④使用get时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用get;对于敏感数据还是应用使用post;

⑤get使用MIME类型application/x-www-form-urlencoded的URL编码(也叫百分号编码)文本的格式传递参数,保证被传送的参数由遵循规范的文本组成,例如一个空格的编码是"%20"。

补充:GET方式提交表单的典型应用是搜索引擎。GET方式就是被设计为查询用的。

2.5. 转发(Forward)和重定向(Redirect)的区别

转发是服务器行为,重定向是客户端行为。
转发(Forword) 通过RequestDispatcher对象的forward(HttpServletRequest request,HttpServletResponse response)方法实现的。RequestDispatcher可以通过HttpServletRequest 的getRequestDispatcher()方法获得。例如下面的代码就是跳转到login_success.jsp页面。

 request.getRequestDispatcher("login_success.jsp").forward(request, response);

重定向(Redirect) 是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。

  1. 从地址栏显示来说
    forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址. redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.
  2. 从数据共享来说
    forward:转发页面和转发到的页面可以共享request里面的数据. redirect:不能共享数据.
  3. 从运用地方来说
    forward:一般用于用户登陆的时候,根据角色转发到相应的模块. redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等
  4. 从效率来说
    forward:高. redirect:低.

2.6. Servlet与线程安全

Servlet不是线程安全的,多线程并发的读写会导致数据不同步的问题。 解决的办法是尽量不要定义name属性,而是要把name变量分别定义在doGet()和doPost()方法内。虽然使用synchronized(name){}语句块可以解决问题,但是会造成线程的等待,不是很科学的办法。 注意:多线程的并发的读写Servlet类属性会导致数据不同步。但是如果只是并发地读取属性而不写入,则不存在数据不同步的问题。因此Servlet里的只读属性最好定义为final类型的。

2.7. JSP和Servlet是什么关系

其实这个问题在上面已经阐述过了,Servlet是一个特殊的Java程序, 它运行于服务器的JVM中,能够依靠服务器的支持向浏览器提供显示内容。JSP本质上是Servlet的一种简易形式,JSP会被服务器处理成一个类似于Servlet的Java程序,可以简化页面内容的生成。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。有人说,Servlet就是在Java中写HTML,而JSP就是在HTML中写Java代码,当然这个说法是很片面且不够准确的。JSP侧重于视图,Servlet更侧重于控制逻辑,在MVC架构模式中,JSP适合充当视图(view)而Servlet适合充当控制器(controller)。

2.8. JSP有哪些内置对象、作用分别是什么

JSP有9个内置对象:

  1. request:封装客户端的请求,其中包含来自GET或POST请求的参数;
  2. response:封装服务器对客户端的响应;
  3. pageContext:通过该对象可以获取其他对象;
  4. session:封装用户会话的对象;
  5. application:封装服务器运行环境的对象;
  6. out:输出服务器响应的输出流对象;
  7. config:Web应用的配置对象;
  8. page:JSP页面本身(相当于Java程序中的this);
  9. exception:封装页面抛出异常的对象。

2.9. 讲解JSP中的四种作用域

JSP中的四种作用域包括page、request、session和application,具体来说:

  1. page代表与一个页面相关的对象和属性。
  2. request代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个Web组件;需要在页面显示的临时数据可以置于此作用域。
  3. session代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。
  4. application代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。

2.10. Cookie和Session的的区别

  1. 由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 Session。
  2. 思考一下服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
  3. Cookie其实还可以用在一些方便用户的场景下,设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是Cookie名称的由来,给用户的一点甜头。所以,总结一下:Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。

(三)集合框架整理

首先附两张图展现集合框架整体

collection:
在这里插入图片描述
Map:
在这里插入图片描述

3.1. List,Set,Map三者的区别及总结

  1. List:对付顺序的好帮手

List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象

  1. Set:注重独一无二的性质

不允许重复的集合。不会有多个元素引用相同的对象。

  1. Map:用Key来搜索的专家

使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。

3.2. Arraylist 与 LinkedList 区别

Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低)。
LinkedList底层使用的是双向循环链表数据结构(插入,删除效率特别高)。
学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了,因为一般数据量都不会蛮大,Arraylist是使用最多的集合类。

3.3.ArrayList 与 Vector 区别

Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector ,代码要在同步操作上耗费大量的时间。Arraylist不是同步的,所以在不需要同步时建议使用Arraylist。

3.4. HashMap 和 Hashtable 的区别

  1. HashMap是非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过synchronized修饰。
  2. 因为线程安全的问题,HashMap要比HashTable效率高一点,HashTable基本被淘汰。
  3. HashMap允许有null值的存在,而在HashTable中put进的键值只要有一个null,直接抛出NullPointerException。

3.5. HashSet 和 HashMap 区别

在这里插入图片描述

3.6.HashMap 和 ConcurrentHashMap 的区别

  1. ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
  2. HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

3.7. HashSet如何检查重复

当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。(摘自我的Java启蒙书《Head fist java》第二版)。

3.8. comparable 和 comparator的区别

  1. comparable接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序
  2. comparator接口实际上是出自 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序
    一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort().

3.9. 如何对Object的list排序

1.对objects数组进行排序,我们可以用Arrays.sort()方法
2.对objects的集合进行排序,需要使用Collections.sort()方法

3.10. 如何实现数组与List的相互转换

List转数组: list.toArray(arraylist.size())方法;
数组转List: Arrays的asList(arr)方法

3.11. 集合框架底层数据结构总结

Collection

  1. List
    Arraylist:数组(查询快,增删慢 线程不安全,效率高 )
    Vector:数组(查询快,增删慢 线程安全,效率低 )
    LinkedList:链表(查询慢,增删快 线程不安全,效率高 )
  2. Set
    HashSet(无序,唯一):哈希表或者叫散列集(hash table)
    LinkedHashSet:链表和哈希表组成 。 由链表保证元素的排序 , 由哈希表证元素的唯一性
    TreeSet(有序,唯一):红黑树(自平衡的排序二叉树。)

Map
3. HashMap:基于哈希表的Map接口实现(哈希表对键进行散列,Map结构即映射表存放键值对)
4. LinkedHashMap:HashMap 的基础上加上了链表数据结构
5. HashTable:哈希表
6. TreeMap:红黑树(自平衡的排序二叉树)

(四)多线程整理

4.1.Java多线程基础

4.1.1 进程和多线程简介

  1. 何为进程?
    进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
  2. 何为线程?
    线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程
  3. 何为多线程?
    多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。

4.1.2 几个重要的概念

  1. 同步和异步
    同步和异步通常用来形容一次方法调用。同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作。

关于异步目前比较经典以及常用的实现方式就是消息队列:在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善

  1. 并发(Concurrency)和并行(Parallelism)
    并发和并行是两个非常容易被混淆的概念。它们都可以表示两个或者多个任务一起执行,但是偏重点有些不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。而并行是真正意义上的“同时执行”。并发和并行是两个非常容易被混淆的概念。它们都可以表示两个或者多个任务一起执行,但是偏重点有些不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。而并行是真正意义上的“同时执行”。
    多线程在单核CPU的话是顺序执行,也就是交替运行(并发)。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行(并行)。

  2. 高并发
    高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。

高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。

  1. 临界区
    临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。在并行程序中,临界区资源是保护的对象。
  2. 阻塞和非阻塞
    非阻塞指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回,而阻塞与之相反。

4.1.3 使用多线程常见的四种方式

  1. 继承Thread类创建线程
    Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。 start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
  2. 实现Runnable接口创建线程
    如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口。
  3. **通过Callable和FutureTask创建线程 重写call方法(有返回值) **
    Java 5.0 在 java.util.concurrent 提供了一个新的创建执行线程的方式: 实现 Callable 接口。

Callable 接口类似于 Runnable,但是 Runnable 不会返回结果,并且无法抛出经过检查的异常,而 Callable 依赖 FutureTask 类获取返回结果。

public class CallableAndFuture {
    public static void main(String[] args) {
        Callable<Integer> callable = new Callable<Integer>() {
            public Integer call() throws Exception {
                return new Random().nextInt(100);
            }
        };
        FutureTask<Integer> future = new FutureTask<Integer>(callable);
        new Thread(future).start();
        try {
            Thread.sleep(5000);// 可能做一些事情
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
  1. 使用线程池
    使用线程池的方式也是最推荐的一种方式,另外,《阿里巴巴Java开发手册》在第一章第六节并发处理这一部分也强调到“线程资源必须通过线程池提供,不允许在应用中自行显示创建线程”
    1. 那么既然已经可以实现多线程,那么为什么需要使用线程池呢?
      虽然以上两种方式能够实现多线程,但是如果不加控制,反而会对系统造成负担。线程本身也要占用内存空间,因为jvm会为每个线程分配一定内存(JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K,也可以通过jvm参数-Xss来设置),所以当线程数达到一定数量时就报了该error。大量的线程会占用内存资源并且可能会导致Out of Memory。即便没有这样的情况,大量的线程回收也会给GC带来很大的压力。
      现在问题明朗了:java线程池是为了防止内存溢出,而不是为了加快效率。

    2. 线程池的作用
      线程池的作用就2个:

      1. **减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务**
      
      2. 可以根据系统的承受能力,调整线程池中工作线程的数据,防止因为消耗过多的内存导致服务器崩溃。
      
    3. 线程池类结构
      在这里插入图片描述
      这张图基本简单代表了线程池类的结构

1、最顶级的接口是Executor,不过Executor严格意义上来说并不是一个线程池而只是提供了一种任务如何运行的机制而已

2、ExecutorService才可以认为是真正的线程池接口,接口提供了管理线程池的方法

3、下面两个分支,AbstractExecutorService分支就是普通的线程池分支,ScheduledExecutorService是用来创建定时任务的。
Executors
java.util.concurrent.Executors工厂类可以创建四种类型的线程池,通过Executors.newXXX方法即可创建。

下面来看一下Executors为用户提供的几种线程池:(重点)
1、newSingleThreadExecutos() 单线程线程池, 它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级列队)执行。
2. newFixedThreadPool(int nThreads) 固定大小线程池 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
3. newCachedThreadPool 缓存线程池 创建一个可缓存线程池,这种线程池内部没有核心线程,线程的数量是有没限制的。(闲置状态)在超过了60S还不做事,就会销毁
4. newScheduledThreadPool 定时线程池,创建一个定长线程池,支持定时及周期性任务执行。

4.1.4 一些常用方法

  1. currentThread():返回对当前正在执行的线程对象的引用
  2. getId():返回此线程的标识符
  3. getName():返回此线程的名称
  4. setName(String name):将此线程的名称更改为等于参数 name 。
  5. getPriority():返回此线程的优先级
  6. isAlive():测试这个线程是否还处于活动状态。
    什么是活动状态呢?
    活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备运行的状态。
  7. sleep(long millis):使当前正在执行的线程以指定的毫秒数“休眠”(暂时停止执行)
  8. isDaemon():测试这个线程是否是守护线程
  9. setDaemon(boolean on):将此线程标记为 daemon线程或用户线程。
  10. join():在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是 主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
  11. yield():yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。注意:放弃的时间不确定,可能一会就会重新获得CPU时间片。
  12. setPriority(int newPriority):更改此线程的优先级
  13. interrupt():中断这个线程。
  14. interrupted() 和isInterrupted()
    interrupted(): 测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能

isInterrupted(): 测试线程Thread对相关是否已经是中断状态,但部清楚状态标志

4.1.5 线程的优先级与守护线程

线程的优先级

  1. 线程优先级具有继承特性比如A线程启动B线程,则B线程的优先级和A是一样的。
  2. 线程优先级具有随机性也就是说线程优先级高的不一定每一次都先执行完。
  3. 在默认情况下优先级都是Thread.NORM_PRIORITY(常数5)

守护线程

  1. 用户线程: 运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

  2. 守护线程: 运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 “佣人”。

  3. 特点: 一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作

  4. 应用: 数据库连接池中的检测线程,JVM虚拟机启动后的检测线程

最常见的守护线程:垃圾回收线程

如何设置守护线程?
可以通过调用Thead类的setDaemon(true)方法设置当前的线程为守护线程

未更新未完成(待后续更新)

版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/qq_44614710/article/details/86641384

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页