1. 1. temp
    1. 1.1. 说说final,finally,finalize
    2. 1.2. finalize使用的场合是什么
    3. 1.3. finally什么情况下不会执行
    4. 1.4. JDK1.8新增了非常多的特性,本专题主要讨论以下几个
    5. 1.5. 多线程读多写少怎么优化
    6. 1.6. 在 Java 中定义一个不做事且没有参数的构造方法的作用
    7. 1.7. 如何跳出多层循环
  2. 2. 基本数据类型
    1. 2.1. 讲一下包装类型和基本类型的区别?什么是自动拆箱和装箱?
    2. 2.2. 可能会到缓存池那里
  3. 3. 修饰符
    1. 3.1. 访问修饰符
  4. 4. 函数
    1. 4.1. 为什么只有值传递?
    2. 4.2. 重载和重写的区别?
    3. 4.3. 静态方法为什么不能重写
    4. 4.4. 深拷贝与浅拷贝的区别?
  5. 5. 泛型
    1. 5.1. 什么是泛型?
    2. 5.2. 为什么是伪泛型?
    3. 5.3. 都有什么泛型?
    4. 5.4. ?extend Object 和?Super的区别?
  6. 6. Object 方法
    1. 6.1. ==和equals的区别
    2. 6.2. 为什么重写 equals 时必须重写 hashCode 方法?(和HashMap、HashSet查找有关)
  7. 7. 面向对象
    1. 7.1. 面向对象的三大特性?
    2. 7.2. 面向对象和面向过程的区别
    3. 7.3. 成员变量和局部变量的区别?
    4. 7.4. 静态方法和实例方法有什么不同?
    5. 7.5. 接口和抽象类的不同?
  8. 8. String
    1. 8.1. String的subString
    2. 8.2. StringBuffer SringBuilder String 区别
  9. 9. 反射
    1. 9.1. 什么是反射?
    2. 9.2. 知道获取Class对象的几种途径
    3. 9.3. 通过Class对象创建出对象,获取出构造器,成员变量,方法
    4. 9.4. 通过反射的API修改成员变量的值,调用方法
    5. 9.5. 反射优缺点:
  10. 10. java异常体系
    1. 10.1. Throwable 类常用方法
    2. 10.2. 写代码的时候怎么处理异常,继承自Exception还是RuntimeException
    3. 10.3. Error能被捕获吗?
  11. 11. 集合
    1. 11.1. ArrayList扩容机制grow
    2. 11.2. ArrayList Add过程
    3. 11.3. Hashmap底层实现原理
    4. 11.4. Hashmap底层实现原理
    5. 11.5. TreeSet的底层数据结构
    6. 11.6. CopyOnWriteArrayList
    7. 11.7. Java集合的快速失败机制 “fail-fast”?
    8. 11.8. comparable 和 Comparator 的区别
    9. 11.9. HashMap 和TreeMap的区别
    10. 11.10. HashMap和HashTable区别
    11. 11.11. HashTable和ConcurrentHashMap区别
    12. 11.12. ConcurrentHashMap中如何用的CAS和Synchronized
  12. 12. 多线程
    1. 12.1. 进程线程的区别?
    2. 12.2. 线程的基本状态
    3. 12.3. 为什么要多线程?
    4. 12.4. 为什么程序计数器、本地方法站、虚拟机栈是私有的?
    5. 12.5. 多线程可能遇到的问题?
    6. 12.6. 并发编程三要素?
    7. 12.7. 上下文是什么?切换?
    8. 12.8. Java如何安全地停止一个线程(不等它运行结束)
    9. 12.9. 创建线程的四种方式?
    10. 12.10. Callable的创建过程?
    11. 12.11. runnable 和 callable 有什么区别?
    12. 12.12. run()和start()的区别?
    13. 12.13. FutureTask原理?
    14. 12.14. Sleep和wait的区别
    15. 12.15. 你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?
    16. 12.16. 为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
    17. 12.17. 为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?
    18. 12.18. 为什么 Thread 类的 sleep()和 yield ()方法是静态的?
    19. 12.19. 为什么要线程池?
    20. 12.20. 线程池介绍
    21. 12.21. 创建线程池的方式
    22. 12.22. 什么是 Executor 框架?为什么使用 Executor 框架?
    23. 12.23. 自定义线程池ThreadPoolExecutor线程池的参数
    24. 12.24. Executors创建的线程池的类型
      1. 12.24.1. 线程数固定的线程池 Fixed Thread Pool
      2. 12.24.2. 可缓存的线程池 Cached Thread Pool
      3. 12.24.3. 单线程池
      4. 12.24.4. 大小无限线程池
    25. 12.25. 线程池有什么优点?
    26. 12.26. 线程池的状态
    27. 12.27. 线程池中 submit() 和 execute() 方法有什么区别?
    28. 12.28.
    29. 12.29. 如何定义一个注解
    30. 12.30. 哪里用到了线程池?
    31. 12.31. yield()方法
    32. 12.32. System.out.println()线程安全?
  13. 13. IO流
    1. 13.1. 流的划分
    2. 13.2. 四个抽象基类
    3. 13.3. JAVA中的NIO

Java基础

temp

说说final,finally,finalize

final 分别修饰类 方法 变量的时候

finalize使用的场合是什么

被废弃了已经,不建议使用。GC在回收对象是先将其加入一个队列,然后执行finalize方法在该方法可以实现自救,但finalize只能自己调用一次

finally什么情况下不会执行

1 try语句没有执行

2 在try中有System.exit(0)这种语句,会是JVM退出

3 在守护线程中Daemond

JDK1.8新增了非常多的特性,本专题主要讨论以下几个

  • Lambda表达式:Lambda允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
  • 方法引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 默认方法:默认方法就是一个在接口里面有了一个实现的方法。
  • 新工具:新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
  • Stream API:新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
  • Date Time API:加强对日期与时间的处理。
  • Optional类:Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
  • Nashorn,JavaScript引擎:JDK1.8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

多线程读多写少怎么优化

多读少写的场景 如何提高性能https://blog.csdn.net/u013452337/article/details/90238052

ReentrantedLock(读读、读写、写写都是互斥)->ReentrantReadWriteLock(改进了读读,读写、写写还是互斥)->StampeddLock(允许在读的时候获得写锁,然后读锁阻塞)读写锁 但是会造成饥饿问题

https://blog.csdn.net/qq_33220089/article/details/105173632

Mysql悲观锁乐观锁https://blog.csdn.net/qq_33220089/article/details/103920324

使用读写锁在CopyOnWriteArrayList中,写是往拷贝的数组中写,读是读原来的,写完后在赋值给原来的。通过volatile可见性实现。

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

如果子类有一个无参构造函数,而父类没有无参构造函数、只有有参构造函数,则会编译不通过,子类的无参需要先调用父类的无参,而父类有了有参后,默认的无参就不生效了

自己整理的题目以及答案

如何跳出多层循环

使用带有标号的break

1
break a;

基本数据类型

讲一下包装类型和基本类型的区别?什么是自动拆箱和装箱?

java有八大基本类型,short、int、long、float、double、boolean、byte、char 对于这个八个基本类型都有对应的包装类型,比如int Integer、Short与short、char与Character。基本类型到包装类型就是装箱、包装类型到基本类型就是拆箱。

对于Integer 来说, Integer i=1 调用了Intteger.valueOf()方法; int n=i 调用了Integer.intValue()方法;

可能会到缓存池那里

Byte,Short,Integer,Long,Character,Boolean;前面 4 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,Character 创建了数值在[0,127]范围的缓存数据,Boolean 直接返回 True Or False。

为了性能和资源之间平衡的最好结果。

修饰符

访问修饰符

默认是同一个包内可见

protect是同一个包内+所有包的子类

函数

为什么只有值传递?

程序设计语言来说,有值传递和引用传递;

在Java中,函数得到的都是对对象引用的一个拷贝;对于其他设计语言来讲,比如C++ 如果传递的是& 则函数收到的就是这个对象。

重载和重写的区别?

重载是指对象中可以有很多相同方法名相同的函数,根据不同的输入参数,可以做不同的事。方法签名包括方法名与参数。重载函数可以返回类型和访问修饰符可以不同不能有两个方法签名相同但是返回类型不同的方法。就是不能依靠返回类型来判别。

重写是指子类对父类继承函数的重新编写,方法名、参数都必须相同。返回值必须比父类的更小或相等,抛出异常的范围要小于父类,访问修饰符的权限要大于等于父类。如果父类方法被final private static 修饰,不能被重写,对于static的函数可以重新声明,代表子类的静态函数。

返回值必须更小(子类)或者相等是指比如父类返回List 子类可以返回ArrayList (对于Void 和基本数据类型不可更改,只能和父类相同)

抛出异常: 也是指抛出的要是父类异常的子类

静态方法为什么不能重写

静态属性和静态方法只是可以继承没有表现出多态性。根据《java编程思想》中的描述这是因为静态方法和静态属性没有采用动态绑定。即使你定义了相同的静态方法名,他也只是子类和父类的两个方法,并没有继承的关系,并且也会默认隐藏父类的静态方法。具体表现就是,将子类实例向上转型则会调用到基类中的静态方法和属性,不转型就调用子类自身的静态方法和属性。编译器不推荐通过实例去调用静态方法和属性,因为这种调用方式容易造成混淆。

可以通过子类去调用父类的静态方法,但是子类如果有相同名称的静态方法,会默认隐藏父类的静态方法

深拷贝与浅拷贝的区别?

对于基本属于类型来讲,这两个拷贝是一样的都是拷贝的值。

对于引用数据类型来讲,浅拷贝是指只拷贝当前对象对于对象中的各个属性对象不在继续拷贝。对于深拷贝来讲不仅拷贝当前对象,对于对象中的各个属性也会递归拷贝。

例如:有一个Student 中有teacher属性,对student 进行浅拷贝后,这两个student是不同的对象但是其中的teacher却指向了相同的对象。如果是深拷贝则会指向不同的对象。

泛型

什么是泛型?

泛型就是编写模板代码来适应任意类型

为什么是伪泛型?

Java泛型是伪泛型,对于List和List经过JVM编译后都会变成List 里面是通过Object来存储,所以对于List我们可以通过反射来向里面添加String而不报错。

都有什么泛型?

类的泛型,对接口的泛型,对方法的泛型;

?extend Object 和?Super的区别?

?代表所有

?extend C是指继承于C的都可以,确定了上限

?Super C是指是C的父类都可以,确定了下限

Object 方法

equals hashcode wait notify

==和equals的区别

为什么重写 equals 时必须重写 hashCode 方法?(和HashMap、HashSet查找有关)

其实简单的说就是为了保证同一个对象,保证在equals相同的情况下hashcode值必定相同,如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode确实不相同的。

因为Object的Hashcode方法是native方法有JVMC或C++实现的,如果重写了equals方法而没有重写hashxcode 有可能出翔equals相等而hashcode不同。

面向对象

面向对象的三大特性?

继承、子类拥有父类的所有属性和方法,但是被private修饰的不能访问

封装、

多态 程序中定义的引用类型调用的方法在编译时并不能确定,需要在运行时确定,具体表现就是通过父类来指向子类实现多态 或者通过接口来实现多态

面向对象和面向过程的区别

面向对象是一种 对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。

面向过程 (Procedure Oriented) 是一种 以过程为中心 的编程思想。这些都是以什么正在发生为主要目标进行编程,不同于面向对象的是谁在受影响。

成员变量和局部变量的区别?

语法上 成员变量属于类 局部变量属于方法 成员变量可以用访问修饰符修饰 局部变量不能

内存分配上 成员变量在堆中 局部变量在栈中 如果局部变量是对象的话,也是在堆中分配,栈帧中存放一个引用 (可以分一下引用类型和基本类型)

生存时间:成员变量随着类 局部变量随着方法

成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。

静态方法和实例方法有什么不同?

静态方法属于类。对于静态方法其不能访问非静态属性,因为调用该静态方法的时候,该类可能没有对象,成员变量还没有初始化。可以通过类名和对象名来调用。

实例方法属于对象。只能通过对象名来调用实例方法

接口和抽象类的不同?

含义上:而抽象类是对类的抽象,用于捕捉子类共同的特征。

抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。

从抽象来看 接口定义了一组行为规范,实现该接口的必须都要实现接口定义的方法

abstract class 表示的是is a关系,interface表示的是like a关系。

可以实现多个接口但只能继承一个抽象类。

语法上:

抽象类必修要有一个abstract方法,且抽象类不能被实例化,其余和普通方法一样,可以实现静态方法与普通方法、普通变量、静态变量、常量;

接口不能拥有普通方法与普通变量,但可以有默认方法实现,静态变量静态方法;

接口的默认访问权限是public,且不能有其他访问权限进行修饰;

String

String的subString

先判断类型lantin1还是utf16 然后分别调用StringLantin1.newString(byte数组,起始位置,长度)或者StringUtf16.newString()

StringBuffer SringBuilder String 区别

String类是被final修饰表示不可继承,其成员变量byte数组被private final修饰被final修饰指向数组不变,private并且没有提供修改数组的方法则整个 字符串是不i可变的。因为String的不可变性,我们每次对字符串操作其实都要生成一个新的字符串,然后指向新的字符串。会产生大量临时对象,然后GC会便麻烦。

builder和buffer中都是没有final修饰char数组,StringBuilder、StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。

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

反射

什么是反射?

对于任意一个类我们都能知道这个类的属性和方法,对于任意一个对象我们都能调用它的任意一个属性和方法。这种动态获取信息以及动态调用方法的功能称为反射;

知道获取Class对象的几种途径

(类名.class、Class.forname()、对象.getClass()

通过Class对象创建出对象,获取出构造器,成员变量,方法

.getPackage()、getModifiers()、getName()、getSimpleName()、getSuperclass()

获取类属性
getFields() //获取所有的公共(public)字段包括父类 返回Field[]数组
getDeclaredFields() // 获取所有声明的字段(不包括父类) 返回Field[]
getField(String name) // 获取指定的公共字段包括父类 返回Field
getDeclaredField(String name) // 获取指定的声明的字段(不包括父类) 返回Field

通过反射的API修改成员变量的值,调用方法

操作类属性
** 操作静态属性
类属性对象.get(null) //返回静态属性的值
类属性对象.set(null,”值”) //赋值
** 操作非静态属性
类属性对象.get(Object obj);
类属性对象.set(Object obj,”值”);

反射优缺点:

比较灵活,反射是框架设计的灵魂,

速度会变慢

java异常体系

​ java异常都是继承于Throwable,分为Exception和Error。错误一般都是很严重的故障,通常不用捕获Error,比如OOM StackOverFlow

​ Exception分为runtime exception和IO Exception,比如Index OurofBound NullPointer。

Exception还可以按可检查与不可检查来分类,可检查的Exception,如果对其没有做try 或者throws不能通过编译。class not found filenot found 。

RuntimeException 及其子类都统称为非受检查异常

IO异常和SQL异常是受检查异常。除了runtime其余的都是受检查的异常。

Throwable 类常用方法

  • public string getMessage():返回异常发生时的简要描述
  • public string toString():返回异常发生时的详细信息
  • public string getLocalizedMessage():返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
  • public void printStackTrace():在控制台上打印 Throwable 对象封装的异常信息

写代码的时候怎么处理异常,继承自Exception还是RuntimeException

通常业务类异常如果希望它是Throwable的,或者带有业务语义的异常,就定义checked;系统类异常就unchecked。

如果我们的服务是很稳定的,可以继承自RuntimeException,不需要try catch去处理。如果不是稳定的,需要调用它的人对逻辑进行补充,那么可以继承自Exception。

Error能被捕获吗?

Error跟Exception一样都继承自Throwable,是指不应该被捕获的严重错误。实际上在代码中是可以被catch的。但是没用

集合

ArrayList扩容机制grow

1
2
3
4
5
6
7
8
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}

private Object[] grow() {
return grow(size + 1);
}

grow函数调用grow(size+1)在另一个里面会调用newCapacity(size+1),然后返回新的数组长度(原来的+原来的右移一位),然后调用Arrayss.copyOf()

ArrayList Add过程

1
2
3
modCount++;
add(e, elementData, size);
return true;

一个普通的add 会调用私有的add

先判断当前数组的容量是否和长度相同,相同则表示已经满了需要扩容调用grow

然后elementData[size]=e;

size++;

Hashmap底层实现原理

  1. 底层结构

    内部是Entry数组,Entry包含四个字段,hash值,val,key,next;

    数组中每一个位置都当成一个桶,在桶内使用拉链法解决哈希冲突的问题。

    数组的大小为2的幂,默认16,寻址可以通过取余,但是太慢,当size为2的幂的时候,通过与size-1 & 按位与会和取余得到相同的结果还很快;扩容的时候一个拆两个也会更容易。在分库的时候一般也是按2的幂来分库,更容易扩展。

  2. put调用putval,1.8链表并不是头插法,是尾插入,要先比较有无key相同的情况。

  3. 扩容

    当map中的Entry数量大于阈值,就表明可能存在很多哈希冲突,导致性能下降,会进行扩容。

    先获取原数组+原容量+原阈值 获得新大小(对容量为0等情况进行了考虑)普通情况下得到2倍容量,然后将原数组映射到新数组,再指向新数组,映射是将hash与size与,因为size的后面全是0 的得到的都是0,第一位是1,通过这样可以快速将其分为高位和低位。

  4. 树化 每putVal之后会检查当前桶的大小是否大于8 大于8并且总的数组大小大于64会将该桶树化,数组大小小于64的话会扩容,因为扩容也会将该桶内的节点稀释到高位桶中。

  5. 加载因子用于控制HashMap的疏密程度,如果Entry数量/数组大小>加载因子,表明将有很多哈希冲突,需要扩容

  6. 计算哈希值 null返回0 否则和hashcode的高16位异或。

    1
    (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  7. get方法,先判断数组是否为空 数组的长度是否大于0,在数组的那个节点是否为null ,然后检查第一个节点是不是,然后判断结构,分别调用tree的方法和链表遍历判断equals

capacity+阈值+扩容

Hashmap底层实现原理

  1. size方法 size不仅要得到当前的大小,还需要得到当前CAS失败的数量。

TreeSet的底层数据结构

使用TreeMap实现,和HashSet一样

CopyOnWriteArrayList

1
private transient volatile Object[] array;

写/删时加锁,并且写的时候写道副本里,然后往副本写,写完后将原指针指向副本。

读直接读。

实现了读写分离。

Java集合的快速失败机制 “fail-fast”?

是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。

例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。

原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

解决办法:

在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。

使用CopyOnWriteArrayList来替换ArrayList

comparable 和 Comparator 的区别

  • comparable 接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序
  • comparator接口实际上是出自 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序

HashMap 和TreeMap的区别

实现 NavigableMap 接口让 TreeMap 有了对集合内元素的搜索的能力。

实现SortMap接口让 TreeMap 有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序,不过我们也可以指定排序的比较器。示例代码如下:

HashMap和HashTable区别

HashTable是线程安全的,为每个方法加了Synchronized。HashTable继承于字典类。

Hash Table 中不能存在null 。HashMap中的key和value都可以是null

计算Hash的方式不同,hashtable是使用对象的hashcode,hashmap重新计算了hash值,通过和前面16位异或

计算索引的方法不同:hashtable是取余,hashmap是与length按位与&

扩容hashtable 长度必须是奇数,减少hash碰撞;hashmap的2的幂

HashTable和ConcurrentHashMap区别

HashTable粗暴的为每个方法加了Synchronized,为整个数组加了锁,效率很低。

ConHashMap在1.7中,将整个数组分为很多Segment,每个Segment中有几个Node,每次只对一个Segment加锁。在1.8中,取消了segment的使用,为每个节点使用CAS和Synchronized保证并发。

ConcurrentHashMap中如何用的CAS和Synchronized

put方法中,先判断key地方位置是否为空,如果是空就cas写入,如果不为空则需要加锁来对这个链表写入。

多线程

进程线程的区别?

进程是程序的一次执行,是系统运行程序的基本单位,相对于程序来讲他是动态的。进程是系统进行资源分配的基本单位,系统会为

线程是进程的一个执行单元,是进程内的调度实体,是系统进行资源调度的基本单位。

进程之间是互相独立的地址空间、独立的资源、同一个进程的多个线程之间共享本进程的地址空间,线程共享本进程的资源如内存、I/O、cpu等。在Java中,多个线程共享进程的方法区资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈

进程切换时,消耗的资源大,线程切换时消耗的资源小。

线程之间通信更加方便,同一个进程下,线程共享全局变量,静态变量等数据,但是需要处理多线程并发的问题。

线程的基本状态

新建NEW 还没有调用start方法

可运行-运行RUNNABLE

阻塞 等待锁的时候进入阻塞状态 join

等待 wait() 通过notify()唤醒

超时等待 sleep(1000) wait(1000) 通过notify()唤醒

终止 线程结束

为什么要多线程?

为什么程序计数器、本地方法站、虚拟机栈是私有的?

多线程可能遇到的问题?

死锁、内存泄漏、不安全

并发编程三要素?

原子 可见 有序

上下文是什么?切换?

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。

当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换

Java如何安全地停止一个线程(不等它运行结束)

共享变量或者 interrupt() 然后再run中不断判断isInterrupt()

创建线程的四种方式?

thread runnable callable 线程池

Callable的创建过程?

  1. 创建实现Callable接口的类myCallable
  2. 以myCallable为参数创建FutureTask对象
  3. 将FutureTask作为参数创建Thread对象
  4. 调用线程对象的start()方法

runnable 和 callable 有什么区别?

  1. 返回值callable 通过futuretask.get()获取,且会阻塞当前主进程
  2. runnable只能抛出运行时异常且无法捕获处理
  3. callable的call方法允许抛出异常,可以获得异常信息。

run()和start()的区别?

start()方法用于启动线程,run()方法是线程体。run可以重复调用,而start只能调用一次。我们如果单独的调用run方法就相当于调用了普通的函数。

new一个线程进入到新建状态,start后进入可运行状态,在时间片到了之后就可以执行run方法了

FutureTask原理?

FutureTask用来表示一个异步执行任务的结果,可以通过get方法获得该结果,如果还没有完成会 阻塞。

FutureTask本身也实现了callable, 可以用它本身来构建futuretask

Sleep和wait的区别

wait是Object的方法,sleep是线程的方法

wait 释放锁sleep不释放锁

wait后需要notify来唤醒 wait(long time)会自动唤醒但是会争夺锁如果没有获得锁无法继续向下执行

sleep自动唤醒

你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。

wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:

为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?

Java中,任何对象都可以作为锁,并且 wait(),notify()等方法用于等待对象的锁或者唤醒线程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。

wait(), notify()和 notifyAll()这些方法在同步代码块中调用

有的人会说,既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面啊,新定义的线程继承于Thread类,也不需要重新定义wait()方法的实现。然而,这样做有一个非常大的问题,一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是不能实现,只是管理起来更加复杂。

综上所述,wait()、notify()和notifyAll()方法要定义在Object类中。

为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?

当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。

为什么 Thread 类的 sleep()和 yield ()方法是静态的?

Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。

为什么要线程池?

因为对象的创建和销毁是很花费时间的,因为创建一个对象需要获取内存和其它资源。对于线程对象也是如此,所以我们将若干线程放入到线程池中,然后如果有任务来就把任务给线程去执行,减少线程创建和销毁的时间。

线程池介绍

线程池为线程生命周期的开销和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。

创建线程池的方式

new ThreadPoolExcutor()

使用Excutors工具类创建

什么是 Executor 框架?为什么使用 Executor 框架?

使用Executors工具类可以很方便的创建线程池。

自定义线程池ThreadPoolExecutor线程池的参数

https://juejin.cn/post/6844903475197788168

1
2
3
4
5
6
7
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
  • corePoolSize=> 线程池里的核心线程数量

  • maximumPoolSize=> 线程池里允许有的最大线程数量

  • keepAliveTime=> 空闲线程存活时间

  • unit=> keepAliveTime的时间单位,比如分钟,小时等

  • workQueue=> 缓冲队列

  • threadFactory=> 线程工厂用来创建新的线程放入线程池

  • handler=> 线程池拒绝任务的处理策略,比如抛出异常等策略

  • 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务;

    如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中;

    如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务;

    如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

Executors创建的线程池的类型

java.util.concurrent.Executors 提供了一系列静态方法来创建各种线程池。下面例举出了主要的一些线程池及特性,其它未例举线程池的特性可由下面这些推导出来。

线程数固定的线程池 Fixed Thread Pool

顾名思义,这种类型线程池线程数量是固定的。如果线程数量设置为n,则任何时刻该线程池最多只有n个线程处于运行状态。当线程池中处于饱和运行状态时,再往线程池中提交的任务会被放到执行队列中。如果线程池处于不饱和状态,线程池也会一直存在,直到ExecuteService 的shutdown方法被调用,线程池才会被清除。

1
// 创建线程数量为5的线程池。``ExecutorService executorService = Executors.newFixedThreadPool(``5``);

它使用的阻塞队列是Linked 无限大,所以可能会堆积大量任务 然后OOM

可缓存的线程池 Cached Thread Pool

这种类型的线程池初始大小为0个线程,随着往池里不断提交任务,如果线程池里面没有闲置线程(0个线程也表示没有闲置线程),则会创建新的线程,保证没有任务在等待;如果有闲置线程,则复用闲置状态线程执行任务。处于闲置状态的线程只会在线程池中缓存60秒,闲置时间达到60s的线程会被关闭并移出线程池。在处理大量短暂的(官方说法:short-lived)异步任务时可以显著得提供程序性能。

1
//创建一个可缓存的线程池 ``ExecutorService executorService = Executors.newCachedThreadPool();

他会构建一个 最大线程为无限大的线程池

单线程池

这或许不能叫线程池了,由于它里面的线程永远只有1个,而且自始至终都只有1个(为什么说这句话,因为要和 Executors.newFixedThreadPool(1) 区别开来),所以还是叫它“单线程池把”。你尽可以往单线程池中添加任务,但是每次只执行1个,且任务是按顺序执行的。如果前面的任务出现了异常,当前线程会被销毁,但1个新的线程会被创建用来执行后面的任务。以上这些和线程数只有1个的线程Fixed Thread Pool一样。两者唯一不同的是, Executors.newFixedThreadPool(1) 可以在运行时修改它里面的线程数,而 Executors.newSingleThreadExecutor() 永远只能有1个线程。

1
//创建一个单线程池``ExecutorService executorService = Executors.newSingleThreadExecutor();

它使用的阻塞队列是Linked 无限大,所以可能会堆积大量任务 然后OOM

大小无限线程池
1
ExecutorService executorService = Executors.newScheduledThreadPool();

核心线程自定义 可以是Integer.MAX_VALUE

线程池有什么优点?

  • 降低资源消耗:重用存在的线程,减少对象创建销毁的开销。
  • 提高响应速度。可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
  • 附加功能:提供定时执行、定期执行、单线程、并发数控制等功能。

综上所述使用线程池框架 Executor 能更好的管理线程、提供系统资源使用率。

线程池的状态

  • RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
  • SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
  • STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
  • TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
  • TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。

线程池中 submit() 和 execute() 方法有什么区别?

接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和 Callable 类型的任务。

返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()没有

异常处理:submit()方便Exception处理

submit()执行Runnable的任务时,run()方法没显式抛出异常。
execute()执行Callable的任务时,call()方法有显式的抛出异常。

如何定义一个注解

1
2
3
public @interface Hahaha{
String key() default "";
}

通过四种元注解修饰注解:@Target,@Retention,@Documented,@Inherited,

哪里用到了线程池?

连接数据库的时候Druid

jion()方法:线程实例的join()方法可以使得一个线程在另一个线程结束后再执行,即也就是说使得当前线程可以阻塞其他线程执行;

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。

比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

yield()方法

理论上,yield意味着放手,放弃,投降。一个调用yield()方法的线程告诉虚拟机它乐意让其他线程占用自己的位置。这表明该线程没有在做一些紧急的事情。注意,这仅是一个暗示,并不能保证不会产生任何影响。

System.out.println()线程安全?

是安全的,通过synchronized实现

https://blog.csdn.net/ft305977550/article/details/78769573

IO流

流的划分

  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的角色划分为节点流和处理流。

四个抽象基类

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

select poll epoll是linux中实现io多路复用的方法,

JAVA中的NIO

Java的NIO模式的Selector网络通讯,其实就是一个简单的Reactor模型。可以说是Reactor模型的朴素原型。

http://www.cyc2018.xyz/Java/Java%20IO.html#%E4%B8%83%E3%80%81nio

通道 Channel

**缓冲区 **

**选择器 **

NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。

通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。

因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。