文章目录

  • 前言
  • 一、java基础
    • 1 java基础知识
    • 2 集合(必会)
    • 3多线程(必会)
    • 4线程池(高薪必问)
    • 5JVM(高薪必问)
  • 总结

前言

提示:回答面试题时最好面试官问一个点以后,我们自己延伸到其他问题。比如乐观锁、悲观锁。这种问题回答完以后,延伸到CAS机制,或者线程、多线程、线程池。


一、java基础

1 java基础知识

  1.1:面向对象的特征(必会)
面向对象的特征:封装、继承、多态、抽象。
   封装:就是把对象的属性和行为(数据)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节,就是把不想告诉或者不该告诉别人的东西隐藏起来,把可以告诉别人的公开,别人只能用我提供的功能实现需求,而不知道是如何实现的。增加安全性。
   继承:子类继承父类的数据属性和行为,并能根据自己的需求扩展出新的行为,提高了代码的复用性。
   多态:指允许不同的对象对同一消息做出相应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用)。封装和继承几乎都是为多态而准备的,在执行期间判断引用对象的实际类型,根据其实际的类型调用其相应的方法。
   抽象:表示对问题领域进行分析、设计中得出的抽象的概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。在Java中抽象用 abstract 关键字来修饰,用 abstract 修饰类时,此类就不能被实例化,从这里可以看出,抽象类(接口)就是为了继承而存在的。
  1.2:基本数据类型有哪些(了解)

  1.3:JDK JRE JVM 的区别(必会)

   JDK(Java Development Kit)是整个 Java 的核心,是java开发工具包,包括了 Java 运行环境 JRE、Java 工具和 Java 基础类库。
   JRE(Java Runtime Environment)是运行 JAVA 程序所必须的环境的集合,包含java虚拟机和java程序的一些核心类库。
   JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,是整个 java 实现跨平台的最核心的部分,能够运行以 Java 语言写作的软件程序。
  1.4:重载和重写的区别(了解)
   重载: 发生在同一个类中,方法名必须相同,参数类型不同.个数不同.顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。(同类同名不同参数)
   重写: 发生在父子类中,方法名.参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类, 访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。(同名同参不同类)
  1.5:java中==和equals的区别(必会)
== 的作用:
    基本类型:比较的就是值是否相同
    引用类型:比较的就是地址值是否相同
  equals 的作用:
   引用类型:默认情况下,比较的是地址值。
   特殊情况:String、Integer、Date这些类库中equals被重写,比较的是内容而不是地址!

面试题:请解释字符串比较之中 “ == ” 和 equals() 的区别?
答: ==:比较的是两个字符串内存地址(堆内存)的数值是否相等,属于数值比较; equals():比较的是两个字符串的内容,属于内容比较。
  1.6:String 、StringBuffer、StringBuilder三者之间的区别(必会)
       String 字符串常量
       StringBuffer 字符串变量(线程安全)
       StringBuilder 字符串变量(非线程安全)
   String 中的String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[] ,String对象是不可变的,也就可以理解为常量,线程安全。
   AbstractStringBuilder是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
   StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
   StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
小结:
  1)如果要操作少量的数据用 String;
  (2)多线程操作字符串缓冲区下操作大量数据用 StringBuffer;
  (3)单线程操作字符串缓冲区下操作大量数据用 StringBuilder。
  1.7:接口和抽象类的区别
   实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
   构造函数:抽象类可以有
构造函数*;接口不能有。
   main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
   实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
   访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符.
  1.8:String常用的方法都有哪些(了解)
       indexOf():返回指定字符的索引。
       charAt():返回指定索引处的字符。
       replace():字符串替换。
       trim():去除字符串两端空白。
       split():分割字符串,返回一个分割后的字符串数组。
       getBytes():返回字符串的 byte 类型数组。
       length():返回字符串长度。
       toLowerCase():将字符串转成小写字母。
       toUpperCase():将字符串转成大写字符。
       substring():截取字符串。
       equals():字符串比较。
       compareTo():比较两个字符串的字典
       hashCode:返回此字符串的哈希代码
       toCharArray():将此字符串转换为一个新的字符数组
       toString():这个对象(这已经是一个字符串!)是自己回来了。
       trim():返回一个字符串,去掉前后空格。
       lastIndexOf(();返回在指定字符的最后一个发生的字符串内的索引
  1.9:反射(必会)
   在 Java 中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的 属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息 以及动态调用对象方法的功能成为 Java 语言的反射机制。通俗点说就是解耦合。
    1):获取 Class 对象的 3 种方法 :
       1.1):调用某个对象的 getClass()方法
       Person p=new Person();
       Class clazz=p.getClass();
       1.2):调用某个类的 class 属性来获取该类对应的 Class 对象
       Person p=new Person();
       Class clazz=p.class();
       1.3):使用 Class 类中的 forName()静态方法(最安全/性能最好)
       Class clazz=Class.forName(“类的全路径”); (最常用)
    2):获取 Class 对象的构造方法 :->如果没有权限,可以使用SetAccessible
       2.1):批量获取
       获取这个类中的所有“构造方法”,包括私有、受保护的。
       public Constructor<?>[] getDeclaredConstructors()
       获取这个类中的所有“公有构造方法”
       public Constructor<?>[] getConstructors()
       2.2):单个获取
       获取这个类中的某个“构造方法”,包括私有、受保护的。
       public Constructor<?>[] getDeclaredConstructor()
       获取这个类中的某个“共有构造方法”
       public Constructor<?>[] getConstructor()
    3):获取 Class 对象的成员属性并赋值和取值 :->如果没有权限,可以使用SetAccessible
       3.1):批量获取
       获取这个类中所有的公有成员属性
       public Field[] getFields()
       获取这个类中所有的成员属性
       public Field[] getDeclaredFields()
       3.2):单个获取
       获取这个类中某个公有成员属性
       public Field getField(String name)
       获取这个类中的某个成员属性
       public Field getDeclaredField(String name)
       设置属性的值:获取到成员属性的返回值.set(Object target,Object value).
       获取属性的值:获取到成员属性的返回值.get(Object target,Object value).
    4):获取 Class 对象的成员方法 :->如果没有权限,可以使用SetAccessible
       4.1):批量获取
       获取这个类中所有的公有成员方法
       public 方法[] getMethods()
       获取这个类中所有的成员方法
       public 方法[] getDeclaredMethods()
       4.2):单个获取
       获取这个类中某个公有成员方法
       public 方法 getMethod(String name,
       获取这个类中的某个成员方法
       public 方法 getDeclaredMethod(String name)
       调用方法:获取成员方法后返回值.inuoke(Object targetObj,Object…实参列);
  1.10:什么是单例模式,有哪几种。
   单例模式:某个类的实例在多线程的环境下只会被创建一次出来
   单例模式有饿汉式单例模式、懒汉式模式、双检锁模式三种
  1.11:jdk1.8的新特性
   1:Lambda表达式
    Lambda允许把函数作为一个方法的参数。

   2:方法引用
    方法引用允许直接引用已有的java类或对象的方法或构造方法。

   3:函数式接口
   有且仅有一个抽象方法的接口叫做函数式接口,函数式接口可以被隐式转换为 Lambda 表达式。
   通常函数式接口上会添@FunctionalInterface 注解。
   接口允许定义默认方法和静态方法
   从 JDK8 开始,允许接口中存在一个或多个默认非抽象方法和静态方法。
   4:Strieam API
   新添加的 Stream API(java.util.stream)把真正的函数式编程风格引入到 Java 中。这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。
   常用方法有
   1):引用类型数组流 Stream.of();
   2):List集合中:
     遍历:forEach()终结方法,没有返回值
     遍历:过滤 filter() 拼接方法,返回Stream流
     取出前n个元素 Iimit(n) 返回一个新流对象
    跳过前n个元素 skip(n)返回一个新流对象
     转换泛型 map() 返回一个新流对象
     合并流 concat(Stream s1.Stream s2)
   5:日期/时间类改进
   之前的 JDK 自带的日期处理类非常不方便,我们处理的时候经常是使用的第三方工具包,比如 commons-lang包等。不过 JDK8 出现之后这个改观了很多,比如日期时间的创建、比较、调整、格式化、时间间隔等。
这些类都在 java.time 包下,LocalDate/LocalTime/LocalDateTime。
   6:Optional 类
   Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent()方法会返回 true,调用 get()方法会返回该对象。

  1.12:java的异常(了解)

  Throwable是所有Java程序中错误处理的父类,有两种资类:Error和Exception。
  Error:表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误,导致JVM无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。
  Exception:表示可恢复的例外,这是可捕捉到的。
  1.非运行时异常 (编译异常):是RuntimeException以外的异常** 类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

  2.运行时异常:都是RuntimeException类及其子类异常
   NullPointerException(空指针异常)
   IndexOutOfBoundsException(下标越界异常)。
    这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
常见的RunTime异常几种如下:
  NullPointerException - 空指针引用异常
  ClassCastException - 类型强制转换异常。
  IllegalArgumentException - 传递非法参数异常。
  ArithmeticException - 算术运算异常
  ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
  IndexOutOfBoundsException - 下标越界异常
      
  1.13:BIO、NIO、AIO 有什么区别?
      
  BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
  New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
  Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
      
  1.14:Threadloal的原理(高薪常问)
      
  ThreadLocal: 为共享变量在每个线程中创建一个副本,每个线程都可以访问自己内部的副本变量。通过threadlocal保证线程的安全性。
  ThreadLocal :类中有一个静态内部类ThreadLocalMap(其类似于Map),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本。
      
  1.15:同步锁、死锁、乐观锁、悲观锁(高薪常问)
      
  同步锁:当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要保证线程同步互斥,就是指并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。Java 中可以使用 synchronized 关键字来取得一个对象的同步锁。
  死锁:多个线程同时被阻塞,他们中的一个或者全部都在等待某个资源被释放。
  乐观锁: 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。(在这里可以和面试官延伸到CAS机制)
  悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。(这里就可以和面试官延伸到synchronized的实现原理、和Lock的区别,和volatile的区别。或者直接点就是多线程)
      
  1.16:说一下 synchronized 底层实现原理?
      
  synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。
  java中每一个对象都可以作为锁,这是synchronized实现同步的基础。
  普通同步方法,锁是当前实例对象。
  静态同步方法,锁是当前类的class对象
  同步方法块,锁是括号里面的对象
      
  1.17:synchronized 和 volatile 的区别是什么?
      
  volatile本质是在告诉JVM当前变量是在寄存器(工作内存)中的值是不确定的,需要从主内存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞。
  synchronized通过加锁的方式,使得其在需要原子性、可见性和有序性三个特性的时候都可以作为其中一种解决方案,看起来是万能的。volatile通过在volatile变量的操作前后插入内存屏障的方式,保证了变量在并发场景下的可见性和有序性。
  volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
  volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
      
  1.18:synchronized 和 Lock 有什么区别?
      
  首先,synchronized是java中的关键字,而Lock是一个类
  synchronized无法判断是否获取锁的状态,但是Lock可以通过lock()和unlock()方法来判断锁的状态。
  synchronized会自动释放锁(执行完相应的代码块或者出现异常就会释放锁),Lock需在finally中手工释放锁(unock()方法)。否则就会容易造成线程死锁。
  用synchronized关键字的俩个线程。线程1和线程2,如果线程1获得锁,线程2等待,,如果线程一堵塞,线程2就会一直等待下去。而这个Lock锁就不一定会等待下去,如果尝试获取不到锁。线程可以不用一直等待就结束了。
  synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
  synchronized锁适合代码少量的同步问题,而Lock锁适合大量同步的代码的同步问题。
      
  1.19:手写冒泡排序!(必会)
      


      
  1.20:final、finally、finalize的区别
      
final:最终的意识,可以修饰类,成员变量,成员方法。
  修饰类,类不能被继承
  修饰变量,变量是常量
  修饰方法,方法不能被重写。
finally:是异常处理的一部分,用来释放资源。一般来说,代码肯定会执行。特殊情况:在执行到finally之前JVM退出了
finalize:是Object类的一个方法,用于垃圾回收。
      
  1.21:如果catch语句里面有return,请问finally里面的代码还会执行嘛。如果会,请问是return前,还是return后。
     会执行,在return前。
      

2 集合(必会)

  2.1:常见的数组结构
      
     常见的数组结构有:数组、栈、队列、链表、树、散列、堆、图等

     数组是最常见的数据结构,数组的特点是长度固定,数组的大小固定后就没有办法扩容,数组只能存储一种类型的数据。添加删除的操作慢,因为要移动其他的元素
     是一种基于先进后出的数据结构,是一种只能在一段进行插入和删除操作的特殊线性表。它按照先进后出的原值存入数据,先进入的数据被压在栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个弹出来)
     队列是一种基于先进先出的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊性线表,它按照先进先出的原则存储数据,先进的数据,在读取数据时先被读取出来,
     链表是一种物理存储单元上非连续,非顺序的存储结构,其物理结构不能只表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表是有一系列的节点(链表中的每一个元素称为节点)组成,节点可以在运行时动态生成,根据指针的指向。链表能形成不同的结构,例如单链表、双向链表、循环列表等
     是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家谱、单位的组织架构等。有二叉树、平衡树、红黑树、B树、B+树
     散列表,也叫哈利表,是根据关键码和值直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。
     是计算机学科中一类特殊的数据结构的同城,堆通常可以被看作是一颗完全二叉树的数组对象。
     是由一组顶点和一组能够将俩个顶点相连的边组成的。
      
  2.2:集合和数组的区别
      
  区别:数组长度固定,集合长度可变
  数组中存储的是同一种数据类型的元素,可以存储基本数据类型,也可以存储引用数据类型
  集合村粗的都是对象,而且对象的数据类型可以不一致,在开发中一般当对象比较多的时候,使用集合来存储对象。
      
  2.3:java中集合的体系
      

  Connection接口:
    1):List:有序,可重复
     1.1)ArrayList
     优点:底层数据结构是数组。查询快,增删慢。
     缺点:线程不安全,效率高
     1.2):Vector
     优点:底层数据结构是数组查询快,增删慢。
     缺点:线程安全,效率低(已经舍弃了)。
     1.3):LinkedList
     优点:底层数据结构是链表,查询慢,增删快。
     缺点:县城不安全,效率高
    2):Set 无序,唯一
     2.1):HashSet
     底层数据结构是哈希表。(无序,唯一)
     如何来保证元素的唯一性?
     依赖俩个方法 hashCode()和equals()
     2.2):LinkedHashSet
     底层数据结构是链表和哈希表。
     由链表保证元素有序
     由哈希表保证元素唯一
     2.3):TreeSet
     底层数据结构是红黑树。(唯一,有序)
     1):如何保证元素排序的呢?
     自然排序
     比较器排序
     2):如何保证元素的唯一性的呢?
     根据比较的返回值是否是0来决定
  Map接口:
     1):HashMap
     基于hash表的Map接口实现,非线程安全、高效、支持null值和null键,线程不安全
     2):HashTable
     线程安全、低效、不支持null键和null值
    3):LinkedHashMap
     线程不安全,是HashMap的一个子类,保存了已记录的插入顺序。
     4):TreeMap
     能够把它保存的记录根据键排序,默认是键值的升序排序,线程不安全
      
  2.4:List和Map、Set的区别
      
  Listset是存储单列数据的集合,Map是存储键值对这样的双列数据的集合;
  List中存储的数据是有顺序的,并且值允许重复
  Map中存储的数据是无序的,它的是不允许重复的,但是是允许重复的;
  Set中存储的数据是无顺序的,并且并不允许重复,但元素在集合中的位置是由元素的hashcode决定,即位置是固定的(set集合是根据hashcode来进行数据存储的,所以位置是固定的,但是这个位置不是用户可以控制的,所以对于用户来说set中的元素还是无序的)。
      
  2.5:HashMap的底层原理
      
  HashMap在JDK1.8以前的实现方式是数组+链表
  但是在JDK1.8后对HashMap进行可底层优化,改为了由数组+链表+红黑树实现,只要的目的是提高查找效率

  1. Jdk8数组+链表或者数组+红黑树实现,当链表中的元素超过了 8 个以后, 会将链表转换为红黑树,当红黑树节点 小于 等于6 时又会退化为链表。
  2. 当new HashMap():底层没有创建数组,首次调用put()方法示时,底层创建长度为16的数组,jdk8底层的数组是:Node[],而非Entry[],用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,专业术语叫做扩容,在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能.
默认的负载因子大小为0.75,数组大小为16。也就是说,默认情况下,那么当HashMap中元素个数超过160.75=12的时候,就把数组的大小扩展为216=32,即扩大一倍。
  在我们Java中任何对象都有hashcodehash算法就是通过hashcode与自己进行向右位移16的异或运算。这样做是为了计算出来的hash值足够随机,足够分散,还有产生的数组下标足够随机,
  map.put(k,v)实现原理
    (1)首先将k,v封装到Node对象当中(节点)。
    (2)先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
     (3)下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal()。如果所有的equals()方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
  map.get(k)实现原理
    (1)先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
    (2)在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。
      
  2.6:HashMap和HashTable、ConcurrentHashMap区别
      
  区别对比一(HashMap和HashTable区别
1):HashMap是非线程安全的,HashTable是线程安全的。
2):HashMap的键和值都允许有null值存在,而Table不行。
3):因为线程安全的问题,HashMap效率比HashTable的要高。
4):HashTable是同步的,而HashMap不是,因此HashMap适合于单线程环境,而HashTable适合多线程环境。
5):一般不建议使用HashTable,因为是遗留类,内部实现很多没优化,即使在多线程环境下,现在也有同步的ConcurrentHashMap替代。没有必要因为是多线程而使用HashTable。
  区别对比二(HashTable和CincurrenHashMap区别):
1):HashTable 使用的是 Synchronized 关键字修饰,ConcurrentHashMap 是JDK1.7使用了锁分段技术来保证线程安全的。JDK1.8ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。
2):synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
      

3多线程(必会)

  3.1:什么事线程?线程和进程的区别
      
  线程:是进程的一个实体,是cpu调度和分配的基本单位,是比进程更少的可以独立运行的基本单位
  进程:具有一定独立功能的程序关于某个数据集合上的异常运行活动,是操作系统进行资源分配和调度的一个独立单位。
  特点:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行时各自内存单元相互独立,线程之间,内存共享,这使多线程编程可以拥有更好的性能和用户体验。
      
  3.2:创造线程有几种方式
      
  1):继承Thread类并重写run方法,实现简单但是不可以继承其他类
  2):实现Runnable接口并重写run方法,避免了单继承局限性,编程更加灵活,实现解耦。
  3):实现Callable接口并重写Call方法,创建线程,可以获取线程执行结果的返回值,并且抛出异常
  4):使用线程池创建(Executor接口)
      
  3.3:Runnable和Callable的区别
      
  Runnable接口run方法没有返回值,Callable接口call方法有返回值,支持泛型
  Runnable接口run方法只能抛出运行时异常,且无法捕获处理;Callable接口Call方法允许抛出异常,可以获取异常信息。
      
  3.4:如何启动一个新线程、调用start和run方法的区别?
      

  线程对象调用run方法不开启线程,仅是对象调用方法。
  线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行
  调用start方法可以启动线程,并且使得线程进入就绪状态,而run方法只是thread的一个普通方法,还是在主线程中执行。
      
  3.5:线程有哪几种状态,以及各种状态之间的转换?
      

  1. 第一是new->新建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
  2. 第二是Runnable->就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。
  3. 第三是Running->运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
  4. 第四是阻塞状态。阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (1)等待 – 通过调用线程的wait() 方法,让线程等待某工作的完成。
    (2)超时等待 – 通过调用线程的sleep() 或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
    (3)同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
  5. 第五是dead->死亡状态: 线程执行完了或者因异常退出了run()方法,该线程结束生命周期.
      
  3.6:线程相关的基本方法
      
  线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等

1.线程等待(wait)
  调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中
断才会返回,需要注意的是调用 wait()方法后,会释放对象的锁。因此,wait 方
法一般用在同步方法或同步代码块中。
2.线程睡眠(sleep)
  sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占
有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法
会导致当前线程进入 WATING 状态.
3.线程让步(yield)
  yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争
CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU
时间片,但这又不是绝对的,有的操作系统对 线程优先级并不敏感。
4.线程中断(interrupt)
  中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的
一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)
5.Join 等待其他线程终止
  join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方
法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变
为就绪状态,等待 cpu 的宠幸.
6.线程唤醒(notify)
  Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视 器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,
被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类 似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。
      
  3.7:wait()和sleep()的区别
      
1:来自不同的类
  wait来自Object类
  sleep来自Thread类
2:关于锁的释放
  wait在等待的过程中会释放锁
  sleep在等待的过程中不会释放锁
3:使用的范围
  wait必须是在同步代码块中使用
  sleep可以再任何地方使用
4:是否需要捕获异常
  wait不需要捕获异常;
  sleep需要捕获异常
      

4线程池(高薪必问)

  4.1:为什么需要线程池
      
  在实际使用中,线程是很占用系统资源的,如果对线程管理不完善的话很容易导致系统问题。因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主要有如下好处:
1、使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建和销毁时造成的消耗
2、由于没有线程创建和销毁时的消耗,可以提高系统响应速度
3、通过线程可以对线程进行合理的管理,根据系统的承受能力调整可运行线程数量的大小等
      
  4.2:线程池的分类
      

  1):newCachedThreadPool:创建一个可进行缓存重复利用的线程池
  2):newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,线程池中的线程处于一定的量,可以很好的控制线程的并发量
  3):newSingleThreadExecutor:创建一个使用单个 worker 线程的Executor ,以无界队列方式来运行该线程。线程池中最多执行一个线程,之后提交的线程将会排在队列中以此执行
  4):newSingleThreadScheduledExecutor:创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期执行
  5):newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
  6):newWorkStealingPool:创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传并行级别参数,将默认为当前系统的CPU个数
      
  4.3:核心参数
      
  corePoolSize:核心线程池的大小
  maximumPoolSize:线程池能创建线程的最大个数
  keepAliveTime:空闲线程存活时间
  unit:时间单位,为keepAliveTime指定时间单位
  workQueue:阻塞队列,用于保存任务的阻塞队列
  threadFactory:创建线程的工程类
  handler:饱和策略(拒绝策略)
      
  4.4:线程池的原理
      


线程池的工作过程如下:
当一个任务提交至线程池之后,

  1. 线程池首先判断核心线程池里的线程是否已经满了。如果不是,则创建一个新的工作线程来执行任务。否则进入2.
  2. 判断工作队列是否已经满了,倘若还没有满,将线程放入工作队列。否则进入3.
  3. 判断线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行。如果线程池满了,则交给饱和策略来处理任务。
  4.       

  4.5:拒绝策略
      
  ThreadPoolExecutor.AbortPolicy(系统默认): 丢弃任务并抛出RejectedExecutionException异常,让你感知到任务被拒绝了,我们可以根据业务逻辑选择重试或者放弃提交等策略
  ThreadPoolExecutor.DiscardPolicy: 也是丢弃任务,但是不抛出异常,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
  ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程),通常是存活时间最长的任务,它也存在一定的数据丢失风险
  ThreadPoolExecutor.CallerRunsPolicy:既不抛弃任务也不抛出异常,而是将某些任务回退到调用者,让调用者去执行它。
      
  4.6:线程池的关闭
      
关闭线程池,可以通过shutdown和shutdownNow两个方法
原理:遍历线程池中的所有线程,然后依次中断
  1、shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;
  2、shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程
      

5JVM(高薪必问)

虚拟机,一种能够运行java字节码的虚拟机。
      
  5.1:JDK1.8 JVM运行时内存


程序计数器:
线程私有的(每个线程都有一个自己的程序计数器), 是一个指针. 代码运行, 执行命令. 而每个命令都是有行号的,会使用程序计数器来记录命令执行到多少行了.
Java虚拟机栈:
线程私有的(每个线程都有一个自己的Java虚拟机栈). 一个方法运行, 就会给这个方法创建一个栈帧, 栈帧入栈执行代码, 执行完毕之后出栈(弹栈)
本地方法栈:
线程私有的(每个线程都有一个自己的本地方法栈), 和Java虚拟机栈类似, Java虚拟机栈加载的是普通方法,本地方法加载的是native修饰的方法.
native:在java中有用native修饰的,表示这个方法不是java原生的.
堆:
线程共享的(所有的线程共享一份). 存放对象的,new的对象都存储在这个区域.还有就是常量池.
元空间: 存储.class 信息, 类的信息,方法的定义,静态变量等.而常量池放到堆里存储
JDK1.8和JDK1.7的jvm内存最大的区别是, 在1.8中方法区是由元空间(元数据区)来实现的, 常量池.
1.8不存在方法区,将方法区的实现给去掉了.而是在本地内存中,新加入元数据区(元空间).
      
  5.2:JDK1.8堆内存结构


Young 年轻区(代): Eden+S0+S1, S0和S1大小相等, 新创建的对象都在年轻代
Tenured 年老区: 经过年轻代多次垃圾回收存活下来的对象存在年老代中.
Jdk1.7和Jdk1.8的区别在于, 1.8将永久代中的对象放到了元数据区, 不存永久代这一区域了.
      
5.3:Gc垃圾回收**
  JVM的垃圾回收动作可以大致分为两大步,首先是「如何发现垃圾」,然后是「如何回收垃圾」。说明一点, 线程私有的不存在垃圾回收, 只有线程共享的才会存在垃圾回收, 所以堆中存在垃圾回收.

  5.3.1 如何发现垃圾
  Java语言规范并没有明确的说明JVM使用哪种垃圾回收算法,但是常见的用于「发现垃圾」的算法有两种,引用计数算法和根搜索算法。
1.引用计数算法
  该算法很古老(了解即可)。核心思想是,堆中的对象每被引用一次,则计数器加1,每减少一个引用就减1,当对象的引用计数器为0时可以被当作垃圾收集。
  优点:快。
  缺点:无法检测出循环引用。如两个对象互相引用时,他们的引用计数永远不可能为0。
2.根搜索算法(也叫可达性分析)
  根搜索算法是把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即可以当作垃圾。
  Java中可作为GC Root的对象有
    1.虚拟机栈中引用的对象
    2.本地方法栈引用的对象
    2.方法区中静态属性的引用对象
    3.方法区中常量的引用对象

  5.3.2 如何回收垃圾
  Java中用于「回收垃圾」的常见算法有4种:
1.标记-清除算法(mark and sweep)
  分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。
  缺点:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片。
2.标记-整理算法
  是在标记-清除算法基础上做了改进,标记阶段是相同的,但标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
  优点:内存被整理后不会产生大量不连续内存碎片。
3.复制算法(copying)
  将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。
  缺点:可使用的内存只有原来一半。
4.分代收集算法(generation)
当前主流JVM都采用分代收集(Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为年轻代、年老代、永久代,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。


(1)年轻代(Young Generation)
  1.所有新生成的对象首先都是放在年轻代的。
  2.新生代内存按照8:1:1的比例分为一个eden区和两个Survivor(survivor0,survivor1)区。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
  3.当survivor1区不足以存放eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
  4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)
(2)年老代(Old Generation)
  1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
  2.内存比新生代也大很多(大概是2倍),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率比较高。
(3)持久代(Permanent Generation)
  用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,从JDK8以后已经废弃, 将存放静态文件,如Java类、方法等这些存储到了元数据区.
  5.4:JVM调优参数
  这里只给出一些常见的性能调优的参数及其代表的含义。(大家记住5.6个就行, 并不需要都记住.)

  -Xmx3550m:设置JVM最大可用内存为3550M。
  -Xms3550m:设置JVM初始内存为3550m。注意:此值一般设置成和-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
  -Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
  -Xss256k:设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,以前每个线程栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。

  -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4。(该值默认为2)
  -XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4。

总结

这里是最基本的java基础面试题。下一章Web面试题

2020年Java阶段面试题(初)相关推荐

  1. 2020最新Java集合面试题

    2020最新Java集合面试题 1.ArrayList和linkedList的区别 Array(数组)是基于索引**(index)**的数据结构,它使用索引在数组中搜索和读取数据是很快的. Array ...

  2. 2020年Java最新面试题整理题库完整版

    JAVA面试题 一.JavaSE编程基础 JDK,JRE,JVM三者关系 a. .jdk是JAVA程序开发时用的开发工具包,其内部也有JRE运行环境JRE. b. .JRE是JAVA程序运行时需要的运 ...

  3. 年度盘点:2020年Java后端面试题整理(大专5面凉凉)

    前言 致即将过去的2020年! 对于生命垂危的病人来说,时刻就是宝贵的生命;对于做生意的人来说,时刻就是财富,有的人说时刻就像是从山坡上流下来的水,从何处吹来的风,走了就不再回来了;而对于正在工作的我 ...

  4. 爱奇艺2020校招Java方向笔试题(第一场)

    1. 计算下列程序的时间复杂度(B) for (i=1;i<n;i++)for(j=1;j<m;j++){a1,a2,a3,a4}; A. O(n) B. O(nm) C. O(m) D. ...

  5. 考研英语真题笔记 2020,Java数据结构面试题及答案

    They housed(给-房子住) eight adult rats with two types of robotic rat(机械老鼠) - one social(社会的,社交的) and on ...

  6. 爱奇艺2020校招Java方向笔试题(第二场)

    1. 以下关于synchronized描述不正确的是(C) A. 当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程对该对象的该synchronized方 ...

  7. 2020年Java常问面试题--聂

    2020年Java常问面试题 打算这几天每天更新15~20题.(这样有助于你们阅读和理解!)我们先从简单的开始 加qq群:568680919,免费赠送1000道java面试题和简历模板 JDK 和 J ...

  8. 阿里2020春招Java实习生笔试题

    阿里2020春招Java实习生笔试题 第1题 第2题 今天上午参加了阿里实习笔试,2个编程题(60分钟),做的简直是惨不忍睹,一个都没出来.虽然和面试无缘了,但希望能帮到后面笔试的同学,所以将题目大概 ...

  9. 2020 Java工程师面试题汇总

    前言 2020,不平凡的一年. 经过一个多月的面试,整理了一些面试题,分享出来.不敢说对大家能有多大帮助,至少可以查漏补缺吧. 里面很多东西,写得很长,并不是说要死记硬背,而是要理解,一次不懂,多看几 ...

最新文章

  1. 如何让一滴水不蒸发?
  2. IOS开发高级之点餐系统的实现-01
  3. 人体的血管连起来竟能绕地球两圈!?| 今日最佳
  4. Java泛型中的多态
  5. html悬浮弹窗后面背景变深,JS+CSS实现Div弹出窗口同时背景变暗的方法
  6. 解决webstorm中vue语法没有提示
  7. 解析几何 —— 经典题解
  8. C++类模版demo
  9. 达梦数据库DM7入门教程-安装
  10. solidity 中的时间_智能合约语言 Solidity 教程系列7 - 以太单位及时间单位
  11. 对抗样本:知其然,知其所以然
  12. 04 可视化开发工具
  13. 制作maven模板框架
  14. Linux .swp文件恢复
  15. Hadoop Java对应版本号
  16. 中兴力维动环监控接线图_中兴力维传统动环监控系统解决方案
  17. 电脑桌面没有wifi图标了导致连接不上网络,解决方法
  18. An internal error occurred during: Validating SYYH. Java heap space
  19. android 动态替换logo
  20. 全面布局安全可靠 听听东软怎么说!

热门文章

  1. Bilateral Filters(双边滤波算法)的超简单原理,学不会你打我。
  2. Open3D 点云双边滤波
  3. 记事本写的html文件保存到c:\inetpub\wwwroot失败
  4. 2019数据科学/人工智能比赛作品解决方案合集
  5. 怪盗基德的滑翔翼【从小白解法到DP解法】
  6. 青职院人工智能03机器学习基础(二)
  7. 验证码 html 自动验证码,15分钟搞定自动识别网站验证码
  8. Netflix第四季度营收77亿美元 订阅用户增长放缓盘后暴跌19%
  9. c语言——程序出现C4996:scanf 等错误的解决方法
  10. 面了三十个人,说说真实感受