Java面试1000题突击班(抓住金九银十) 持续更新中(一)

文章目录

一、Java基础

1.使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?

是引用不能变,即对象的指向不能变,但引用的对象即引用里的值是可以变得,因为它又没有用final修饰。基本数据类型的值是不能更改的
比如:final int a=1;a=2;
那肯定编译也过不了,因为a是final修饰的不可改变;a原来指向1,后来指向2;a的指向变了。引用类型的值是能更改的
比如:final int[] arr= {1,2,3}; arr[0]=2。这种是可以的。因为arr的指向并没有变 只不过它里边的值可以变。

2."=="和equals方法究竟有什么区别?

如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存,例如Objet obj = new Object();变量obj是一个内存,new Object()是另一个内存,此时,变量obj所对应的内存中存储的数值就是对象占用的那块内存的首地址。
对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用==操作符进行比较。

equals方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。例如,对于下面的代码:

String a=new String("foo");
String b=new String("foo");

两条new语句创建了两个对象,然后用a,b这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即a和b中存储的数值是不相同的,所以,表达式a==b将返回false,
这两个对象中的内容是相同的,所以,表达式a.equals(b)将返回true。

3.静态变量和实例变量的区别?

在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。

4.Java访问控制修饰符详解(public、 private、protected 和 friendly)

表1 各种访问修饰符的可访问性
访问范围 private friendly(默认) protected public
同一个类 可访问 可访问 可访问 可访问
同一包中的其他类 不可访问 可访问 可访问 可访问
不同包中的子类 不可访问 不可访问 可访问 可访问
不同包中的非子类 不可访问 不可访问 不可访问 可访问

5.Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?

构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。

6.接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承具体类(concrete class)? 抽象类中是否可以有静态的main方法?

接口可以继承接口。抽象类可以实现(implements)接口,抽象类可以继承具体类。抽象类中可以有静态的main方法。
备注:只要明白了接口和抽象类的本质和作用,这些问题都很好回答,想想看,如果自己作为是java语言的设计者,是否会提供这样的支持,如果不提供的话,有什么理由吗?如果没有道理不提供,那答案就是肯定的了。
只有记住抽象类与普通类的唯一区别就是不能创建实例对象和允许有abstract方法。

7.面向对象的特征有哪些方面

封装:隐藏了类的内部机制,可以再不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界它的内部结构细节是隐藏的,暴露给外界的只是它的访问方法。属性的封装:使用者只能通过事先定制好的方法来访问数据,可以方便的加入逻辑控制。限制对属性的不合理操作;方法的封装:使用这按照既定的方式调用方法,不必关心方法的内部实现,便于使用;便于修改,增强代码的可维护性;
增加代码的复用性
继承:继承就是从类中派生出新的类,新的类吸收已有类的数据属性和行为,并能拓展新的能力。在本质上是特殊~一般的关系。子类继承父类,表明子类是父类的特殊类,并且具有父类不具有的一些属性或者方法。extends关键字实现,多种实现类抽象出一个基类,使其具备多种实现类的共同特性,继承避免了一般类与特殊类之间共同特征的重复描述。
增加代码的复用性
多态:封装和继承最后归结于多态,多态指的是类和类的关系,两个类有继承关系,存在方法的重写,故而可以调用时有父类引用指向子类对象。多态必备三个要素:继承、重写、父类引用指向子类对象。
健壮性、灵活性、可移植性

8.abstract class和interface有什么区别?

含有abstract修饰符的class为抽象类,不能创建实例对象,含有abstract方法的类必须时抽象类,但是抽象类中可以不含有abstract方法。
1)抽象类可以有构造方法,接口中不能有构造方法。
2)抽象类中可以有普通成员变量,接口中没有普通成员变量
3)抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4) 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5)抽象类中可以包含静态方法,接口中不能包含静态方法,java8之后可以,还有可实现方法。
6)抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。final的原因:接口中如果可能定义非final的变量的话,那实现类就可以随意改变接口的属性值,这就和接口定义的初衷违背了,接口是实现类的规范,怎么能随意改变呢,所以接口中的属性必须是常量。
7)一个类可以实现多个接口,但只能继承一个抽象类。

9. abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?

abstract的method 不可以是static的,因为抽象的方法是要被子类实现的,而static与子类扯不上关系;native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类实现的问题,所以,它也不能是抽象的,不能与abstract混用;synchronized同步,抽象方法需要的是被实现,连方法体都没有,更不用说同步问题了。

10.byte、int、char、long、float、double、boolean和short。

11.String 和StringBuffer、StringBuilder的区别

执行速度由快到慢:StringBuilder > StringBuffer > String
(1)如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据 StringBuilder。

12.final、finally、finalize

final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
内部类要访问局部变量,局部变量必须定义成final类型。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM不保证此方法总被调用

13.运行时异常和一般异常有何异同

定义不同:运行时异常都是RuntimeException类及其子类异常,如NullPointException、IndexOutOfBoundException等。一般异常是RuntimeException以外的异常,类型都属于Exception类及其子类。
处理方法不同:运行时异常是不检查异常,程序中可以选择捕获处理,也可以不处理。对于一般异常,Java编译器强制要求用户必须对出现的这些异常仅从catch并处理,斗则编译不通过。
发生原因不同:运行时异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。面对这种异常不管我们是否愿意,只能去捕获。

14.Java中的异常处理机制的简单原理和应用

Java运行时发生的非正常情况或者错误,面向对象的方式处理,Java中常见的异常分别用不同的类表示,所有异常的根类都是Throwable,下边派生error(无法克服和恢复的一种严重问题)和Exception(可以捕获);try…catch处理或用throws声明继续抛给上层调用方法处理

15.请写出你最常见到的5个runtime exception:

IndexOutOfBoundsException(抛出以表示某种索引(例如数组,字符串或向量)的索引超出范围)、NullPointException(空指针)、IllegalArgumentException(抛出表示一种方法已经通过了非法或不正确的参数。)、ClassCastException(抛出表示代码尝试将对象转换为不属于实例的子类)、FileSystemAlreadExistException(尝试创建已存在的文件系统时抛出运行时异常)、UncheckedIOExxception()、

16. Collection框架中实现比较要实现什么接口 comparable/comparator

17. Vector、ArrayList和LinkedList有什么区别

都实现了List接口,都是有序集合,数据都是允许重复的;ArrayList和Vector基于数组,Vector线程安全synchronized同步锁,当存储元素的个数超过了容量时,;LinkedList基于双向链表,易于修改,不易查询。

18.HashMap和Hashtable的区别

Hashtable是线程安全的,也就是说是同步的  ,Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。而HashMap是线程序不安全的,不是同步的;都是基于哈希表实现的

19. List、Map、Set三个接口,存取元素时,各有什么特点

List与Set相比于Map,它们是单列元素的集合,有共同的父接口Collection。Set它是无序的,不允许有重复的数据,List是有序的,允许有重复的数据;Set取元素时,没法说取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素

20. Collection 和 Collections的区别?

Collection是集合类的上级接口,继承与他的接口主要有Set 和List。
Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

21. java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?

字节流继承于InputStream OutputStream,字符流继承于InputStreamReader OutputStreamWriter。在java.io包中还有许多其他的流,主要是为了提高性能和使用方便;

字节流 字符流
二进制数据:音乐、图像等IO流:InputStream、OutputStream 文本:Reader、Writer
底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再进行写入。

22. 什么是java序列化,如何实现java序列化?或者请解释Serializable接口的作用。

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化(将对象转换成二进制)。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间,序列化是为了解决在对对象流进行读写操作时所引发的问题。把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化。

23.描述一下JVM类加载机制、加载class文件的原理机制

Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。
JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化
在这里插入图片描述

  • 加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)
  • 验证:这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
  • 准备:准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。

注意这里所说的初始值概念,比如一个类变量定义为: public static int v = 8080;
实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080,将 v 赋值为8080 的 put static 指令是
程序被编译后,存放于类构造器方法之中。但是注意如果声明为:public static final int v = 8080;
在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v赋值为 8080。

  • 解析:阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。

24.heap和stack有什么区别。

区别:1、堆(heap)的空间一般由程序员分配释放;而栈(stack)的空间由操作系统自动分配释放 。2、heap是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定;而stack使用的是一级缓存,通常都是被调用时处于存储空间中,调用完毕立即释放。3、数据结构不同,heap可以被看成是一棵树,而stack是一种先进后出的数据结构。

25.GC是什么? 为什么要有GC?

垃圾回收集,内存处理是开发人员容易出现问题的地方,忘记或者错误地内存回收会导致程序或者系统地不稳定甚至崩溃,Java提供地垃圾回收机制可以自动检测对象是否超过作用域从而达到自动回收地目的。
简述:Java开发中,开发者并不需要去主动释放一个对象地内存,而是由虚拟机自动进行管理。在JVM中,有一个低优先级的垃圾回收线程,在正常情况下这个线程是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的对象集合中,然后进行回收操作。

26.垃圾回收的优点和原理。并考虑2种回收机制。

1.Java开发者在编程时,不需要考虑内存管理地问题
2.有效防止内存泄漏,可以有效使用可使用地内存
3.清除长时间不适用地对象和内存
4.作用域
分代复制垃圾回收:
标记垃圾回收:
增量垃圾回收:

27. java中会存在内存泄漏吗,请简单描述。

所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。Java中的内存泄露的情况:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是Java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。

28.获得一个类的类对象有哪些方式?

方法1:类型.class,例如:String.class
方法2:对象.getClass(),例如:”hello”.getClass()
方法3:Class.forName(),例如:Class.forName(“java.lang.String”)

29.JAVA 四中引用类型

  • 强引用:

在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之一。

  • 软引用

软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。

  • 弱引用

弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。

  • 虚引用

虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。

30.JAVA IO/NIO/BIO

阻塞 IO 模型

最传统的一种 IO 模型,即在读写数据过程中会发生阻塞现象。当用户线程发出 IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除 block 状态。典型的阻塞 IO模型的例子为:data = socket.read();如果数据没有就绪,就会一直阻塞在 read 方法。

非阻塞 IO 模型

当用户线程发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送 read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。所以事实上,在非阻塞 IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞 IO不会交出 CPU,而会一直占用 CPU。但是对于非阻塞 IO就有一个非常严重的问题,在 while 循环中需要不断地去询问内核数据是否就绪,这样会导致 CPU占用率非常高,因此一般情况下很少使用 while 循环这种方式来读取数据。

多路复用 IO 模型

多路复用 IO 模型是目前使用得比较多的模型。Java NIO 实际上就是多路复用 IO。在多路复用IO模型中,会有一个线程不断去轮询多个 socket 的状态,只有当 socket 真正有读写事件时,才真正调用实际的 IO读写操作。因为在多路复用 IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用 IO 资源所以它大大减少了资源占用。在 Java NIO 中,是通过selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这种方式会导致用户线程的阻塞。多路复用IO 模式,通过一个线程就可以管理多个 socket,只有当socket 真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO 比较适合连接数比较多的情况。
另外多路复用 IO 为何比非阻塞 IO 模型的效率高是因为在非阻塞IO 中,不断地询问 socket 状态时通过用户线程去进行的,而在多路复用 IO 中,轮询每个 socket状态是内核在进行的,这个效率要比用户线程要高的多。
不过要注意的是,多路复用 IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。因此对于多路复用 IO模型来说,一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。

信号驱动 IO 模型
异步 IO 模型

异步 IO 模型才是最理想的 IO 模型,在异步 IO 模型中,当用户线程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个 asynchronous read 之后,它会立刻返回,说明 read 请求已经成功发起了,因此不会对用户线程产生任何 block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它 read 操作完成了。也就说用户线程完全不需要实际的整个 IO 操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接去使用数据了。
也就说在异步 IO 模型中,IO 操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用 IO 函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用 IO 函数进行实际的读写操作;而在异步 IO 模型中,收到信号表示 IO 操作已经完成,不需要再在用户线程中调用 IO 函数进行实际的读写操作。

NIO

NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统 IO 基于字节流和字符流进行操作,而 NIO 基于 Channel 和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
NIO 和传统 IO 之间第一个最大的区别是,IO 是面向流的,NIO 是面向缓冲区的

NIO 的非阻塞

IO 的各种流是阻塞的。这意味着,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO 的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞 IO 的空闲时间用于在其它通道上执行 IO 操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

BIO

同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事会造成不必要的线程开销

31.双亲委派

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象。

32.hibernate和Mybatis的区别

相同之处

  1. Hibernate和mybatis都是属于持久层框架(操作数据库的框架).
  2. 操作数据库的底层都是使用的JDBC.
  3. 并且都是ORM(Object relational mapping)类型的框架.
  4. 通过操作对象,映射操作数据库的表.

不同之处

映射关系不同:
Hibernate: 实体类对象 =对应=> 数据库的表、 对象的属性 =对应=> 表的列
MyBatis: 表dao =对应=> 映射文件、表dao中的方法 =对应=> 映射文件中的sql语句

Hibernate优势:

  1. 由框架自动生成sql语句,减少编写代码的时间, 提高开发效率.
  2. 通过方言可以自动生成不同的sql语句,可移植型强.

Hibernate劣势:

  1. sql语句由框架自动生成,无法由开发者优化,导致运行效率降低.
  2. 框架设计复杂,学习成本极高.

MyBatis劣势:

  1. 所有sql需要开发者手动编写,开发效率低,编写代码时间长
  2. 固定的sql语句,没有方言机制,完全没有可移植性.

MyBatis优势:

  1. sql语句手动编写,运行效率高,灵活性高.
  2. 框架设计简单,学习成本较低.

33.jdk8的新特性

    1. lambda表达式

Lambda 表达式也可称为闭包,是推动 Java 8发布的最重要新特性。lambda表达式本质上是一个匿名方法。Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)或者把代码看成数据。使用Lambda 表达式可以使代码变的更加简洁紧凑。

    1. 函数式接口。

Supplier接口、Consumer接口、Function接口、Predicate接口四种接口。

    1. 方法引用。

符号表示:::
符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用
应用场景:如果lambda表达式所要实现的方案,已经有其它方法存在相同的方案,那么则可以使用方法引用方法引用在JDK8中使用是相当灵活的
有以下几种形式:
instanceName::methodName 对象::方法名
C lassName::staticMethodName 类名::静态方法
ClassName::methodName 类名::普通方法
ClassName::new 类名::new 调用的构造器
TypeName[]::new String[]::new 调用数组的构造器

    1. Stream流

Stream 不是 集合元素,也不是数据结构,它相当于一个 高级版本的 Iterator,不可以重复遍历里面的数据,像水一样,流过了就一去不复返。它和普通的 Iterator 不同的是,它可以并行遍历,普通的 Iterator 只能是串行,在一个线程中执行。

中间操作:
filter(): 对元素进行过滤
sorted():对元素排序
map():元素映射
distinct():去除重复的元素
最终操作:
forEach():遍历每个元素。
reduce():把Stream 元素组合起来。例如,字符串拼接,数值的 sum,min,max ,average 都是特殊的 reduce。
collect():返回一个新的集合。
min():找到最小值。
max():找到最大值。

    1. 日期时间类

34.反射

JAVA机制反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

Java反射机制主要提供了以下功能:
 * 1.在运行时判断任意一个对象所属的类。
 * 2.在运行时构造任意一个类的对象。
 * 3.在运行时判断任意一个类所具有的成员变量和方法。
 * 4.在运行时调用任意一个对象的方法。

有三种方法获得类的Class对象:Class.forName(String className)、className.class、实例对象.getClass()。

1.调用某个对象的getClass方法以获取该类的Class对象:
Person p = new Person();
Class clazz = p.getClass();
2.调用某个类的Class属性以获取该类对应的Class对象:
Class clazz = Person.class;
3.调用Class类中的forName静态方法获取该类对象,这是最安全也是性能最好的方法:
Class clazz = Class.forNmae("com.user.entery.Person")//类的全路径包括包名`

场景:

  1. 框架设计:在框架设计中,我们通常需要使用反射技术来解耦,使框架可扩展和灵活。
  2. 单元测试:在单元测试中,我们可以使用反射技术来访问私有或受保护的类成员,使测试更加全面。
  3. 动态代理:使用反射技术可以创建动态代理对象,从而可以在运行时期代理任意一个实现了接口的对象,实现AOP等功能
  4. JavaBean:JavaBean是一种轻量级组件,它封装了一个JavaBean类的属性和方法,并提供了访问和修改JavaBean属性的方法。反射技术可以访问和修改JavaBean类的私有属性和方法。
  5. 序列化和反序列化:许多Java序列化和反序列化工具都是基于Java反射机制实现的,例如Java的ObjectInputStream和ObjectOutputStream。

优点:增加程序的灵活性,可以在运行的过程中动态的对类进行修改和操作;提高代码的复用率,比如:动态代理,就是用到了反射来实现;可以在运行时轻松的获取任意一个类的方法、属性,并且还能通过反射进行动态调用;
缺点:反射会涉及到动态类型的解析,所以JVM无法对这些代码进行优化,导致性能要比非反射调用更低。;使用反射以后,代码的可读性会下降;反射可以绕过一些限制访问的属性或者方法,可能会导致破换代码本身的抽象性。

35.TCP协议为社么要设记三次握手

TCP协议,是一种可靠的,基于字节流的,面向连接的传输层协议
可靠性体现在TCP协议通信的双方的数据传输是稳定的,即便是网络不好的情况下,TCP也能保证数据传输到目标端,然后TCP通讯双方的数据传输是基于字节流传输的,最后面向连接是说在数据传输之前,必须建立一个连接,然后基于这个连接来进行数据传输。因为是面向连接的协议,所以TCP需要建立一个可靠的连接,TCP采用了三次我周的方式实现连接的建立。

  1. 客户端向服务端发送连接请求并携带同步序列号SYN。
  2. 服务器收到请求后,发送SYN(服务端的一个同步序列号)和ACK(对前面客户端请求的一个确认)。
  3. 客户算收到服务端的请求后,再次发送ACK(针对服务端请求的一个确认)。

原因:

  1. TCP是可靠性通信协议,所以TCP协议的通信双方都必须要维护一个序列号,去标记已经发送出去的数据包,哪些是已经被对方签收。而三次握手就是通信双方互相告知序列号的起始值,为了确保这个序列号被收到,所以双方都要有一个确认的操作。
  2. TCP协议需要在一个不可靠的网络环境下实现可靠的数据传输,意味着通信萨湖给你放要通过某种手段实现一个可靠的数据传输通道,而三次通信是建立这样一个通道的最小值。当然还可以多次,只是没有必要。
  3. 防止历史的重复连接初始造成的混乱问题,比如说在网络比较差的情况下,客户端连续多次发送建立连接的请求,假设只有两次握手,那么服务端只能选择接受或者拒绝这个连接请求,但是服务端不知道这个请求是不是之前因为网络堵塞而过期的请求,也就是说服务端不知道当前客户端的连接是有效的还是无效的。

二、线程

1.线程及实现方式

继承Thread类、实现Runnable接口、实现Callable接口

(1)继承Thread类,重写run方法。
启动线程是调用start()方法,这样会创建一个新的线程,并执行线程任务。
如果直接调用run()方法,这样会让当前线程执行run()方法中的业务逻辑。
(2) 实现Runnable接口 重写run方法
Thread实现Runnable接口,因此两者追根究底一样。 但由于Java中是单继承,所以相比继承Thread类要好一些。
最常用的方式:

  • 匿名内部类:
Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++){  System.out.println("i=" + i);
                }
            }
        });
  • lambda方式:
  Thread thread = new Thread(()->{
  for (int i = 0; i < 10; i++){   System.out.println("lambda:"+i);
            } 
        });
    }

(3)实现Callable重写call方法,配合FutureTask
Callable一般用于有返回结果的非阻塞的执行方法。非同步阻塞,如果想要返回一个结果,以上两种是没有办法是现实的。
(4) 基于线程池构建线程
追其底层,其实只有一只,就是实现Runnable接口

2.Java线程中的状态?

5种(一般针对操作系统层面):新建状态—start—就绪状态—(CPU调度)—运行状态—(wait()、sleep()、join())—等待状态——结束状态。
Java中给线程准备的6种状态:新建——运行\就绪状态(runnable)——阻塞状态(blocked)——等待(手动唤醒wating)——时间等待(timed_waiting)——结束
NEW:

//New
    public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(()->{
        	});
   	 System.out.println(t1.getState());
}
 // Runnable 就绪/运行状态
     public static void main(String[] args) throws InterruptedException {
       Thread t2 = new Thread(()->{
           while(true){
           }
        });
        t2.start();
        Thread.sleep(500);
        System.out.println(t2.getState());
}
    //Blocked
     public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        Thread t3 = new Thread(()->{
            //t3下拿不到锁资源,导致变成Blocked状态
            synchronized (object){

            }
        });
        //main线程拿到obj的锁资源
         synchronized (object){
            t3.start();
            Thread.sleep(500);
            System.out.println(t3.getState());
         }
      }
      public static void main(String[] args) throws InterruptedException {
        // Waiting
        Object object0 = new Object();
        Thread t4 = new Thread(()->{
            synchronized (object0){
                try {
                    object0.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t4.start();
        Thread.sleep(500);
        System.out.println(t4.getState());
       }
  // Timed_Waiting
     public static void main(String[] args) throws InterruptedException {
        Object object1 = new Object();
        Thread t5 = new Thread(()->{
            synchronized (object1){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t5.start();
        Thread.sleep(500);
        System.out.println(t5.getState());
   }

3.Java中Sleep和wait方法的区别?

  • sleep属于Thread类中的static方法,wait 属于Object类的方法
  • sleep属于Timed_waiting,自动被唤醒;wait属于waiting,需要手动唤醒
  • sleep方法在持有锁时,执行,不会释放锁资源;wait在执行后,会释放锁资源
  • sleep方法在持有锁或者不持有锁时,执行;wait方法必须在持有锁时才可以被执行
    wait方法会将持有锁的线程从owner仍到WaitSet集合中,这个操作实在修改ObjectMonitor对象,如果没有持有synchronized锁的话,是无法操作ObjectMonitor对象的。

4.同步和异步有何异同,在什么情况下分别使用他们?举例说明。

如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

5. 线程有几种实现方法?同步有几种实现方法?

(1)继承Thread类,重写run方法。
启动线程是调用start()方法,这样会创建一个新的线程,并执行线程任务。
如果直接调用run()方法,这样会让当前线程执行run()方法中的业务逻辑。
代码如下:

public class P1 {
    public static void main(String[] args) {
        MyJob myJob = new MyJob();
        myJob.run();
        myJob.start();
        for (int j = 0; j < 2; j++) {
            System.out.println("start:j="+j);
        }
    }
}
class MyJob extends Thread{
    public void run(){
        for (int i = 0; i < 2; i++) {
            System.out.println("run:i="+i);
        }
    }
}

(2) 实现Runnable接口 重写run方法
Thread实现Runnable接口,因此两者追根究底一样。 在这里插入图片描述
但由于Java中是单继承,所以相比继承Thread类要好一些。
最常用的方式:

  • 匿名内部类:
Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("i=" + i);
                }
            }
        });
  • lambda方式:
  Thread thread = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("lambda:"+i);
            } 
        });
    }

(3)实现Callable重写call方法,配合FutureTask
Callable一般用于有返回结果的非阻塞的执行方法。
非同步阻塞
如果想要返回一个结果,以上两种是没有办法是现实的。

public class P3 {
    public static void main(String[] args) throws ExecutionException,InterruptedException {
        Mycallables mycallables = new Mycallables();
        FutureTask futureTask = new FutureTask(mycallables);
        Thread thread = new Thread(futureTask);
        thread.start();
        //操作
        //要结果
        Object count = futureTask.get();
        System.out.println(count);
    }
}
class Mycallables implements Callable{

    @Override
    public Object call() throws Exception {
        int count = 0;
        for (int i = 0; i < 10; i++) {
            count +=1 ;
        }
        return count;
    }
}

在这里插入图片描述
在这里插入图片描述

(4) 基于线程池构建线程
追其底层,其实只有一只,就是实现Runnable接口
同步的实现方法:
(1)有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
(2)a.volatile关键字为域变量的访问提供了一种免锁机制b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新c.因此每次使用该域就要重新计算,而不是使用寄存器中的值d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。
(3)

6. 启动一个线程是用run()还是start()

7. 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?

分几种情况:
1)其他方法前是否加了synchronized关键字,如果没加,则能。
2)如果这个方法内部调用了wait,则可以进入其他synchronized方法。
3)如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。
4)如果其他方法是static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是this。

8. Java中如何停止线程?

(1)stop方法(不推荐,但却是可以做到。过时了)
强制让线程结束的方法有很多,最常用的就是让run方法结束,如论是return结束,还是抛出异常结束,都可以

public static void main(String[] args) throws InterruptedException {
	Thread t1 = new Thread(()->{
		try{
			Thread.sleep(5000);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	});
	t1.strat();
	Thread.sleep(5000);
	t1.stop();
	System.out.println(t1.getState());
}

(2)使用共享变量(很少会用)
这种方式用的不多,有的线程可能会通过死循环来保证一直运行。
咱们可以通过修改共享变量在破坏死循环,让线程退出循环,结束run方法

 static volatile boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            while(flag){
                // 处理任务
            }
            System.out.println("任务结束");
        });
        t1.start();
        Thread.sleep(500);
        flag = false;

(3)interrupt方式
休眠时也可以停掉

/*=============interrupt================*/
        //中断标记位 默认形况下 为false
        System.out.println(Thread.currentThread().isInterrupted());
        // 执行interrupt之后,再次查看打断信息
        Thread.currentThread().interrupt();
        // interrupt标记位:true
        System.out.println(Thread.currentThread().isInterrupted());
        // 返回当前线程,并归位为false interrupt标记位:true
        System.out.println(Thread.interrupted());
        // 已经归位了
        System.out.println(Thread.interrupted());

        //=======================================
        Thread t3 = new Thread(()->{
           while(!Thread.currentThread().isInterrupted()){
               // 处理业务
           }
            System.out.println("end");
        });
        t3.start();
        Thread.sleep(500);
        t3.interrupt();

9. Java中Sleep和wait方法的区别?

  • sleep属于Thread类中的static方法,wait 属于Object类的方法
  • sleep属于Timed_waiting,自动被唤醒;wait属于waiting,需要手动唤醒
  • sleep方法在持有锁时,执行,不会释放锁资源;wait在执行后,会释放锁资源
  • sleep方法在持有锁或者不持有锁时,执行;wait方法必须在持有锁时才可以被执行
    wait方法会将持有锁的线程从owner仍到WaitSet集合中,这个操作实在修改ObjectMonitor对象,如果没有持有synchronized锁的话,是无法操作ObjectMonitor对象的。

10.扩展—— P5典型 P6典型 P7典型

P5典型:解释一下什么是乐观锁、悲观锁、自旋锁、读写锁、排他锁、共享锁、统一锁、分段锁、行锁、表锁等。
  • 乐观锁:首先来看乐观锁,顾名思义,乐观锁就是持比较乐观态度的锁。就是在操作数据时非常乐观,认为别的线程不会同时修改数据,所以不会上锁,但是在更新的时候会判断在此期间别的线程有没有更新过这个数据。比如数据库提供的类似于write_condition机制,Java API 并发工具包下面的原子变量类就是使用了乐观锁的CAS来实现的。
    适用场景:它适用于写少读多的情况,也就是说减少操作冲突,这样可以省去锁竞争的开销,提高系统的吞吐量。
    坏事未必会发生,事后补偿
  • 悲观锁 :悲观锁就是持悲观态度的锁。就在操作数据时比较悲观,每次去拿数据的时候认为别的线程也会同时修改数据,所以每次在拿数据的时候都会上锁,这样别的线程想拿到这个数据就会阻塞直到它拿到锁。比如行锁、表锁、读锁、写锁,都是在操作之前先上锁,Java API中的synchronized和ReentrantLock等独占锁都是悲观锁思想的实现。
    适用场景:它适用于写多读少的情况。因为,如果还使用乐观锁的话,会经常出现操作冲突,这样会导致应用层会不断地Retry,反而会降低系统性能。
    坏事一定会发生,预先预防
  • 自旋锁:一种常见的乐观锁实现,CS锁
    • ABA问题(看似值没有改变,其实已经经过改变如:0-8-0)(解决方法:加版本< version、boolean >)
long sequence ;经历一个操作就要++一次
public class TestAtomicStampedReference {
    private static class Order {
        long sequence;
        long time;

        @Override
        public String toString() {
            return "Order{" +
                    "sequence=" + sequence +
                    ", time=" + time +
                    '}';
        }
    }

//    static AtomicStampedReference<Order> orderRef = new AtomicStampedReference<>(new Order(), 0);
    static AtomicMarkableReference<Order> orderRef = new AtomicMarkableReference<>(new Order(), false);

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                Order old = orderRef.getReference();
//                int stamp = orderRef.getStamp();

                Order o = new Order();
                o.sequence = old.sequence + 1;
               o.time = System.currentTimeMillis();

//               orderRef.compareAndSet(old,o,stamp,stamp+1);
                orderRef.compareAndSet(old,o,false,true);

            }).start();
        }
//            SleepHelper.sleepSeconds(1);
        System.out.println(orderRef.getReference());

    }
}

- 保障CAS操作的原则性问题(lock问题)
  • 排他锁:只有一个线程能访问代码
  • 共享锁:可以允许有多个线程访问代码
  • 读写锁
    - 读锁:读的时候,不允许写,但是允许同时读
    - 写锁:写的时候,不允许写,也不允许读(排他锁)
  • 统一锁:大粒度的锁
    - 锁定A等待B,锁定B等待A
    - A+B统一起来成为大锁 解决死锁问题
  • 分段锁:分成一段一段小粒度的锁
  • 间隙锁:是一个在 索引记录 之间的间隙上的锁
P6典型 对于线程池的理解和运用
  • 对于线程池的理解和运用
  • 如何理解线程池的7大参数?
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2,4,60, 
                TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(4),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());

在这里插入图片描述
1.corePoolSize: 核心线程数的大小
2.maximunPoolSize:最大线程数大小
3.keepAliveTime:生存时间
4. TimeUnit.SECONDS:生存时间的单位
5.ArrayBlockingQueue(4):任务队列(核心)
6.Executors.defaultThreadFactory():线程池产生的工厂(区分不同的线程抛出的不同异常)
7.RejectedExecutionHandler handler:拒绝策略
估计并发量,确定核心线程数,核心线程永远存在。如餐厅服务员,两个核心员工,消息队列长度为四个,当两个客户占用两个核心员工之后,则从外部调用临时员工(临时员工+核心员工=最大员工数),临时员工服务接下来的客户,之后再服务队列中的客户。(规则由程序来定)。《此中,员工对应线程,客户对应任务》
设置线程的数量能把cpu占满,是跟该线程的IO密集和CPU密集息息相关的。
当线程数全满,又有任务进来的时候,不能够丢掉的任务,可以对其进行持久化。默认有四个策略(jdk)。服务外面可以搭一个mq的集群,扔进mq中,当线程空闲,再拿出来。
有界队列:永远使用有界
无界:如链表队列,尽量不使用

P7典型 什么是纤程/协程,它和普通的Java线程有什么不同,为什么它能提高效率
  • 什么是纤程/协程
  • 它和普通的Java线程有什么不同
  • 为什么它能提高效率
扩充一下:什么是线程、进程?
一个程序-可执行文件,一般程序在硬盘上,想执行时,从硬盘放入内存里,这是这个程序就是一个进程(为其分配进程空间)。若要运行起来,则需要在进程空间找到线程入口-主线程。
线程是一般称为程序运行的基本单元。
一个CPU一般跑一个线程
线程撕裂者/超线程

11.并发编程的三大特性

原子性
JMM(Java Memory Model)。不同的硬件和不同的操作系统在内存上的操作有一定的差异的。Java为了解决相同代码在不同操作系统上出现的各种问题,用JMM屏蔽掉各种硬件和操作系统带来的差异。
让Java的并发编程可以做到跨平台。
JMM规定所有变量都会存储在主内存中,在操作的时候,需要从主内存中复制一份到线程内存(CPU内存),在线程内部做计算。然后再写回主内存中(不一定)
什么时并发编程得原子性
原子性得定义:一个操作是不可分割得,不可中断得,一个线程再执行时,另一个线程不会影响到他.
保证并发编程得原子性
synchronized:可以在方法上追加synchronized关键字或者采用同步代码块得形式来保证原子性,synchronized可以让避免多线程同时操作临界资源,同一时间点,只会有一个线程正在操作临界资源。
CAS:compare and swap也就是比较和交换,他是一条CPU的并发原语。
他在替换内存的某个位置的值时,首先查看内存中的值与预期值是否一致,如果一致,执行替换操作。这个操作是一个原子性操作。
java中基于Unsafe的类提供了对CAS的操作的方法,JVM会帮助我们将方法实现CAS汇编指令。但是要清楚CAS只是比较和交换,在获取原值的这个操作上,需要你自己实现。
Lock锁
Lock锁在1.5之前,性能相比较synchronized好,1.6之后,synchronized做了优化,性能相差就不大了。如果涉及并发比较多时,推荐ReentrantLock,性能会更好。其底层主要通过lock和unlock来实现。
ThreadLocal
其实,原子性用ThreadLocal很难保证,因为一般原子性是为了避免多线程使用共享变量,从而带来线程不安全。
ThreadLocal保证原子性的方式,是不让多线程去操作临界资源,让每个线程去操作属于自己的数据。
可见性
什么时可见性
可见性问题是基于CPU位置出现的,CPU处理速度非常快,相对于CPU来说,去内存获取数据这个事情太慢了,CPU就提供了L1,L2,L3的三级缓存,每次去主内存拿完数据后,就会储存到CPU的三级缓存,每次去三级缓存拿数据,效率肯定会提升。
这就会带来问题,现在二点CPU都是多核的,每个线程的工作内存(CPU三级缓存)都是独立的,会告知每个线程中做修改时,只改自己额工作内存,没有及时的同步到主内存,导致数据不一致问题。
解决可见性的方式
volatile:修饰成员变量,使用该关键字,相当于告知CPU,对当前属性的操作,不允许使用CPU的缓存,必须去和主存内操作
volatile的内存语义:
- volatile属性被写:当写一个volatile变量,JMM会将当前线程对应的CPU缓存及时刷新到主内存中
- volatile属性被读:当读一个volatile变量,JMM会将对应的CPU缓存中的内存设置无效,必须去主内存中重新读取共享变量。
加了volatile修饰的属性,会在转为汇编之后,追加一个lock的前缀,CPU执行这个指令时,如果带有lock前缀会做两个事情:
- 将当前处理器缓存行的数据写回到主内存
- 这个写回的数据,在其他的CPU内核的缓存中,直接无效
synchronized:加锁的时候将数据,同步到主内存
Lock:与synchronized完全不同,synchronized是基于他的内存语义,在获取和释放锁时,对CPU缓存做一个同步到主内存的操作;而lock锁是基于volatile实现的,Lock锁内部再进行枷锁和释放时,会对一个由volatile修饰和state属性进行加减操作。
final:final和volatile不允许同时修饰一个属性,final修饰的属性不允许再被写了。
有序性
什么是有序性

在Java中,.java文件中的内容会被编译,再执行前需要再次转为CPU可以识别的指令,CPU再执行这些指令时,为了提升执行效率,在不影响最终结果的前提下(满需一些要求),会对指令进行重排。
指令乱序执行的原因,是为了尽可能的发挥CPU的性能。
as-if-serial:
happens-before:

  • 程序次序规则(Program Order Rule):在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。
  • 管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。
  • volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作。
    线程启动规则(Thread Start Rule):Thread对象start()方法先行发生于此线程的每一个动作。
  • 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法和Thread.isAlive()的返回值等手段检测线程是否已经终止执行。
  • 线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
  • 对象终结规则(Finalizer Rule) :一个对象的初始化完成(构造函数结束)先行发生于它的finalize()方法的开始。
    传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。
    volatile:

12.什么是CAS,有什么优缺点

CAS:compare and swap也就是比较和交换,他是一条CPU的并发原语。
他在替换内存的某个位置的值时,首先查看内存中的值与预期值是否一致,如果一致,执行替换操作。这个操作是一个原子性操作。
java中基于Unsafe的类提供了对CAS的操作的方法,JVM会帮助我们将方法实现CAS汇编指令。但是要清楚CAS只是比较和交换,在获取原值的这个操作上,需要你自己实现。
缺点:CAS只能保证对一个变量的操作是原子性的,无法实现对多行代码实现原子性。
AtomicStampedReference在CAS时,不但会判断原值,还会比较版本信息。

 public static void main(String[] args) {
        AtomicStampedReference<String>reference = new AtomicStampedReference<>("AAA",1);
        String oldValue = reference.getReference();
        int oldVersion = reference.getStamp();
        boolean b = reference.compareAndSet(oldValue,"B",oldVersion,oldVersion+1);
        System.out.println("修改版本1的:"+b);
        boolean c = reference.compareAndSet("b","c",1,1+1);
        System.out.println("修改版本2的:"+c);
    }

自旋时间过长问题:
- 可以指定CAS一共循环多少次,如果超过这个次数,直接失败/或者挂起线程。(自旋锁、自适应自旋锁)
- 可以在CAS一次失败后,将这个操作暂存起来,后面需要获取结果时,将暂存的操作全部执行,再返回最后的结果。

13.线程池七大核心参数

线程池七大核心参数如下:
1.corePoolSize核心线程数目-池中会保留的最多线程数。
2.maximumPoolSize最大线程数目-核心线程+救急线程的最大数目。
3.keepAliveTime生存时间-救急线程的生存时间,生存时间内没有新任务,此线程资源会释放。
4.unit时间单位-救急线程的生存时间单位,如秒、毫秒等。
5.workQueue-当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务。
6.threadFactory线程工厂-可以定制线程对象的创建,例如设置线程名字、是否是守护线程等。
7.handler拒绝策略-当所有线程都在繁忙,workQueue也放满时,会触发拒绝策略

工作流程:

线程池内部是通过队列+线程实现的,当我们利⽤线程池执⾏任务时:

  1. 如果此时线程池中的线程数量⼩于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建
    新的线程来处理被添加的任务。
  2. 如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放⼊
    缓冲队列。
  3. 如果此时线程池中的线程数量⼤于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数
    量⼩于maximumPoolSize,建新的线程来处理被添加的任务。
  4. 如果此时线程池中的线程数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等
    于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
  5. 当线程池中的线程数量⼤于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被
    终⽌。这样,线程池可以动态的调整池中的线程数

14.守护线程和线程

1.定义:

守护线程又称为“服务线程”。在没有用户线程可服务时会自动离开。

2.优先级:
守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。

3.设置:

通过setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程的方式是在 线程对象创建 之前 用线程对象的setDaemon方法。

4.举例说明:

垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是 JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

5.生命周期

守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。那Java的守护线程是什么样子的呢。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出。

三、Spring相关面试题

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不想当个程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值