常见面试题
Posted 蒙面侠1024
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见面试题相关的知识,希望对你有一定的参考价值。
面试题
- 零、开场介绍
- 一、JAVA基础
- 二、JAVA容器
- 三、多线程
- 1.为什么要使用多线程?多线程可能出现什么问题 ?
- 2. java实现多线程的方式有几种?
- 3. Runnable和Callable有什么区别?
- 4. 线程和进程的区别
- 5. 什么是守护线程?
- 6. java的线程大概有几种状态?
- 7. 说说与线程相关的方法
- 8. sleep 和 wait方法的区别?
- 9. 线程的 run() 和 start() 有什么区别?
- 10. sleep()和yield()有什么区别?
- 11. 死锁的四个条件?
- 12. 怎么在开发中避免死锁?
- 13. 怎么检测死锁?
- 14. 怎么解决死锁?
- 15.线程安全是什么?如何保证线程安全?
- 16. 10个线程,一个线程出错,怎么通知其它的线程。
- 17. 如何避免指令重排序
- 18. volatile除了避免指令重排序还有什么功能
- 19. 说说volatile关键字
- 20. ThreadLocal有什么作用?有哪些使用场景?
- 21. 高并发下,如何安全地修改同一行数据?
- 22. synchronized 和 volatile 的区别是什么?
- 23. synchronized 和 Lock 有什么区别?
- 24. synchronized 和 ReentrantLock 区别是什么?
- 四、计算机网络
- 五、JVM
- 六、数据库
- 七、设计模式
- 八、框架
- 九、Redis
- 十、Kafka
- 十一、项目
- 1. 校园论坛项目介绍
- 2. 这个项目最具挑战的是什么?
- 3. 项目中如何使用多线程?
- 4. 发帖支持几层评论?如何设计表,来拉取这些具有层级关系的评论?
- 5. 如何实现系统通知?Kafka里的数据如何持久化,如何处理消息堆积问题?
- 6. MySQL里存什么数据?
- 7. 登录注册是如何实现的?用Cookie做了什么? Cookie被窃取了该怎么办?
- 8. 项目Kafka使用场景。为什么要用Kafka? Kafka为什么吞吐量高?
- 9. 如何做到显示首页的热度最高帖子?如何更新缓存?
- 10. Redis存了什么数据?缓存过期时间是多少?如何解决缓存一致性问题?
- 11. 如何识别热点数据?热度如何计算?如何更新热度?
- 15. ES如何实现全文搜索的功能?ES的底层数据结构?
- 16. ES倒排索引为什么能够加速搜索?
- 17. 项目中SpringSecurity的权限模型是怎么样的?
- 18 .用户的授权信息如何存储?
- 19. 用户名密码等身份信息如何存储?
- 20.如何对不文明的词汇进行过滤?
- 21.如何对整个系统进行流量监控?
- 十二、算法
零、开场介绍
面试官,您好!我叫秀儿。大学时间我主要利用课外时间学习了Java 以及 SpringBoot、 MyBatis等框架。在校期间参与过一个论坛系统的开发,这个系统的主要用了SpringBoot,MyBatis-Plus和Kafaka,Es等框架。另外,我在大学的时候参加过几次xxx比赛,成功获得了第二名的成绩。说到业余爱好的话,我比较喜欢通过博客整理分享自己所学知识。生活中我是一个比较积极乐观的人,一般会通过运动打球的方式来放松。我一直都非常想加入贵公司,我觉得贵公司的文化和技术氛围我都非常喜欢,期待能与你共事!
一、JAVA基础
1. Java和C++,C#区别
- 都是面向对象的语言,都支持封装、继承和多态
- Java 不提供指针来直接访问内存,程序内存更加安全
- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
- Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
- C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。
2. 面向对象和面向过程的区别
面向过程: 面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。
面向对象: 面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低。
3. JDK,JRE区别
Java运行时环境(JRE)是将要执行Java程序的Java虚拟机。它同时也包含了执行applet需要的浏览器插件。Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。
4. ==和 equals 的区别?
==比较
- 基本数据类型比较的是值;
- 引用类型比较的是地址值。
equals(Object o):
1)不能比较基本数据类型,基本数据类型不是类类型;
2)比较引用类型时(该方法继承自Object,在object中比较的是地址值)等同于”==”;
Object类中的方法,所以,在每一个java类中,都会有这个方法,因为每一个java类都是直接或者间接的Object类的子类,会继承到这个方法。
5. 为什么重写equals还要重写hashcode?
如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。
6. 说说抽象类和接口
- 抽象类可以有构造方法;接口中不能有构造方法。
- 抽象类中可以有普通成员变量;接口中没有普通成员变量。
- 抽象类中可以包含非抽象普通方法;JDK1.8 以前接口中的所有方法默认都是抽象的,JDK1.8 开始方法可以有 default 实现和 static 方法。
- 抽象类中的抽象方法的访问权限可以是 public、protected 和 default;接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型。
- 抽象类中可以包含静态方法;JDK1.8 前接口中不能包含静态方法,JDK1.8 及以后可以包含已实现的静态方法。
- 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量可以是任意访问权限;接口中变量默认且只能是 public static final 类型。
- 一个类可以实现多个接口,用逗号隔开,但只能继承一个抽象类。
- 接口不可以实现接口,但可以继承接口,并且可以继承多个接口,用逗号隔开。
7. String,StringBuffer和StringBuilder。
相同点:
- 都可以储存和操作字符串
- 都使用 final 修饰,不能被继承
- 提供的 API 相似
区别:
- String 是只读字符串,String 对象内容是不能被改变的
- StringBuffer 和 StringBuilder 的字符串对象可以对字符串内容进行修改,在修改后的内存地址不会发生改变
- StringBuilder 线程不安全;StringBuffer 线程安全
- 方法体内没有对字符串的并发操作,且存在大量字符串拼接操作,建议使用 StringBuilder,效率较高。
8. final在java中的作用。
final 语义是不可改变的。
- 被 final 修饰的类,不能够被继承
- 被 final 修饰的成员变量必须要初始化,赋初值后不能再重新赋值(可以调用对象方法修改属性值)。对基本类型来 说是其值不可变;对引用变量来说其引用不可变,即不能再指向其他的对象
- 被 final 修饰的方法不能重写
9. final修饰的对象什么时候被初始化?
final类型的静态变量(即编译期常量)在类加载时就会被初始化放入常量池中,其他的非编译期常量是在运行期初始化的。
10. final finally finalize()区别
- final 表示最终的、不可改变的。用于修饰类、方法和变量。final 修饰的类不能被继承;final 方法也同样只能使用,不能重写,但能够重载;final 修饰的成员变量必须在声明时给定初值或者在构造方法内设置初始值,只能读取,不可修改;final 修饰的局部变量必须在声明时给定初值;final 修饰的变量是非基本类型,对象的引用地址不能变,但对象的属性值可以改变
- finally 异常处理的一部分,它只能用在 try/catch 语句中,表示希望 finally 语句块中的代码最后一定被执行(存在一些情况导致 finally 语句块不会被执行,如 jvm 结束)
- finalize() 是在 java.lang.Object 里定义的,Object 的 finalize() 方法什么都不做,对象被回收时 finalize() 方法会被调用。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要清理工作,在垃圾收集器删除对象之前被调用的。一般情况下,此方法由JVM调用。特殊情况下,可重写 finalize() 方法,当对象被回收的时候释放一些资源,须调用 super.finalize() 。
11. 什么是反射?有什么作用?
Java 反射,就是在运行状态中
- 获取任意类的名称、package 信息、所有属性、方法、注解、类型、类加载器、modifiers(public、static)、父类、现实接口等
- 获取任意对象的属性,并且能改变对象的属性
- 调用任意对象的方法
- 判断任意一个对象所属的类
- 实例化任意一个类的对象
Java 的动态就体现在反射。通过反射我们可以实现动态装配,降低代码的耦合度;动态代理等。反射的过度使用会严重消耗系统资源。
JDK 中 java.lang.Class 类,就是为了实现反射提供的核心类之一。
一个 jvm 中一种 Class 只会被加载一次。
12.常见的异常类有哪些?
java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类 Exception(异常)和 Error(错误)。Exception 能被程序本身处理(try-catch), Error 是无法处理的(只能尽量避免)。
Exception 和 Error 二者都是 Java 异常处理的重要子类,各自都包含大量子类。
-
Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。
-
Error :Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获 。例如,Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
13. Java如何序列化?
序列化:将 Java 对象转换成字节流的过程。
反序列化:将字节流转换成 Java 对象的过程。
当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。
序列化的实现:类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化。
注意事项:
- 某个类可以被序列化,则其子类也可以被序列化
- 对象中的某个属性是对象类型,需要序列化也必须实现 Serializable 接口
- 声明为 static 和 transient 的成员变量,不能被序列化。static 成员变量是描述类级别的属性,transient 表示临时数据
- 反序列化读取序列化对象的顺序要保持一致
14.你知道java8的新特性吗,请简单介绍一下?
- Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。
- 方法引用− 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 默认方法− 默认方法就是一个在接口里面有了一个实现的方法。
- 新工具− 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Date Time API − 加强对日期与时间的处理。
- Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Nashorn, javascript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
15. 什么是多态?如何实现?有什么好处?
多态:
同一个接口,使用不同的实例而执行不同操作。同一个行为具有多个不同表现形式或形态的能力。
实现多态有三个条件:
- 继承
- 子类重写父类的方法
- 父类引用变量指向子类对象
实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
Java 中使用父类的引用变量调用子类重写的方法,即可实现多态。
二、JAVA容器
1. 集合了解吧,说说集合有几大类,分别介绍一下
Java 集合, 也叫作容器,主要是由两大接口派生而来:一个是 Collecton接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。对于Collection 接口,下面又有三个主要的子接口:List、Set 和 Queue。
- List(对付顺序的好帮手): 存储的元素是有序的、可重复的。
- Set(注重独一无二的性质): 存储的元素是无序的、不可重复的。
- Queue(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
- Map(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),“x” 代表 key,“y” 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
2. hashmap和concurenthashmap区别
-
HashMap和ConcurentHashMap的主要区别是HashMaP是线程不安全
-
ConcurentHashMap是线程安全
JDK1.7
- HashMap的线程不安全主要是发生在扩容函数中,即根源是在transfer函数中,由于采用头插法,在多线程高并发环境下会造成死循环或数据丢失问题。
- ConcurentHashMap采用分段锁,可重入锁Segment类,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
JDK1.8
- HashMap在JDK 1.8中采用尾插法修复了1.7中由于头插法引起的线程不安全(死循环和数据丢失)。在JDK 1.8中进行put操作会引起线程不安全而导致数据覆盖。
- ConcurentHashMap采用CAS和synchronized来保证线程安全,使用的是锁分离思想,只是锁住的是一个node,而锁住Node之前的操作是基于在volatile和CAS之上无锁并且线程安全的,并且大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现。
3. Array,ArrayList和LinkedList的区别?ArrayList如何扩容?
Array 即数组
定义一个 Array 时,必须指定数组的数据类型及数组长度,即数组中存放的元素个数固定并且类型相同。
ArrayList 是动态数组,长度动态可变,会自动扩容。不使用泛型的时候,可以添加不同类型元素。
ArrayList和LinkedList的区别
- ArrayList 基于动态数组实现的非线程安全的集合;LinkedList 基于双向链表实现的非线程安全的集合。
- 扩容问题:ArrayList 使用数组实现,无参构造函数默认初始化长度为 10,数组扩容是会将原数组中的元素重新拷贝到新数组中,长度为原来的 1.5 倍(扩容代价高);LinkedList 不存在扩容问题,新增元素放到集合尾部,修改相应的指针节点即可。
- LinkedList 比 ArrayList 更占内存,因为 LinkedList 为每一个节点存储了两个引用节点,一个指向前一个元素,一个指向下一个元素。
- 对于随机 index 访问的 get 和 set 方法,一般 ArrayList 的速度要优于 LinkedList。因为 ArrayList 直接通过数组下标直接找到元素;LinkedList 要移动指针遍历每个元素直到找到为止。
- 新增和删除元素,一般 LinkedList 的速度要优于 ArrayList。因为 ArrayList 在新增和删除元素时,可能扩容和复制数组;LinkedList 实例化对象需要时间外,只需要修改节点指针即可。
- LinkedList 集合不支持高效的随机访问(RandomAccess)
- ArrayList 的空间浪费主要体现在在list列表的结尾预留一定的容量空间;LinkedList 的空间花费则体现在它的每一个元素都需要消耗存储指针节点对象的空间。
- 都是非线程安全,允许存放 null
4. hashMap底层实现了解过吗?具体讲讲
- HashMap 基于 Hash 算法实现,通过 put(key,value) 存储,get(key) 来获取 value
- 当传入 key 时,HashMap 会根据 key,调用 hash(Object key) 方法,计算出 hash 值,根据 hash 值将 value 保存在 Node 对象里,Node 对象保存在数组里
- 当计算出的 hash 值相同时,称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value
- 当 hash 冲突的个数:小于等于 8 使用链表;大于 8 且 tab length 大于等于 64 时,使用红黑树解决链表查询慢的问题
ps:
- 上述是 JDK 1.8 HashMap 的实现原理,并不是每个版本都相同,比如 JDK 1.7 的 HashMap 是基于数组 + 链表实现,所以 hash 冲突时链表的查询效率低
- hash(Object key) 方法的具体算法是 (h = key.hashCode()) ^ (h >>> 16),经过这样的运算,让计算的 hash 值分布更均匀
5. 说说hashMap的jdk1.8的优化
JDK1.8在JDK1.7的基础上针对一个链上数据过多(即拉链过长的情况)导致性能下降,增加了红黑树来进行优化。即当链表超过8时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。
6. HashMap 和 hashTable的区别?
JDK 1.8 中 HashMap
和 Hashtable
主要区别如下:
- 线程安全性不同。HashMap 线程不安全;Hashtable 中的方法是 synchronized 的。
- key、value 是否允许 null。HashMap 的 key 和 value 都是可以是 null,key 只允许一个 null;Hashtable 的 key 和 value 都不可为 null。
- 迭代器不同。HashMap 的 Iterator 是 fail-fast 迭代器;Hashtable 还使用了 enumerator 迭代器。
- hash的计算方式不同。HashMap 计算了 hash值;Hashtable 使用了 key 的 hashCode方法。
- 默认初始大小和扩容方式不同。HashMap 默认初始大小 16,容量必须是 2 的整数次幂,扩容时将容量变为原来的2倍;Hashtable 默认初始大小 11,扩容时将容量变为原来的 2 倍加 1。
- 是否有 contains 方法。HashMap 没有 contains 方法;Hashtable 包含 contains 方法,类似于 containsValue。
- 父类不同。HashMap 继承自 AbstractMap;Hashtable 继承自 Dictionary。
7. HashSet和HashMap有什么区别?
HashMap
- 实现 Map 接口
- 键值对的方式存储
- 新增元素使用 put(K key, V value) 方法
- 底层通过对 key 进行 hash,使用数组 + 链表或红黑树对 key、value 存储
HashSet
- 实现 Set 接口
- 存储元素对象
- 新增元素使用 add(E e) 方法
- 底层是采用 HashMap 实现,大部分方法都是通过调用 HashMap 的方法来实现
8. 说说ConcurrentHashMap的底层实现
ConcurrentHashMap1.7 实现原理
ConcurrentHashMap 采用分段锁设计、将一个大的 HashMap 集合拆分成 n 多个不同的小的 HashTable(Segment),默认的情况下是分成 16 个不同的 Segment,每个Segment 中都有自己独立的 HashEntry<K,V>[] table
;
数组+Segments 分段锁+HashEntry 链表实现
使用 Lock 锁+CAS 乐观锁+UNSAFE 类
PUT 方法流程
- 第一次需要计算出:key 出存放在那个 Segment 对象中
- 还需要计算 key 存放在 Segment 对象中具体 index 位置。
ConcurrentHashMap1.8 实现原理
Put 原理 锁的粒度非常小,对每个数组 index 位置上锁 对 1.7ConcurrentHashMap 实现优化
- 取消 segment 分段设计,使用 synchronized 锁
- synchronized 在 JDK1.6 开始做了优化 默认实现锁的升级过程
JDK 1.7 到 JDK 1.8 中的 ConcurrentHashMap 最大的改动:
链表上的 Node 超过 8 个改为红黑树,查询复杂度 O(logn)
ReentrantLock 显示锁改为 synchronized,说明 JDK 1.8 中 synchronized 锁性能赶上或超过 ReentrantLock
9. Jdk中map的实现都有什么:
HashMap、TreeMap、Hashtable、LinkedHashMap。
10. LinkedHashMap跟HashMap的关系:
LinkedHashMap维护了一个双向循环链表,是有序的,保留了元素的插入顺序。
11. 红黑树和完全平衡二叉树(AVL)
红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。
平衡二叉树(AVL)的性质
它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
区别:
1、红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
2、平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知
三、多线程
1.为什么要使用多线程?多线程可能出现什么问题 ?
由于创建和销毁线程都需要很大的开销,运用线程池就可以大大的缓解这些内存开销很大的问题;可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存 。
多线程并发编程并不总是能提高程序的执行效率和运行速度,而且可能存在一些问题,包括内存泄漏、上下文切换、死锁以及受限于硬件和软件的资源限制问题等。
2. java实现多线程的方式有几种?
有4种方式可以用来创建线程:
- 继承Thread类
class MyThread extends Thread{
public void run(){
System.out.println("线程运行");
}
}
public class Test{
public static void main(String[] args){
MyThread thread=new MyThread();
thread.start();//开启线程
}
}
- 实现Runnable接口
class MyThread implements Runnable
{
public void run(){
System.out.println("线程运行");
}
}
public class Test{
public static void main(String[] args){
MyThread thread=new MyThread();
Thread t=new Thread(thread);
t.start();//开启线程
}
}
- 还有一种方式是实现Callable接口
实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。
import java.util.concurrent.*;
public class CallableAndFuture{
//创建线程
public static class CallableTest implements Callable<String>{
public String call() throws Exception{
return "Hello World";
}
}
public static void main(String[] args){
ExecutorService threadPool=Executors.newSingleThreadExecutor();
//启动线程
Future<String> future=threadPool.submit(new CallableTest());
try{
System.out.println("等待线程执行完成");
System.out.println(future.get());//等待线程结束,并获取返回结果
}
catch(Exception e){
e.printStackTrace();
}
}
}
3. Runnable和Callable有什么区别?
主要区别
- Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
- Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
4. 线程和进程的区别
-
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即为一个进程的创建、运行以及消亡的过程。
-
线程是比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程,多个线程共享进程的堆和方法区内存资源,每个线程都有自己的程序计数器、虚拟机栈和本地方法栈。由于线程共享进程的内存,因此系统产生一个线程或者在多个线程之间切换工作时的负担比进程小得多,线程也称为轻量级进程。
-
进程和线程最大的区别是,各进程是独立的,而各线程则不一定独立,因为同一进程中的多个线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护,进程则相反
5. 什么是守护线程?
Java线程分为用户线程和守护线程。
- 守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
- Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。
6. java的线程大概有几种状态?
线程在运行的生命周期中的任何时刻只能是5 种不同状态的其中一种。
- 初始状态(NEW):线程已经构建,尚未启动。
- 运行状态(RUNNABLE):包括就绪(READY)和运行中(RUNNING)两种状态,统称为运行状态。
- 阻塞状态(BLOCKED):线程被锁阻塞。
- 等待状态(WAITING):线程需要等待其他线程做出特定动作(通知或中断)。
- 终止状态(TERMINATED):当前线程已经执行完毕。
7. 说说与线程相关的方法
- 加锁对象的 wait() 方法,使一个线程处于等待状态,并且释放所持有的对象的锁
- 加锁对象的 notify() 方法,由 JVM 唤醒一个处于等待状态的线程,具体哪个线程不确定,且与优先级无关
- 加锁对象的 notityAll() 方法,唤醒所有处入等待状态的线程,让它们重新竞争对象的锁
- 线程的 sleep() 方法,使一个正在运行的线程处于睡眠状态,是静态方法,调用此方法要捕捉 InterruptedException 异常
- JDK 1.5 开始通过 Lock 接口提供了显式锁机制,丰富了锁的功能,可以尝试加锁和加锁超时。Lock 接口中定义了加锁 lock()、释放锁 unlock() 方法 和 newCondition() 产生用于线程之间通信的 Condition 对象的方法
- JDK 1.5 开始提供了信号量 Semaphore 机制,信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须调用 Semaphore 对象的 acquire() 方法得到信号量的许可;在完成对资源的访问后,线程必须调用 Semaphore 对象的 release() 方法向信号量归还许可
8. sleep 和 wait方法的区别?
- sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
- wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
9. 线程的 run() 和 start() 有什么区别?
-
start方法用于启动线程,真正实现了多线程运行。在调用T线程的sturt方法后.线程会在后台执行,无须等待run方法体的代码执行完毕,就可以继续执行下面的代码。
-
在通过调用Thrend 类的start方法启动一个线程时,此线程处于就绪状态,并没有运行。
-
run方法也叫作线程体。包含了要执行的线程的逻辑代码,在调用run 方法后.线程会进人运行状态,开始运行run方法中的代码。在run 方法运行结束后,该线程终止,CPU再次调度其他线程。
10. sleep()和yield()有什么区别?
- sleep() 方法给其他线程运行机会时不考虑线程的优先级;yield() 方法只会给相同优先级或更高优先级的线程运行的机会
- 线程执行 sleep() 方法后进入超时等待状态;线程执行 yield() 方法转入就绪状态,可能马上又得得到执行
- sleep() 方法声明抛出 InterruptedException;yield() 方法没有声明抛出异常
- sleep() 方法需要指定时间参数;yield() 方法出让 CPU 的执行权时间由 JVM 控制
11. 死锁的四个条件?
- 互斥条件:一个锁一次只能由一个进程占有
- 不可剥夺条件:一个进程占有的资源在使用完之前不可以被其他进程剥夺,只能由该进程释放之后才能被其他进程获取。
- 请求和保持条件:一个进程在申请资源的同时保持已经占有的资源不释放。
- 循环等待条件:同时需要A、B两个资源的进程分别占有了A和B,形成了两个进程都阻塞并等待对方释放资源的状态。
12. 怎么在开发中避免死锁?
避免死锁:
对于以上 4 个条件,只要破坏其中一个条件,就可以避免死锁的发生。
对于第一个条件 “互斥” 是不能破坏的,因为加锁就是为了保证互斥。
其他三个条件,我们可以尝试
- 一次性申请所有的资源,破坏 “占有且等待” 条件
- 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 “不可抢占” 条件
- 按序申请资源,破坏 “循环等待” 条件
编程中的最佳实践:
- 使用 Lock 的 tryLock(long timeout, TimeUnit unit)的方法,设置超时时间,超时可以退出防止死锁
- 尽量使用并发工具类代替加锁
- 尽量降低锁的使用粒度
- 尽量减少同步的代码块
13. 怎么检测死锁?
jstack -l可以查看堆栈运行的状态,-l会显示锁状态,里面会报告死锁。
14. 怎么解决死锁?
1、系统重启
2、撤销代价比较低的线程,例如低优先级的线程
15.线程安全是什么?如何保证线程安全?
当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。
就是我们在需要的时候去手动的获取锁和释放锁,甚至我们还可以中断获取以及超时获取的同步特性,但是从使用上说Lock明显没有synchronized使用起来方便快捷。
16. 10个线程,一个线程出错,怎么通知其它的线程。
重写了自定义线程组的uncaughtException()方法后,加上相应的中断操作和判断,是可以做到当某个线程出现异常然后中断时,其他的线程也会马上运行结束,不过这里的其他线程指得是当前和出现异常的线程在同一线程组的线程们,而在异常线程之后新加入线程组的线程就不会被影响到的,从正常线程可以持续运行下去就可以证明这点,所以即使采取了异常中断的手段,但是当线程组内的某个线程出现异常,只会影响到当前在线程组内的线程的运行情况,异常之后才加入到线程组的线程就不会被停止了。
17. 如何避免指令重排序
观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
- 它会强制将对缓存的修改操作立即写入主存;
- 如果是写操作,它会导致其他CPU中对应的缓存行无效。
18. volatile除了避免指令重排序还有什么功能
Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性。
19. 说说volatile关键字
volatile 可以说是 JVM 提供的最轻量级的同步机制,当一个变量定义为volatile之后,它将具备两种特性:
-
保证此变量对所有线程的可见性。
而普通变量不能做到这一点,普通变量的值在线程间传递均需要通过主内存来完成。注意,volatile 虽然保证了可见性,但是 Java 里面的运算并非原子操作,导致 volatile 变量的运算在并发下一样是不安全的。而 synchronized 关键字则是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得线程安全的。 -
禁止指令重排序优化。
普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。
20. ThreadLocal有什么作用?有哪些使用场景?
ThreadLocal 是线程本地存储,在每个线程都创建了一个ThreadLocalMap对象,每个线程可以访问自己内部ThreadLocalMal对象内的value. 通过这种方式,避免资源在多线程见共享。
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都会获得该变量的副本
副本之间相互独立,这样每一个线程都可以随意更改自己的变量副本,而不会对其他线程产生影响。
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
21. 高并发下,如何安全地修改同一行数据?
- 可以将数据加载到缓存中,利用 CAS 方式进行更新
- 也可以将所有请求放到同一个消息队列里,异步返回,按顺序执行更新
注意:
- 如果使用悲观锁,在并发请求量很大的情况下,会导致服务和数据连接数耗尽,系统卡死
22. synchronized 和 volatile 的区别是什么?
作用:
- synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
- volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。
区别:
- synchronized 可以作用于变量、方法、对象;volatile 只能作用于变量。
- synchronized 可以保证线程间的有序性(个人猜测是无法保证线程内的有序性,即线程内的代码可能被 CPU 指令重排序)、原子性和可见性;volatile 只保证了可见性和有序性,无法保证原子性。
- synchronized 线程阻塞,volatile 线程不阻塞。
- volatile 本质是告诉 jvm 当前变量在寄存器中的值是不安全的需要从内存中读取;sychronized 则是锁定当前变量,只有当前线程可以访问到该变量其他线程被阻塞。
- volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。
23. synchronized 和 Lock 有什么区别?
- 实现层面不一样。synchronized 是 Java 关键字,JVM层面 实现加锁和释放锁;Lock 是一个接口,在代码层面实现加锁和释放锁
- 是否自动释放锁。synchronized 在线程代码执行完或出现异常时自动释放锁;Lock 不会自动释放锁,需要再 finally {} 代码块显式地中释放锁
- 是否一直等待。synchronized 会导致线程拿不到锁一直等待;Lock 可以设置尝试获取锁或者获取锁失败一定时间超时
- 获取锁成功是否可知。synchronized 无法得知是否获取锁成功;Lock 可以通过 tryLock 获得加锁是否成功
- 功能复杂性。synchronized 加锁可重入、不可中断、非公平;Lock 可重入、可判断、可公平和不公平、细分读写锁提高效率
24. synchronized 和 ReentrantLock 区别是什么?
- synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
- synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
- synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
- synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
- synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
- synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放
补充一个相同点:都可以做到同一线程,同一把锁,可重入代码块。
四、计算机网络
1. get 和 post的区别
- Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。 但是这种做法也不时绝对的,大部分人的做法也是按照上面的说法来的,但是也可以在get请求加上 request body,给 post请求带上 URL 参数。
- Get请求提交的url中的数据最多只能是2048字节,这个限制是浏览器或者服务器给添加的,http协议并没有对url长度进行限制,目的是为了保证服务器和浏览器能够正常运行,防止有人恶意发送请求。Post请求则没有大小限制。
- Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。
- Get执行效率却比Post方法好。Get是form提交的默认方法。
- GET产生一个TCP数据包;POST产生两个TCP数据包。
- 对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(
以上是关于常见面试题的主要内容,如果未能解决你的问题,请参考以下文章