10万字208道Java经典面试题总结(附答案)

您所在的位置:网站首页 java公共类有什么特点吗 10万字208道Java经典面试题总结(附答案)

10万字208道Java经典面试题总结(附答案)

2023-12-16 02:23| 来源: 网络整理| 查看: 265

🍅 作者简介:哪吒,CSDN2021博客之星亚军🏆、新星计划导师✌、博客专家💪

🍅 哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师

🍅 技术交流:定期更新Java硬核干货,不定期送书活动

🍅 关注公众号【哪吒编程】,回复 面试题 ,获取《10万字208道Java经典面试题总结(附答案)》pdf,背题更方便,一文在手,面试我有

前言 

最近有很多粉丝问我,有什么方法能够快速提升自己,通过阿里、腾讯、字节跳动、京东等互联网大厂的面试,我觉得短时间提升自己最快的手段就是背面试题,最近总结了Java常用的面试题,分享给大家,希望大家都能圆梦大厂,加油,我命由我不由天。

目录

1、JDK 和 JRE 有什么区别?

2、== 和 equals 的区别是什么?

3、final 在 java 中有什么作用?

4、java 中的 Math.round(-1.5) 等于多少?

5、String 属于基础的数据类型吗?

6、String str="i"与 String str=new String(“i”)一样吗?

7、如何将字符串反转?

8、String 类的常用方法都有那些?

9、new String("a") + new String("b") 会创建几个对象?

10、如何将字符串反转?

11、String 类的常用方法都有那些?

12、普通类和抽象类有哪些区别?

13、接口和抽象类有什么区别?

14、java 中 IO 流分为几种?

15、BIO、NIO、AIO 有什么区别?

16、Files的常用方法都有哪些?

17、什么是反射?

18、什么是 java 序列化?什么情况下需要序列化?

19、为什么要使用克隆?如何实现对象克隆?深拷贝和浅拷贝区别是什么?

20、throw 和 throws 的区别?

21、final、finally、finalize 有什么区别?

22、try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

23、常见的异常类有哪些?

24、hashcode是什么?有什么作用?

25、java 中操作字符串都有哪些类?它们之间有什么区别?

26、java 中都有哪些引用类型?

27、在 Java 中,为什么不允许从静态方法中访问非静态变量?

28、说说Java Bean的命名规范

29、Java Bean 属性命名规范问题分析

30、什么是 Java 的内存模型?

31、在 Java 中,什么时候用重载,什么时候用重写?

32、举例说明什么情况下会更倾向于使用抽象类而不是接口?

33、实例化对象有哪几种方式

34、byte类型127+1等于多少

35、Java 容器都有哪些?

36、Collection 和 Collections 有什么区别?

37、list与Set区别

38、HashMap 和 Hashtable 有什么区别?

39、说一下 HashMap 的实现原理?

40、set有哪些实现类?

41、说一下 HashSet 的实现原理?

42、ArrayList 和 LinkedList 的区别是什么?

43、如何实现数组和 List 之间的转换?

44、在 Queue 中 poll()和 remove()有什么区别?

45、哪些集合类是线程安全的

46、迭代器 Iterator 是什么?

47、Iterator 怎么使用?有什么特点?

48、Iterator 和 ListIterator 有什么区别?

49、怎么确保一个集合不能被修改?

50、队列和栈是什么?有什么区别?

51、Java8开始ConcurrentHashMap,为什么舍弃分段锁?

52、ConcurrentHashMap(JDK1.8)为什么要使用synchronized而不是如ReentranLock这样的可重入锁?

53、concurrentHashMap和HashTable有什么区别

54、HasmMap和HashSet的区别

55、请谈谈 ReadWriteLock 和 StampedLock

56、线程的run()和start()有什么区别?

57、为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

58、Synchronized 用过吗,其原理是什么?

59、JVM 对 Java 的原生锁做了哪些优化?

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

61、Java 如何实现多线程之间的通讯和协作?

62、Thread 类中的 yield 方法有什么作用?

63、为什么说 Synchronized 是非公平锁?

64、请谈谈 volatile 有什么特点,为什么它能保证变量对所有线程的可见性?

65、为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有什么特性?

66、乐观锁一定就是好的吗?

67、请尽可能详尽地对比下 Synchronized 和 ReentrantLock 的异同。

68、ReentrantLock 是如何实现可重入性的?

69、什么是锁消除和锁粗化?

70、跟 Synchronized 相比,可重入锁 ReentrantLock 其实现原理有什么不同?

71、那么请谈谈 AQS 框架是怎么回事儿?

72、AQS 对资源的共享方式?

73、如何让 Java 的线程彼此同步?

74、你了解过哪些同步器?请分别介绍下。

75、Java 中的线程池是如何实现的

76、创建线程池的几个核心构造参数

77、线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗?

78、volatile 关键字的作用

79、既然 volatile 能够保证线程间的变量可见性,是不是就意味着基于 volatile 变量的运算就是并发安全的?

80、ThreadLocal 是什么?有哪些使用场景?

81、请谈谈 ThreadLocal 是怎么解决并发安全的?

82、很多人都说要慎用 ThreadLocal,谈谈你的理解,使用 ThreadLocal 需要注意些什么?

83、为什么代码会重排序?

84、什么是自旋

85、多线程中 synchronized 锁升级的原理是什么?

86、synchronized 和 ReentrantLock 区别是什么?

87、Java Concurrency API 中的 Lock 接口(Lock interface)是什么?对比同步它有什么优势?

88、jsp 和 servlet 有什么区别?

89、jsp 有哪些内置对象?作用分别是什么?

90、forward 和 redirect 的区别?

91、说一下 jsp 的 4 种作用域?

92、session 和 cookie 有什么区别?

93、如果客户端禁止 cookie 能实现 session 还能用吗?

94、什么是上下文切换?

95、cookie、session、token

96、说一下 session 的工作原理?

97、http 响应码 301 和 302 代表的是什么?有什么区别?

98、简述 tcp 和 udp的区别?

99、tcp 为什么要三次握手,两次不行吗?为什么?

100、OSI 的七层模型都有哪些?

101、get 和 post 请求有哪些区别?

102、什么是 XSS 攻击,如何避免?

103、什么是 CSRF 攻击,如何避免?

104、如何实现跨域?说一下 JSONP 实现原理?

105、websocket应用的是哪个协议

106、说一下 tcp 粘包是怎么产生的?

107、请列举出在 JDK 中几个常用的设计模式?

108、什么是设计模式?你是否在你的代码里面使用过任何设计模式?

109、Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式

110、在 Java 中,什么叫观察者设计模式(observer design pattern)?

111、使用工厂模式最主要的好处是什么?在哪里使用?

112、请解释自动装配模式的区别?

113、举一个用 Java 实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类层次?

114、什么是 Spring 框架?Spring 框架有哪些主要模块?

115、使用 Spring 框架能带来哪些好处?

116、Spring IOC、AOP举例说明

117、什么是控制反转(IOC)?什么是依赖注入?

118、BeanFactory 和 ApplicationContext 有什么区别?

119、什么是 JavaConfig?

120、什么是 ORM 框架?

121、Spring 有几种配置方式?

122、请解释 Spring Bean 的生命周期?

123、Spring Bean 的作用域之间有什么区别?Spring容器中的bean可以分为5个范围:

124、如何在 Spring Boot 中禁用 Actuator 端点安全性?

125、什么是 Spring inner beans?

126、Spring 框架中的单例 Beans 是线程安全的么?

127、请解释 Spring Bean 的自动装配?

128、如何开启基于注解的自动装配?

129、什么是 Spring Batch?

130、spring mvc 和 struts 的区别是什么?

131、请举例解释@Required 注解?

132、Spring常用注解

133、项目中是如何实现权限验证的,权限验证需要几张表

134、谈谈controller,接口调用的路径问题

135、如何防止表单重复提交

136、Spring中都应用了哪些设计模式

137、请举例说明如何在 Spring 中注入一个 Java Collection?

138、mybatis 中 #{}和 ${}的区别是什么?

139、mybatis 是否支持延迟加载?延迟加载的原理是什么?

140、说一下 mybatis 的一级缓存和二级缓存?

141、mybatis 有哪些执行器(Executor)?

142、mybatis 和 hibernate 的区别有哪些?

143、myBatis查询多个id、myBatis常用属性

144、mybatis一级缓存、二级缓存

145、mybatis如何防止sql注入

146、hibernate 中如何在控制台查看打印的 sql 语句?

147、hibernate 有几种查询方式?

148、hibernate 实体类可以被定义为 final 吗?

149、在 hibernate 中使用 Integer 和 int 做映射有什么区别?

150、什么是 Spring Boot?Spring Boot 有哪些优点?

151、Spring Boot 中的监视器是什么?

152、什么是 YAML?

153、如何使用 Spring Boot 实现分页和排序?

154、如何使用 Spring Boot 实现异常处理?

155、单点登录

156、Spring Boot比Spring多哪些注解

157、打包和部署

158、Spring Boot如何访问不同的数据库

159、查询网站在线人数

160、easyExcel如何实现

161、什么是 Swagger?你用 Spring Boot 实现了它吗?

162、数据库的三范式是什么?

163、一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几?

164、如何获取当前数据库版本?

165、说一下 ACID 是什么?

166、char 和 varchar 的区别是什么?

167、float 和 double 的区别是什么?

168、Oracle分页sql

169、数据库如何保证主键唯一性

170、如何设计数据库

171、性别是否适合做索引

172、如何查询重复的数据

173、数据库一般会采取什么样的优化方法?

174、索引怎么定义,分哪几种

175、mysql 的内连接、左连接、右连接有什么区别?

176、RabbitMQ的使用场景有哪些?

177、RabbitMQ有哪些重要的角色?有哪些重要的组件?

178、RabbitMQ中 vhost 的作用是什么?

179、说一下 jvm 的主要组成部分?及其作用?

180、说一下 jvm 运行时数据区?

181、什么是类加载器,类加载器有哪些?

182、说一下类加载的执行过程?

183、JVM的类加载机制是什么?

184、什么是双亲委派模型?

185、怎么判断对象是否可以被回收?

186、说一下 jvm 有哪些垃圾回收算法?

187、说一下 jvm 有哪些垃圾回收器?

188、JVM栈堆概念,何时销毁对象

189、新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

190、详细介绍一下 CMS 垃圾回收器?

191、简述分代垃圾回收器是怎么工作的?

192、Redis是什么?

193、Redis都有哪些使用场景?

194、Redis有哪些功能?

195、Redis支持的数据类型有哪些?

196、Redis取值存值问题

197、Redis为什么是单线程的?

198、Redis真的是单线程的吗?

199、Redis持久化有几种方式?

200、Redis和 memecache 有什么区别?

201、Redis支持的 java 客户端都有哪些?

202、jedis 和 redisson 有哪些区别?

203、什么是缓存穿透?怎么解决?

204、怎么保证缓存和数据库数据的一致性?

205、Redis,什么是缓存穿透?怎么解决?

206、Redis怎么实现分布式锁?

207、Redis分布式锁有什么缺陷?

208、Redis如何做内存优化?

1、JDK 和 JRE 有什么区别?

JDK(Java Development Kit),Java开发工具包

JRE(Java Runtime Environment),Java运行环境

JDK中包含JRE,JDK中有一个名为jre的目录,里面包含两个文件夹bin和lib,bin就是JVM,lib就是JVM工作所需要的类库。

2、== 和 equals 的区别是什么? 对于基本类型,==比较的是值;对于引用类型,==比较的是地址;equals不能用于基本类型的比较;如果没有重写equals,equals就相当于==;如果重写了equals方法,equals比较的是对象的内容; 3、final 在 java 中有什么作用?

(1)用来修饰一个引用

 如果引用为基本数据类型,则该引用为常量,该值无法修改; 如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。 如果引用时类的成员变量,则必须当场赋值,否则编译会报错。

(2)用来修饰一个方法

当使用final修饰方法时,这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承。

(3)用来修饰类

当用final修改类时,该类成为最终类,无法被继承。

 比如常用的String类就是最终类。

4、java 中的 Math.round(-1.5) 等于多少?

Math提供了三个与取整有关的方法:ceil、floor、round

(1)ceil:向上取整;

Math.ceil(11.3) = 12;

Math.ceil(-11.3) = 11;

(2)floor:向下取整;

Math.floor(11.3) = 11;

Math.floor(-11.3) = -12;

(3)round:四舍五入;

加0.5然后向下取整。

Math.round(11.3) = 11;

Math.round(11.8) = 12;

Math.round(-11.3) = -11;

Math.round(-11.8) = -12;

5、String 属于基础的数据类型吗?

不属于。

八种基本数据类型:byte、short、char、int、long、double、float、boolean。

6、String str="i"与 String str=new String(“i”)一样吗?

String str="i"会将起分配到常量池中,常量池中没有重复的元素,如果常量池中存中i,就将i的地址赋给变量,如果没有就创建一个再赋给变量。

String str=new String(“i”)会将对象分配到堆中,即使内存一样,还是会重新创建一个新的对象。

7、如何将字符串反转?

将对象封装到stringBuilder中,调用reverse方法反转。

8、String 类的常用方法都有那些?

(1)常见String类的获取功能

length:获取字符串长度; charAt(int index):获取指定索引位置的字符; indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引; substring(int start):从指定位置开始截取字符串,默认到末尾; substring(int start,int end):从指定位置开始到指定位置结束截取字符串;

(2)常见String类的判断功能

equals(Object obj): 比较字符串的内容是否相同,区分大小写; contains(String str): 判断字符串中是否包含传递进来的字符串; startsWith(String str): 判断字符串是否以传递进来的字符串开头; endsWith(String str): 判断字符串是否以传递进来的字符串结尾; isEmpty(): 判断字符串的内容是否为空串"";

(3)常见String类的转换功能

byte[] getBytes(): 把字符串转换为字节数组; char[] toCharArray(): 把字符串转换为字符数组; String valueOf(char[] chs): 把字符数组转成字符串。valueOf可以将任意类型转为字符串; toLowerCase(): 把字符串转成小写; toUpperCase(): 把字符串转成大写; concat(String str): 把字符串拼接;

(4)常见String类的其他常用功能

replace(char old,char new) 将指定字符进行互换 replace(String old,String new) 将指定字符串进行互换 trim() 去除两端空格 int compareTo(String str) 会对照ASCII 码表 从第一个字母进行减法运算 返回的就是这个减法的结果,如果前面几个字母一样会根据两个字符串的长度进行减法运算返回的就是这个减法的结果,如果连个字符串一摸一样 返回的就是0。

9、new String("a") + new String("b") 会创建几个对象?

对象1:new StringBuilder()

对象2:new String("a")

对象3:常量池中的"a"

对象4:new String("b")

对象5:常量池中的"b"

深入剖析:StringBuilder中的toString():

对象6:new String("ab")

强调一下,toString()的调用,在字符串常量池中,没有生成"ab"

附加题

String s1 = new String("1") + new String("1");//s1变量记录的地址为:new String s1.intern();//在字符串常量池中生成"11"。如何理解:jdk6:创建了一个新的对象"11",也就有新的地址;jdk7:此时常量池中并没有创建"11",而是创建了一个指向堆空间中new String("11")的地址; String s2 = "11"; System.out.println(s1 == s2);//jdk6:false;jdk7:true

10、如何将字符串反转?

添加到StringBuilder中,然后调用reverse()。

11、String 类的常用方法都有那些?

equals、length、contains、replace、split、hashcode、indexof、substring、trim、toUpperCase、toLowerCase、isEmpty等等。

12、普通类和抽象类有哪些区别?

抽象类不能被实例化; 抽象类可以有抽象方法,只需申明,无须实现; 有抽象方法的类一定是抽象类; 抽象类的子类必须实现抽象类中的所有抽象方法,否则子类仍然是抽象类; 抽象方法不能声明为静态、不能被static、final修饰。

13、接口和抽象类有什么区别?

(1)接口

接口使用interface修饰; 接口不能实例化; 类可以实现多个接口;

①java8之前,接口中的方法都是抽象方法,省略了public abstract。②java8之后;接口中可以定义静态方法,静态方法必须有方法体,普通方法没有方法体,需要被实现;

(2)抽象类

抽象类使用abstract修饰; 抽象类不能被实例化; 抽象类只能单继承; 抽象类中可以包含抽象方法和非抽象方法,非抽象方法需要有方法体; 如果一个类继承了抽象类,①如果实现了所有的抽象方法,子类可以不是抽象类;②如果没有实现所有的抽象方法,子类仍然是抽象类。

14、java 中 IO 流分为几种?

(1)按流划分,可以分为输入流和输出流;

(2)按单位划分,可以分为字节流和字符流;

字节流:inputStream、outputStream;

字符流:reader、writer;

15、BIO、NIO、AIO 有什么区别?

(1)同步阻塞BIO

一个连接一个线程。

JDK1.4之前,建立网络连接的时候采用BIO模式,先在启动服务端socket,然后启动客户端socket,对服务端通信,客户端发送请求后,先判断服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝请求,如果有的话会等待请求结束后才继续执行。

(2)同步非阻塞NIO

NIO主要是想解决BIO的大并发问题,BIO是每一个请求分配一个线程,当请求过多时,每个线程占用一定的内存空间,服务器瘫痪了。

JDK1.4开始支持NIO,适用于连接数目多且连接比较短的架构,比如聊天服务器,并发局限于应用中。

一个请求一个线程。

(3)异步非阻塞AIO

一个有效请求一个线程。

JDK1.7开始支持AIO,适用于连接数目多且连接比较长的结构,比如相册服务器,充分调用OS参与并发操作。

16、Files的常用方法都有哪些?

exist createFile createDirectory write read copy size delete move

17、什么是反射?

所谓反射,是java在运行时进行自我观察的能力,通过class、constructor、field、method四个方法获取一个类的各个组成部分。

在Java运行时环境中,对任意一个类,可以知道类有哪些属性和方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于反射机制。

18、什么是 java 序列化?什么情况下需要序列化?

序列化就是一种用来处理对象流的机制。将对象的内容流化,将流化后的对象传输于网络之间。

序列化是通过实现serializable接口,该接口没有需要实现的方法,implement Serializable只是为了标注该对象是可被序列化的,使用一个输出流(FileOutputStream)来构造一个ObjectOutputStream对象,接着使用ObjectOutputStream对象的writeObejct(Object object)方法就可以将参数的obj对象到磁盘,需要恢复的时候使用输入流。

序列化是将对象转换为容易传输的格式的过程。

例如,可以序列化一个对象,然后通过HTTP通过Internet在客户端和服务器之间传输该对象。在另一端,反序列化将从流中心构造成对象。

一般程序在运行时,产生对象,这些对象随着程序的停止而消失,但我们想将某些对象保存下来,这时,我们就可以通过序列化将对象保存在磁盘,需要使用的时候通过反序列化获取到。

对象序列化的最主要目的就是传递和保存对象,保存对象的完整性和可传递性。

譬如通过网络传输或者把一个对象保存成本地一个文件的时候,需要使用序列化。

19、为什么要使用克隆?如何实现对象克隆?深拷贝和浅拷贝区别是什么?

(1)什么要使用克隆?

想对一个对象进行复制,又想保留原有的对象进行接下来的操作,这个时候就需要克隆了。

(2)如何实现对象克隆?

实现Cloneable接口,重写clone方法; 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。 BeanUtils,apache和Spring都提供了bean工具,只是这都是浅克隆。

(3)深拷贝和浅拷贝区别是什么?

浅拷贝:仅仅克隆基本类型变量,不克隆引用类型变量; 深克隆:既克隆基本类型变量,又克隆引用类型变量;

(4)代码实例

20、throw 和 throws 的区别?

(1)throw

作用在方法内,表示抛出具体异常,由方法体内的语句处理; 一定抛出了异常;

(2)throws

作用在方法的声明上,表示抛出异常,由调用者来进行异常处理; 可能出现异常,不一定会发生异常;

21、final、finally、finalize 有什么区别?

final可以修饰类,变量,方法,修饰的类不能被继承,修饰的变量不能重新赋值,修饰的方法不能被重写

finally用于抛异常,finally代码块内语句无论是否发生异常,都会在执行finally,常用于一些流的关闭。

finalize方法用于垃圾回收。

一般情况下不需要我们实现finalize,当对象被回收的时候需要释放一些资源,比如socket链接,在对象初始化时创建,整个生命周期内有效,那么需要实现finalize方法,关闭这个链接。

但是当调用finalize方法后,并不意味着gc会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,然后到了真正要回收的时候,因为之前调用过一次,这次又不会调用了,产生问题。所以,不推荐使用finalize方法。

22、try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

23、常见的异常类有哪些? NullPointerException:空指针异常;SQLException:数据库相关的异常;IndexOutOfBoundsException:数组下角标越界异常;FileNotFoundException:打开文件失败时抛出;IOException:当发生某种IO异常时抛出;ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出此异常;NoSuchMethodException:无法找到某一方法时,抛出;ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常;NumberFormatException:当试图将字符串转换成数字时,失败了,抛出;IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。ArithmeticException当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。  24、hashcode是什么?有什么作用?

Java中Object有一个方法:

public native int hashcode();

(1)hashcode()方法的作用

hashcode()方法主要配合基于散列的集合一起使用,比如HashSet、HashMap、HashTable。

当集合需要添加新的对象时,先调用这个对象的hashcode()方法,得到对应的hashcode值,实际上hashmap中会有一个table保存已经存进去的对象的hashcode值,如果table中没有改hashcode值,则直接存入,如果有,就调用equals方法与新元素进行比较,相同就不存了,不同就存入。

(2)equals和hashcode的关系

如果equals为true,hashcode一定相等; 

如果equals为false,hashcode不一定不相等;

如果hashcode值相等,equals不一定相等;

如果hashcode值不等,equals一定不等;

(3)重写equals方法时,一定要重写hashcode方法

(4)百度百科

hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。 

hashCode 的常规协定是:  在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。  如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。  以下情况不 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。  实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) 

当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

(5)小白解释

1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有 例如内存中有这样的位置 0  1  2  3  4  5  6  7   而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。 但如果用hashcode那就会使效率提高很多。 我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。

2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。 也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。 那么。重写了equals(),为什么还要重写hashCode()呢? 想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊。

25、java 中操作字符串都有哪些类?它们之间有什么区别?

(1)String

String是不可变对象,每次对String类型的改变时都会生成一个新的对象。

(2)StringBuilder

线程不安全,效率高,多用于单线程。

(3)StringBuffer

线程安全,由于加锁的原因,效率不如StringBuilder,多用于多线程。

不频繁的字符串操作使用String,操作频繁的情况不建议使用String。

StringBuilder > StringBuffer > String。

26、java 中都有哪些引用类型?

(1)强引用

Java中默认声明的就是强引用,比如:

Object obj = new Object(); obj = null;

只要强引用存在,垃圾回收器将永远不会回收被引用的对象。如果想被回收,可以将对象置为null; 

(2)软引用(SoftReference)

在内存足够的时候,软引用不会被回收,只有在内存不足时,系统才会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会跑出内存溢出异常。

byte[] buff = new byte[1024 * 1024]; SoftReference sr = new SoftReference(buff);

(3)弱引用(WeakReference)

进行垃圾回收时,弱引用就会被回收。

(4)虚引用(PhantomReference)

(5)引用队列(ReferenceQueue)

引用队列可以与软引用、弱引用、虚引用一起配合使用。

当垃圾回收器准备回收一个对象时,如果发现它还有引用,就会在回收对象之前,把这个引用加入到引用队列中。

程序可以通过判断引用队列中是否加入了引用,来判断被引用的对象是否将要被垃圾回收,这样可以在对象被回收之前采取一些必要的措施。

27、在 Java 中,为什么不允许从静态方法中访问非静态变量? 静态变量属于类本身,在类加载的时候就会分配内存,可以通过类名直接访问;非静态变量属于类的对象,只有在类的对象产生时,才会分配内存,通过类的实例去访问;静态方法也属于类本身,但是此时没有类的实例,内存中没有非静态变量,所以无法调用。 28、说说Java Bean的命名规范 JavaBean 类必须是一个公共类,并将其访问属性设置为 publicJavaBean 类必须有一个空的构造函数:类中必须有一个不带参数的公用构造器,此构造器也应该通过调用各个特性的设置方法来设置特性的缺省值。一个javaBean类不应有公共实例变量,类变量都为private持有值应该通过一组存取方法(getXxx 和 setXxx)来访问:对于每个特性,应该有一个带匹配公用 getter 和 setter 方法的专用实例变量。

属性为布尔类型,可以使用 isXxx() 方法代替 getXxx() 方法。

通常属性名是要和 包名、类名、方法名、字段名、常量名作出区别的:

首先:必须用英文,不要用汉语拼音

(1)包(package)

用于将完成不同功能的类分门别类,放在不同的目录(包)下,包的命名规则:将公司域名反转作为包名。比如www.sohu.com 对于包名:每个字母都需要小写。比如:com.sohu.test;该包下的Test类的全名是:com.sohu.Test.Java 。

如果定义类的时候没有使用package,那么java就认为我们所定义的类位于默认包里面(default package)。

(2)类

首字母大写,如果一个类由多个单词构成,那么每个单词的首字母都大写,而且中间不使用任何的连接符。尽量使用英文。如ConnectionFactory

(3)方法

首单词全部小写,如果一个方法由多个单词构成,那么从第二个单词开始首字母大写,不使用连接符。addPerson

(4)字段

与方法相同。如ageOfPerson

(5)常量

所有单词的字母都是大写,如果有多个单词,那么使用下划线链接即可。

如:public static final int AGE_OF_PERSON = 20; //通常加上static

29、Java Bean 属性命名规范问题分析 public class User { private String busName; private String pCount; private Boolean isRunning; //正确的命名方式,驼峰式的 public String getBusName() { return busName; } public void setBusName(String busName) { this.busName = busName; } //这是什么? public String getpCount() { return pCount; } public void setpCount(String pCount) { this.pCount = pCount; } //这个也是不允许的 public Boolean getIsRunning() { return isRunning; } public void setIsRunning(Boolean isRunning) { this.isRunning = isRunning; } }

1. javabean属性命名尽量使用常规的驼峰式命名规则 2. 属性名第一个单词尽量避免使用一个字母:如eBook, eMail。 3. boolean属性名避免使用 “is” 开头的名称 4. 随着jdk, eclipse, spring 等软件版本的不断提高, 底版本的出现的问题可能在高版本中解决了, 低版本原来正常的代码可能在高版本环境下不再支持。

30、什么是 Java 的内存模型?

在了解什么是 Java 内存模型之前,先了解一下为什么要提出 Java 内存模型。

之前提到过并发编程有三大问题

CPU 缓存,在多核 CPU 的情况下,带来了可见性问题 操作系统对当前执行线程的切换,带来了原子性问题 译器指令重排优化,带来了有序性问题 为了解决并发编程的三大问题,提出了 JSR-133,新的 Java 内存模型,JDK 5 开始使用。

简单总结下

Java 内存模型是 JVM 的一种规范 定义了共享内存在多线程程序中读写操作行为的规范 屏蔽了各种硬件和操作系统的访问差异,保证了 Java 程序在各种平台下对内存的访问效果一致 解决并发问题采用的方式:限制处理器优化和使用内存屏障 增强了三个同步原语(synchronized、volatile、final)的内存语义 定义了 happens-before 规则

31、在 Java 中,什么时候用重载,什么时候用重写?

(1)重载是多态的集中体现,在类中,要以统一的方式处理不同类型数据的时候,可以用重载。

(2)重写的使用是建立在继承关系上的,子类在继承父类的基础上,增加新的功能,可以用重写。

(3)简单总结:

重载是多样性,重写是增强剂; 目的是提高程序的多样性和健壮性,以适配不同场景使用时,使用重载进行扩展; 目的是在不修改原方法及源代码的基础上对方法进行扩展或增强时,使用重写;

生活例子:

你想吃一碗面,我给你提供了拉面,炒面,刀削面,担担面供你选择,这是重载; 你想吃一碗面,我不但给你端来了面,还给你加了青菜,加了鸡蛋,这个是重写;

设计模式:

cglib实现动态代理,核心原理用的就是方法的重写;

详细解答:

Java的重载(overload) 最重要的应用场景就是构造器的重载,构造器重载后,提供多种形参形式的构造器,可以应对不同的业务需求,加强程序的健壮性和可扩展性,比如我们最近学习的Spring源码中的ClassPathXmlApplicationContext,它的构造函数使用重载一共提供了10个构造函数,这样就为业务的选择提供了多选择性。在应用到方法中时,主要是为了增强方法的健壮性和可扩展性,比如我们在开发中常用的各种工具类,比如我目前工作中的短信工具类SMSUtil, 发短信的方法就会使用重载,针对不同业务场景下的不同形参,提供短信发送方法,这样提高了工具类的扩展性和健壮性。 总结:重载必须要修改方法(构造器)的形参列表,可以修改方法的返回值类型,也可以修改方法的异常信息即访问权限;使用范围是在同一个类中,目的是对方法(构造器)进行功能扩展,以应对多业务场景的不同使用需求。提高程序的健壮性和扩展性。  java的重写(override) 只要用于子类对父类方法的扩展或修改,但是在我们开发中,为了避免程序混乱,重写一般都是为了方法的扩展,比如在cglib方式实现的动态代理中,代理类就是继承了目标类,对目标类的方法进行重写,同时在方法前后进行切面织入。

总结:方法重写时,参数列表,返回值得类型是一定不能修改的,异常可以减少或者删除,但是不能抛出新的异常或者更广的异常,方法的访问权限可以降低限制,但是不能做更严格的限制。

(4)在里氏替换原则中,子类对父类的方法尽量不要重写和重载。(我们可以采用final的手段强制来遵循)

32、举例说明什么情况下会更倾向于使用抽象类而不是接口?

接口和抽象类都遵循”面向接口而不是实现编码”设计原则,它可以增加代码的灵活性,可以适应不断变化的需求。下面有几个点可以帮助你回答这个问题:在 Java 中,你只能继承一个类,但可以实现多个接口。所以一旦你继承了一个类,你就失去了继承其他类的机会了。

接口通常被用来表示附属描述或行为如: Runnable 、 Clonable 、 Serializable 等等,因此当你使用抽象类来表示行为时,你的类就不能同时是 Runnable 和 Clonable( 注:这里的意思是指如果把 Runnable 等实现为抽象类的情况 ) ,因为在 Java 中你不能继承两个类,但当你使用接口时,你的类就可以同时拥有多个不同的行为。

在一些对时间要求比较高的应用中,倾向于使用抽象类,它会比接口稍快一点。如果希望把一系列行为都规范在类继承层次内,并且可以更好地在同一个地方进行编码,那么抽象类是一个更好的选择。有时,接口和抽象类可以一起使用,接口中定义函数,而在抽象类中定义默认的实现。

33、实例化对象有哪几种方式 newclone()通过反射机制创建 //用 Class.forName方法获取类,在调用类的newinstance()方法 Class cls = Class.forName("com.dao.User"); User u = (User)cls.newInstance(); 序列化反序列化 //将一个对象实例化后,进行序列化,再反序列化,也可以获得一个对象(远程通信的场景下使用) ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("D:/data.txt")); //序列化对象 out.writeObject(user1); out.close(); //反序列化对象 ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/data.txt")); User user2 = (User) in.readObject(); System.out.println("反序列化user:" + user2); in.close(); 34、byte类型127+1等于多少

byte的范围是-128~127。

字节长度为8位,最左边的是符号位,而127的二进制为01111111,所以执行+1操作时,01111111变为10000000。

大家知道,计算机中存储负数,存的是补码的兴衰。左边第一位为符号位。

那么负数的补码转换成十进制如下:

一个数如果为正,则它的原码、反码、补码相同;一个正数的补码,将其转化为十进制,可以直接转换。

已知一个负数的补码,将其转换为十进制数,步骤如下:

先对各位取反;将其转换为十进制数;加上负号,再减去1;

例如10000000,最高位是1,是负数,①对各位取反得01111111,转换为十进制就是127,加上负号得-127,再减去1得-128;

35、Java 容器都有哪些?

(1)Collection

① set

HashSet、TreeSet

② list

ArrayList、LinkedList、Vector

(2)Map

HashMap、HashTable、TreeMap

36、Collection 和 Collections 有什么区别?

(1)Collection是最基本的集合接口,Collection派生了两个子接口list和set,分别定义了两种不同的存储方式。

(2)Collections是一个包装类,它包含各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等)。

此类不能实例化,就像一个工具类,服务于Collection框架。

37、list与Set区别

(1)List简介

实际上有两种List:一种是基本的ArrayList,其优点在于随机访问元素,另一种是LinkedList,它并不是为快速随机访问设计的,而是快速的插入或删除。 ArrayList:由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。 LinkedList :对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。 还具有下列方 法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。

(2)Set简介

Set具有与Collection完全一样的接口,因此没有任何额外的功能。实际上Set就是Collection,只是行为不同。这是继承与多态思想的典型应用:表现不同的行为。Set不保存重复的元素(至于如何判断元素相同则较为负责) 

Set : 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。  HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。  TreeSet: 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。 

(3)list与Set区别

① List,Set都是继承自Collection接口

② List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。) 

③ Set和List对比: 

Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。  List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

38、HashMap 和 Hashtable 有什么区别? HashMap是线程不安全的,HashTable是线程安全的;HashMap中允许键和值为null,HashTable不允许;HashMap的默认容器是16,为2倍扩容,HashTable默认是11,为2倍+1扩容; 39、说一下 HashMap 的实现原理?

(1)简介

HashMap基于map接口,元素以键值对方式存储,允许有null值,HashMap是线程不安全的。

(2)基本属性

初始化大小,默认16,2倍扩容; 负载因子0.75; 初始化的默认数组; size threshold。判断是否需要调整hashmap容量

(3)HashMap的存储结构

JDK1.7中采用数组+链表的存储形式。

HashMap采取Entry数组来存储key-value,每一个键值对组成了一个Entry实体,Entry类时机上是一个单向的链表结构,它具有next指针,指向下一个Entry实体,以此来解决Hash冲突的问题。

HashMap实现一个内部类Entry,重要的属性有hash、key、value、next。

JDK1.8中采用数据+链表+红黑树的存储形式。当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。

40、set有哪些实现类?

(1)HashSet

HashSet是set接口的实现类,set下面最主要的实现类就是HashSet(也就是用的最多的),此外还有LinkedHashSet和TreeSet。 HashSet是无序的、不可重复的。通过对象的hashCode和equals方法保证对象的唯一性。 HashSet内部的存储结构是哈希表,是线程不安全的。

(2)TreeSet

TreeSet对元素进行排序的方式:

元素自身具备比较功能,需要实现Comparable接口,并覆盖compareTo方法。 元素自身不具备比较功能,需要实现Comparator接口,并覆盖compare方法。

(3)LinkedHashSet

LinkedHashSet是一种有序的Set集合,即其元素的存入和输出的顺序是相同的。

41、说一下 HashSet 的实现原理?

HashSet实际上是一个HashMap实例,数据存储结构都是数组+链表。

HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value都是一个统一的对象PRESENT。

private static final Object PRESENT = new Object();

HashSet中add方法调用的是底层HashMap中的put方法,put方法要判断插入值是否存在,而HashSet的add方法,首先判断元素是否存在,如果存在则插入,如果不存在则不插入,这样就保证了HashSet中不存在重复值。

 通过对象的hashCode和equals方法保证对象的唯一性。

42、ArrayList 和 LinkedList 的区别是什么?

ArrayList是动态数组的数据结构实现,查找和遍历的效率较高;

LinkedList 是双向链表的数据结构,增加和删除的效率较高;

43、如何实现数组和 List 之间的转换? String[] arr = {"zs","ls","ww"}; List list = Arrays.asList(arr); System.out.println(list); ArrayList list1 = new ArrayList(); list1.add("张三"); list1.add("李四"); list1.add("王五"); String[] arr1 = list1.toArray(new String[list1.size()]); System.out.println(arr1); for(int i = 0; i < arr1.length; i++){ System.out.println(arr1[i]); } 44、在 Queue 中 poll()和 remove()有什么区别?

(1)offer()和add()区别:

增加新项时,如果队列满了,add会抛出异常,offer返回false。

(2)poll()和remove()区别:

poll()和remove()都是从队列中删除第一个元素,remove抛出异常,poll返回null。

(3)peek()和element()区别:

peek()和element()用于查询队列头部元素,为空时element抛出异常,peek返回null。

45、哪些集合类是线程安全的

Vector:就比Arraylist多了个同步化机制(线程安全)。 Stack:栈,也是线程安全的,继承于Vector。 Hashtable:就比Hashmap多了个线程安全。 ConcurrentHashMap:是一种高效但是线程安全的集合。

46、迭代器 Iterator 是什么?

为了方便的处理集合中的元素,Java中出现了一个对象,该对象提供了一些方法专门处理集合中的元素.例如删除和获取集合中的元素.该对象就叫做迭代器(Iterator)。

47、Iterator 怎么使用?有什么特点?

Iterator 接口源码中的方法:

java.lang.Iterable 接口被 java.util.Collection 接口继承,java.util.Collection 接口的 iterator() 方法返回一个 Iterator 对象next() 方法获得集合中的下一个元素hasNext() 检查集合中是否还有元素remove() 方法将迭代器新返回的元素删除 48、Iterator 和 ListIterator 有什么区别?

(1)ListIterator 继承 Iterator

(2)ListIterator 比 Iterator多方法

add(E e)  将指定的元素插入列表,插入位置为迭代器当前位置之前set(E e)  迭代器返回的最后一个元素替换参数ehasPrevious()  迭代器当前位置,反向遍历集合是否含有元素previous()  迭代器当前位置,反向遍历集合,下一个元素previousIndex()  迭代器当前位置,反向遍历集合,返回下一个元素的下标nextIndex()  迭代器当前位置,返回下一个元素的下标

(3)使用范围不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子类

ListIterator 有 add 方法,可以向 List 中添加对象;Iterator 不能ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向遍历;Iterator不可以ListIterator 有 nextIndex() 和previousIndex() 方法,可定位当前索引的位置;Iterator不可以ListIterator 有 set()方法,可以实现对 List 的修改;Iterator 仅能遍历,不能修改。 49、怎么确保一个集合不能被修改?

我们很容易想到用final关键字进行修饰,我们都知道

final关键字可以修饰类,方法,成员变量,final修饰的类不能被继承,final修饰的方法不能被重写,final修饰的成员变量必须初始化值,如果这个成员变量是基本数据类型,表示这个变量的值是不可改变的,如果说这个成员变量是引用类型,则表示这个引用的地址值是不能改变的,但是这个引用所指向的对象里面的内容还是可以改变的。

那么,我们怎么确保一个集合不能被修改?首先我们要清楚,集合(map,set,list…)都是引用类型,所以我们如果用final修饰的话,集合里面的内容还是可以修改的。

我们可以做一个实验:

可以看到:我们用final关键字定义了一个map集合,这时候我们往集合里面传值,第一个键值对1,1;我们再修改后,可以把键为1的值改为100,说明我们是可以修改map集合的值的。

那我们应该怎么做才能确保集合不被修改呢? 我们可以采用Collections包下的unmodifiableMap方法,通过这个方法返回的map,是不可以修改的。他会报 java.lang.UnsupportedOperationException错。

同理:Collections包也提供了对list和set集合的方法。

Collections.unmodifiableList(List) Collections.unmodifiableSet(Set)

50、队列和栈是什么?有什么区别?

(1)队列先进先出,栈先进后出。

(2)遍历数据速度不同。

栈只能从头部取数据 也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性;

队列则不同,他基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多。

51、Java8开始ConcurrentHashMap,为什么舍弃分段锁?

ConcurrentHashMap的原理是引用了内部的 Segment ( ReentrantLock )  分段锁,保证在操作不同段 map 的时候, 可以并发执行, 操作同段 map 的时候,进行锁的竞争和等待。从而达到线程安全, 且效率大于 synchronized。

但是在 Java 8 之后, JDK 却弃用了这个策略,重新使用了 synchronized+CAS。

弃用原因

通过  JDK 的源码和官方文档看来, 他们认为的弃用分段锁的原因由以下几点:

加入多个分段锁浪费内存空间。 生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。 为了提高 GC 的效率 新的同步方案

既然弃用了分段锁, 那么一定由新的线程安全方案, 我们来看看源码是怎么解决线程安全的呢?(源码保留了segment 代码, 但并没有使用)。

52、ConcurrentHashMap(JDK1.8)为什么要使用synchronized而不是如ReentranLock这样的可重入锁?

我想从下面几个角度讨论这个问题:

(1)锁的粒度

首先锁的粒度并没有变粗,甚至变得更细了。每当扩容一次,ConcurrentHashMap的并发度就扩大一倍。

(2)Hash冲突

JDK1.7中,ConcurrentHashMap从过二次hash的方式(Segment -> HashEntry)能够快速的找到查找的元素。在1.8中通过链表加红黑树的形式弥补了put、get时的性能差距。 JDK1.8中,在ConcurrentHashmap进行扩容时,其他线程可以通过检测数组中的节点决定是否对这条链表(红黑树)进行扩容,减小了扩容的粒度,提高了扩容的效率。

下面是我对面试中的那个问题的一下看法。

为什么是synchronized,而不是ReentranLock

(1)减少内存开销

假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承AQS来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。

(2)获得JVM的支持

可重入锁毕竟是API这个级别的,后续的性能优化空间很小。 synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得synchronized能够随着JDK版本的升级而不改动代码的前提下获得性能上的提升。

53、concurrentHashMap和HashTable有什么区别

concurrentHashMap融合了hashmap和hashtable的优势,hashmap是不同步的,但是单线程情况下效率高,hashtable是同步的同步情况下保证程序执行的正确性。

但hashtable每次同步执行的时候都要锁住整个结构,如下图:

concurrentHashMap锁的方式是细粒度的。concurrentHashMap将hash分为16个桶(默认值),诸如get、put、remove等常用操作只锁住当前需要用到的桶。

concurrentHashMap的读取并发,因为读取的大多数时候都没有锁定,所以读取操作几乎是完全的并发操作,只是在求size时才需要锁定整个hash。

而且在迭代时,concurrentHashMap使用了不同于传统集合的快速失败迭代器的另一种迭代方式,弱一致迭代器。在这种方式中,当iterator被创建后集合再发生改变就不会抛出ConcurrentModificationException,取而代之的是在改变时new新的数据而不是影响原来的数据,iterator完成后再讲头指针替代为新的数据,这样iterator时使用的是原来的数据。

54、HasmMap和HashSet的区别

(1)先了解一下HashCode

Java中的集合有两类,一类是List,一类是Set。

List:元素有序,可以重复;

Set:元素无序,不可重复;

要想保证元素的不重复,拿什么来判断呢?这就是Object.equals方法了。如果元素有很多,增加一个元素,就要判断n次吗?

显然不现实,于是,Java采用了哈希表的原理。哈希算法也称为散列算法,是将数据依特定算法直接指定到一根地址上,初学者可以简单的理解为,HashCode方法返回的就是对象存储的物理位置(实际上并不是)。

这样一来,当集合添加新的元素时,先调用这个元素的hashcode()方法,就一下子能定位到他应该放置的物理位置上。如果这个位置上没有元素,他就可以直接存储在这个位置上,不用再进行任何比较了。如果这个位置上有元素,就调用它的equals方法与新元素进行比较,想同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际上调用equals方法的次数就大大降低了,几乎只需要一两次。

简而言之,在集合查找时,hashcode能大大降低对象比较次数,提高查找效率。

Java对象的equals方法和hashCode方法时这样规定的:

相等的对象就必须具有相等的hashcode。

如果两个对象的hashcode相同,他们并不一定相同。如果两个对象的hashcode相同,他们并不一定相同。

如果两个Java对象A和B,A和B不相等,但是A和B的哈希码相等,将A和B都存入HashMap时会发生哈希冲突,也就是A和B存放在HashMap内部数组的位置索引相同,这时HashMap会在该位置建立一个链接表,将A和B串起来放在该位置,显然,该情况不违反HashMap的使用规则,是允许的。当然,哈希冲突越少越好,尽量采用好的哈希算法避免哈希冲突。

equals()相等的两个对象,hashcode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。

(2)HashMap和HashSet的区别

55、请谈谈 ReadWriteLock 和 StampedLock

ReadWriteLock包括两种子锁

(1)ReadWriteLock

ReadWriteLock 可以实现多个读锁同时进行,但是读与写和写于写互斥,只能有一个写锁线程在进行。

(2)StampedLock

StampedLock是Jdk在1.8提供的一种读写锁,相比较ReentrantReadWriteLock性能更好,因为ReentrantReadWriteLock在读写之间是互斥的,使用的是一种悲观策略,在读线程特别多的情况下,会造成写线程处于饥饿状态,虽然可以在初始化的时候设置为true指定为公平,但是吞吐量又下去了,而StampedLock是提供了一种乐观策略,更好的实现读写分离,并且吞吐量不会下降。

StampedLock包括三种锁:

(1)写锁writeLock:

writeLock是一个独占锁写锁,当一个线程获得该锁后,其他请求读锁或者写锁的线程阻塞, 获取成功后,会返回一个stamp(凭据)变量来表示该锁的版本,在释放锁时调用unlockWrite方法传递stamp参数。提供了非阻塞式获取锁tryWriteLock。

(2)悲观读锁readLock:

readLock是一个共享读锁,在没有线程获取写锁情况下,多个线程可以获取该锁。如果有写锁获取,那么其他线程请求读锁会被阻塞。悲观读锁会认为其他线程可能要对自己操作的数据进行修改,所以需要先对数据进行加锁,这是在读少写多的情况下考虑的。请求该锁成功后会返回一个stamp值,在释放锁时调用unlockRead方法传递stamp参数。提供了非阻塞式获取锁方法tryWriteLock。

(3)乐观读锁tryOptimisticRead:

tryOptimisticRead相对比悲观读锁,在操作数据前并没有通过CAS设置锁的状态,如果没有线程获取写锁,则返回一个非0的stamp变量,获取该stamp后在操作数据前还需要调用validate方法来判断期间是否有线程获取了写锁,如果是返回值为0则有线程获取写锁,如果不是0则可以使用stamp变量的锁来操作数据。由于tryOptimisticRead并没有修改锁状态,所以不需要释放锁。这是读多写少的情况下考虑的,不涉及CAS操作,所以效率较高,在保证数据一致性上需要复制一份要操作的变量到方法栈中,并且在操作数据时可能其他写线程已经修改了数据,而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数据,但是一致性得到了保证。

56、线程的run()和start()有什么区别?

每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,run()方法称为线程体。通过调用Thread类的start()方法来启动一个线程。

start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。

start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待run方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度其它线程。

run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。

57、为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!

new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。

而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。

58、Synchronized 用过吗,其原理是什么?

(1)可重入性

synchronized的锁对象中有一个计数器(recursions变量)会记录线程获得几次锁;

可重入的好处:可以避免死锁;可以让我们更好的封装代码;

synchronized是可重入锁,每部锁对象会有一个计数器记录线程获取几次锁,在执行完同步代码块时,计数器的数量会-1,直到计数器的数量为0,就释放这个锁。

(2)不可中断性

一个线程获得锁后,另一个线程想要获得锁,必须处于阻塞或等待状态,如果第一个线程不释放锁,第二个线程会一直阻塞或等待,不可被中断;synchronized 属于不可被中断;Lock lock方法是不可中断的;Lock tryLock方法是可中断的; 59、JVM 对 Java 的原生锁做了哪些优化?

(1)自旋锁

在线程进行阻塞的时候,先让线程自旋等待一段时间,可能这段时间其它线程已经解锁,这时就无需让线程再进行阻塞操作了。

自旋默认次数是10次。

(2)自适应自旋锁

自旋锁的升级,自旋的次数不再固定,由前一次自旋次数和锁的拥有者的状态决定。

(3)锁消除

在动态编译同步代码块的时候,JIT编译器借助逃逸分析技术来判断锁对象是否只被一个线程访问,而没有其他线程,这时就可以取消锁了。

4、锁粗化

当JIT编译器发现一系列的操作都对同一个对象反复加锁解锁,甚至加锁操作出现在循环中,此时会将加锁同步的范围粗化到整个操作系列的外部。

锁粒度:不要锁住一些无关的代码。

锁粗化:可以一次性执行完的不要多次加锁执行。

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

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

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

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

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

61、Java 如何实现多线程之间的通讯和协作?

可以通过中断 和 共享变量的方式实现线程间的通讯和协作

比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。

Java中线程通信协作的最常见的两种方式:

1、syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

2、ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

线程间直接的数据交换:

通过管道进行线程间通信:1)字节流;2)字符流

62、Thread 类中的 yield 方法有什么作用?

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

63、为什么说 Synchronized 是非公平锁?

当锁被释放后,任何一个线程都有机会竞争得到锁,这样做的目的是提高效率,但缺点是可能产生线程饥饿现象。

64、请谈谈 volatile 有什么特点,为什么它能保证变量对所有线程的可见性?

volatile只能作用于变量,保证了操作可见性和有序性,不保证原子性。

在Java的内存模型中分为主内存和工作内存,Java内存模型规定所有的变量存储在主内存中,每条线程都有自己的工作内存。

主内存和工作内存之间的交互分为8个原子操作:

lockunlockreadloadassignusestorewrite

volatile修饰的变量,只有对volatile进行assign操作,才可以load,只有load才可以use,,这样就保证了在工作内存操作volatile变量,都会同步到主内存中。

65、为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有什么特性?

Synchronized的并发策略是悲观的,不管是否产生竞争,任何数据的操作都必须加锁。

乐观锁的核心是CAS,CAS包括内存值、预期值、新值,只有当内存值等于预期值时,才会将内存值修改为新值。

66、乐观锁一定就是好的吗?

乐观锁认为对一个对象的操作不会引发冲突,所以每次操作都不进行加锁,只是在最后提交更改时验证是否发生冲突,如果冲突则再试一遍,直至成功为止,这个尝试的过程称为自旋。

乐观锁没有加锁,但乐观锁引入了ABA问题,此时一般采用版本号进行控制; 也可能产生自旋次数过多问题,此时并不能提高效率,反而不如直接加锁的效率高; 只能保证一个对象的原子性,可以封装成对象,再进行CAS操作;

67、请尽可能详尽地对比下 Synchronized 和 ReentrantLock 的异同。

(1)相似点

它们都是阻塞式的同步,也就是说一个线程获得了对象锁,进入代码块,其它访问该同步块的线程都必须阻塞在同步代码块外面等待,而进行线程阻塞和唤醒的代码是比较高的。

(2)功能区别

Synchronized是java语言的关键字,是原生语法层面的互斥,需要JVM实现;ReentrantLock 是JDK1.5之后提供的API层面的互斥锁,需要lock和unlock()方法配合try/finally代码块来完成。 Synchronized使用较ReentrantLock 便利一些; 锁的细粒度和灵活性:ReentrantLock强于Synchronized;

(3)性能区别

Synchronized引入偏向锁,自旋锁之后,两者的性能差不多,在这种情况下,官方建议使用Synchronized。

① Synchronized

Synchronized会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。

在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计数器+1,相应的执行monitorexit时,计数器-1,当计数器为0时,锁就会被释放。如果获取锁失败,当前线程就要阻塞,知道对象锁被另一个线程释放为止。

② ReentrantLock

ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有如下三项:

等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized避免出现死锁的情况。通过lock.lockInterruptibly()来实现这一机制; 公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁是非公平锁;ReentrantLock默认也是非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好; 锁绑定多个条件,一个ReentrantLock对象可以同时绑定多个对象。ReentrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像Synchronized要么随机唤醒一个线程,要么唤醒全部线程。

68、ReentrantLock 是如何实现可重入性的?

(1)什么是可重入性

一个线程持有锁时,当其他线程尝试获取该锁时,会被阻塞;而这个线程尝试获取自己持有锁时,如果成功说明该锁是可重入的,反之则不可重入。

(2)synchronized是如何实现可重入性

synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。每个锁对象内部维护一个计数器,该计数器初始值为0,表示任何线程都可以获取该锁并执行相应的方法。根据虚拟机规范要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了对象的锁,把锁的计数器+1,相应的在执行monitorexit指令后锁计数器-1,当计数器为0时,锁就被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。

(3)ReentrantLock如何实现可重入性

ReentrantLock使用内部类Sync来管理锁,所以真正的获取锁是由Sync的实现类控制的。Sync有两个实现,分别为NonfairSync(非公公平锁)和FairSync(公平锁)。Sync通过继承AQS实现,在AQS中维护了一个private volatile int state来计算重入次数,避免频繁的持有释放操作带来的线程问题。

(4)ReentrantLock代码实例

// Sync继承于AQS abstract static class Sync extends AbstractQueuedSynchronizer { ... } // ReentrantLock默认是非公平锁 public ReentrantLock() { sync = new NonfairSync(); } // 可以通过向构造方法中传true来实现公平锁 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } protected final boolean tryAcquire(int acquires) { // 当前想要获取锁的线程 final Thread current = Thread.currentThread(); // 当前锁的状态 int c = getState(); // state == 0 此时此刻没有线程持有锁 if (c == 0) { // 虽然此时此刻锁是可以用的,但是这是公平锁,既然是公平,就得讲究先来后到, // 看看有没有别人在队列中等了半天了 if (!hasQueuedPredecessors() && // 如果没有线程在等待,那就用CAS尝试一下,成功了就获取到锁了, // 不成功的话,只能说明一个问题,就在刚刚几乎同一时刻有个线程抢先了 =_= // 因为刚刚还没人的,我判断过了 compareAndSetState(0, acquires)) { // 到这里就是获取到锁了,标记一下,告诉大家,现在是我占用了锁 setExclusiveOwnerThread(current); return true; } } // 会进入这个else if分支,说明是重入了,需要操作:state=state+1 // 这里不存在并发问题 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } // 如果到这里,说明前面的if和else if都没有返回true,说明没有获取到锁 return false; }

(5)代码分析

当一个线程在获取锁过程中,先判断state的值是否为0,如果是表示没有线程持有锁,就可以尝试获取锁。 当state的值不为0时,表示锁已经被一个线程占用了,这时会做一个判断current==getExclusiveOwnerThread(),这个方法返回的是当前持有锁的线程,这个判断是看当前持有锁的线程是不是自己,如果是自己,那么将state的值+1,表示重入返回即可。

69、什么是锁消除和锁粗化?

(1)锁消除

所消除就是虚拟机根据一个对象是否真正存在同步情况,若不存在同步情况,则对该对象的访问无需经过加锁解锁的操作。

比如StringBuffer的append方法,因为append方法需要判断对象是否被占用,而如果代码不存在锁竞争,那么这部分的性能消耗是无意义的。于是虚拟机在即时编译的时候就会将上面的代码进行优化,也就是锁消除。

@Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }

从源码可以看出,append方法用了 synchronized关键字,它是线程安全的。但我们可能仅在线程内部把StringBuffer当做局部变量使用;StringBuffer仅在方法内作用域有效,不存在线程安全的问题,这时我们可以通过编译器将其优化,将锁消除,前提是Java必须运行在server模式,同时必须开启逃逸分析;

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks 其中+DoEscapeAnalysis表示开启逃逸分析,+EliminateLocks表示锁消除。 public static String createStringBuffer(String str1, String str2) { StringBuffer sBuf = new StringBuffer(); sBuf.append(str1);// append方法是同步操作 sBuf.append(str2); return sBuf.toString(); }

逃逸分析:比如上面的代码,它要看sBuf是否可能逃出它的作用域?如果将sBuf作为方法的返回值进行返回,那么它在方法外部可能被当作一个全局对象使用,就有可能发生线程安全问题,这时就可以说sBuf这个对象发生逃逸了,因而不应将append操作的锁消除,但我们上面的代码没有发生锁逃逸,锁消除就可以带来一定的性能提升。 

(2)锁粗化

锁的请求、同步、释放都会消耗一定的系统资源,如果高频的锁请求反而不利于系统性能的优化,锁粗化就是把多次的锁请求合并成一个请求,扩大锁的范围,降低锁请求、同步、释放带来的性能损耗。

70、跟 Synchronized 相比,可重入锁 ReentrantLock 其实现原理有什么不同?

(1)都是可重入锁;

(2)ReentrantLock内部是实现了Sync,Sync继承于AQS抽象类。Sync有两个实现,一个是公平锁,一个是非公平锁,通过构造函数定义。AQS中维护了一个state来计算重入次数,避免频繁的持有释放操作带来的线程问题。

(3)ReentrantLock只能定义代码块,而Synchronized可以定义方法和代码块;

4、Synchronized是JVM的一个内部关键字,ReentrantLock是JDK1.5之后引入的一个API层面的互斥锁;

5、Synchronized实现自动的加锁、释放锁,ReentrantLock需要手动加锁和释放锁,中间可以暂停;

6、Synchronized由于引进了偏向锁和自旋锁,所以性能上和ReentrantLock差不多,但操作上方便很多,所以优先使用Synchronized。

71、那么请谈谈 AQS 框架是怎么回事儿?

(1)AQS是AbstractQueuedSynchronizer的缩写,它提供了一个FIFO队列,可以看成是一个实现同步锁的核心组件。

AQS是一个抽象类,主要通过继承的方式来使用,它本身没有实现任何的同步接口,仅仅是定义了同步状态的获取和释放的方法来提供自定义的同步组件。

(2)AQS的两种功能:独占锁和共享锁

(3)AQS的内部实现

AQS的实现依赖内部的同步队列,也就是FIFO的双向队列,如果当前线程竞争失败,那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。

AQS队列内部维护的是一个FIFO的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的范文前驱和后继节点。每个Node其实是由线程封装,当线程争抢锁失败后会封装成Node加入到AQS队列中。

72、AQS 对资源的共享方式?

AQS定义两种资源共享方式

(1)Exclusive(独占)

只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:

公平锁:按照线程在队列中的排队顺序,先到者先拿到锁非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的

(2)Share(共享)

多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。

ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。

73、如何让 Java 的线程彼此同步? synchronizedvolatileReenreantLock

使用局部变量实现线程同步

74、你了解过哪些同步器?请分别介绍下。

(1)Semaphore同步器

特征:

经典的信号量,通过计数器控制对共享资源的访问 Semaphore(int count):创建拥有count个许可证的信号量 acquire()/acquire(int num) : 获取1/num个许可证 release/release(int num) : 释放1/num个许可证

(2)CountDownLatch同步器

特征:

必须发生指定数量的事件后才可以继续运行(比如赛跑比赛,裁判喊出3,2,1之后大家才同时跑) CountDownLatch(int count):必须发生count个数量才可以打开锁存器 await:等待锁存器 countDown:触发事件

(3)CyclicBarrier同步器

特征:

适用于只有多个线程都到达预定点时才可以继续执行(比如斗地主,需要等齐三个人才开始) CyclicBarrier(int num) :等待线程的数量 CyclicBarrier(int num, Runnable action) :等待线程的数量以及所有线程到达后的操作 await() : 到达临界点后暂停线程

(4)交换器(Exchanger)同步器

(5)Phaser同步器

75、Java 中的线程池是如何实现的

创建一个阻塞队列来容纳任务,在第一次执行任务时创建足够多的线程,并处理任务,之后每个工作线程自动从任务队列中获取线程,直到任务队列中任务为0为止,此时线程处于等待状态,一旦有工作任务加入任务队列中,即刻唤醒工作线程进行处理,实现线程的可复用性。

线程池一般包括四个基本组成部分:

(1)线程池管理器

用于创建线程池,销毁线程池,添加新任务。

(2)工作线程

线程池中线程,可循环执行任务,在没有任务时处于等待状态。

(3)任务队列

用于存放没有处理的任务,一种缓存机制。

(4)任务接口

每个任务必须实现的接口,供工作线程调度任务的执行,主要规定了任务的开始和收尾工作,和任务的状态。

76、创建线程池的几个核心构造参数 // Java线程池的完整构造函数 public ThreadPoolExecutor( int corePoolSize, // 线程池长期维持的最小线程数,即使线程处于Idle状态,也不会回收。 int maximumPoolSize, // 线程数的上限 long keepAliveTime, // 线程最大生命周期。 TimeUnit unit, //时间单位 BlockingQueue workQueue, //任务队列。当线程池中的线程都处于运行状态,而此时任务数量继续增加,则需要一个容器来容纳这些任务,这就是任务队列。 ThreadFactory threadFactory, // 线程工厂。定义如何启动一个线程,可以设置线程名称,并且可以确认是否是后台线程等。 RejectedExecutionHandler handler // 拒绝任务处理器。由于超出线程数量和队列容量而对继续增加的任务进行处理的程序。 ) 77、线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗?

线程池中的线程是在第一次提交任务submit时创建的

创建线程的方式有继承Thread和实现Runnable,重写run方法,start开始执行,wait等待,sleep休眠,shutdown停止。

(1)newSingleThreadExecutor:单线程池。

顾名思义就是一个池中只有一个线程在运行,该线程永不超时,而且由于是一个线程,当有多个任务需要处理时,会将它们放置到一个无界阻塞队列中逐个处理,它的实现代码如下:

public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3