Java面试题(常见)
1.双亲委派机制(模型)
在JDK中,有三个类加载器,分别是:BookStrapClassLoader、ExtClassLoader、AppClassLoader
其中AppClassLoader(加载的是classpath,以及我们引入的架包)
双亲委派其实就是:向上委派(就是查找缓存)、向下查找
向上委派就是AppClassLoader在加载一个类的时候,并不是直接加载的,而是先查看自己缓存中是否有该类,没有继续向上查找ExtClassLoader的缓存中是否有,没有就继续往上BookStrapClassLoader缓存中查找,如果BookStrapClassLoader缓存中也没有,则就会去它的加载路径中查找。此时就变成了向下查找。
向下查找
BookStrapClassLoader首先在自己的加载路径下查找有没有这个类,如果有这个类,则加载,返回。如果没有,向下查找到ExtClassLoader中的加载路径下查看,如果ExtClassLoader中也没有继续向下查找。最后到了AppClassLoader下加载。
向下查找就是查找路径,有则加载返回,没有则继续向下查找。
双亲委派的好处:
1、主要了为了安全性,避免用户自己编写的类动态替换java的一些核心类
2、同时也避免了类的重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类。
如何破坏双亲委派模型
- 继承ClassLoader抽象类,重写里面的loadClass发方法,在这个方法里面可以自定义要加载的类使用的类加载器
- 使用线程的上下文加载器,可以通过Thread类的setContextClassLoader()这个方法,去设置当前类使用的类加载器的一个类型
2.JDK、JRE、JVM三者的区别与联系
JDK:Java开发工具,提供给Java开发人员使用
JRE:Java运行环境,运行Java程序
JVM:Java虚拟机,编译.class文件
JDK包含JRE,JRE包含JVM
3.==和equais
==:比较的是栈中的值,基本数据类型比较的是值,引用数据类型比较的是内存的对象地址
equais:object中默认也是采用==比较,但是一般会重写。
4.简述final的作用
表示最终的
- 修饰类:表示类不能被继承
- 修饰方法:表示方法不能被重写,但是可以重载
- 修饰变量:表示变量一旦赋值不能够被更改
-
- 如果声明的是类变量那么必须在声明的时候必须赋值
public class he{
final int b = 0 //必须在初始化的时候赋值
}
5.为什么局部内部类和匿名内部类只能访问局部final变量
6.String、 StringBuffer 、StringBuilder的区别和使用场景
String是final修饰的,不可变的,每次操作都会产生新的String对象
StringBuffer 、StringBuilder是可变的,每次操作不会产生新的对象
StringBuffer有synchronized修饰,所以是线程安全的、StringBuilder是线程不安全的
性能:StringBuilder>StringBuffer>String
使用场景:经常需要改变字符串内容时使用StringBuffer 、StringBuilder
优先使用StringBuilder,多线程使用共享变量时使用StringBuffer
7.接口和抽象类的区别
- 接口是完全抽象的 * 抽象类是半抽象的
- 接口只能定义抽象方法不能实现方法, * 抽象类既可以定义抽象方法,也可以实现方法。
- 接口中的成员变量只能是public static final(常量)* 抽象类中的成员变量是各种类型的
- 接口可以实现多个 * 抽象类只能继承一个
- 接口中的所有方法都是public abstract 公开抽象的(public可不写)。而且不能有构造方法。
- 抽象类就比较自由了,和普通的类差不多,可以有抽象方法也可以没有,可以有正常的方法,也可以没有
8.重载和重写的区别
重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时
public int add(int a, String b);
public String add(int a, String b);
//这个编译报错,重载和返回值类型无关
重写:发生在具体继承关系的两个类中,方法名相同、参数列表相同,
子类返回值范围小于等于父类,
子类抛出的异常小于等于父类,
子类访问修饰符范围大于等于父类,
如果父类方法修饰为private ,则子类不能重写该方法。
构造方法不能重写 ,因为构造方法的方法名和类名相同
9.List和Set的区别
- List:有序,按对象进入的顺序保存对象,可重复,允许多个null元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标元素
- Set:无序,不可重复,最多允许一个null元素存在,取元素时,只能使用Iterator来取得所有元素,在逐一遍历各个元素
10.hashCode与 euqals
hashCode()是定义在JDK 的Object类中,hashCode()的作用就是获取哈希码,返回一个int整数,这个哈希码的作用就是确定该对象在哈希表中的索引位置,从而找到该对象在堆中存储的位置。Java中任何类型的对象都包含hashCode()函数。
- 如果两个对象相等的话,则hashCode一定相同
- 两个对象相等,对两个对象分别调用equals方法都返回true
- 两个对象有相同的hashCode值,它们也不一定相等
- 因此equals被重写,则hashCode方法也必须重写
- hashCode()默认行为是对堆上的对象产生独特值,如果没有重写hsahCode(),则该class的两个对象无论如何都不会相等(即使两个对象有相同的数据)
11 为什么重写euqals方法还有重写hashCode方法
hashCode()默认行为是对堆上的对象产生独特值,如果没有重写hashCode方法,只重写了equals方法,即使返回结果是true,但是这俩个对象的内存地址还是不相等。
12 ArrayList和LinkedList的区别
- ArrayList底层是动态数组,内存空间是连续的,适合查找,不适合随机增删,是非线程安全的
这个扩容机制:集合的初始化容量是10(底层先创建一个长度为0的数组,当添加第一个元素的时候,初始容量为10),当数组满了以后,会创建一个新的数组,完后将旧的数组拷贝到新的数组中。l ArryList集合创建的时候也可以自己指定初始化容量
扩容原理:从源码来看:这个就是原始数组是10,完后扩容时,是原始的数组右移1,则扩容的长度是:10除以2的1次方等于5,扩容了5个长度,则新数组是15.所以说是新数组的1.5倍
- LinkedList是基于双向链表的数据结构 ,其没有初始化容量,适合做数据的随机增删和插入的操作,不适合查询:需要逐一遍历,遍历其,必须使用Iterator
13 HashMap和HashTable的区别?底层的实现原理是什么?
- HashMap初始容量为16,默认加载因子是0.75(当底层数组的容量达到75%,则开始扩容)扩容之后的容量是原容量的2倍,非线程安全。 HashMap集合允许key为null。但是null值只能有一个。多余就覆盖了。
HashMap底层是哈希表(数组+链表)的数据结构,JDK8以后如果哈希表单向链表中元素超过8个,数组长度超过64的时候,单向链表这种数据结构会变成红黑树这种数据结构,当红黑树上的节点数量小于6时,旧会重新变成单链表数据结构,
如何让插入一个元素:首先,会将这个对象封装在node节点中,底层会调用hashCode来得到key的哈希值,通过哈希算法将哈希值转换成为数组下标,下标位置上如果没有任何元素,就把node添加在这个位置上,如果说下标位置上有链表,那么此时会再调用equals方法,如果返回的是false,那么会将这个元素添加到末尾,如果有一个节点返回true,那么这个节点将会被覆盖。
- HashTable初始容量为11, 默认的加载因子是0.75,扩容是 : 原容量 乘以 2+1,HashTable的key和value都不能为空,
HashTable底层是也是哈希表的数据结构。
14 HashMap有哪些线程安全的方式?
- 通过Connections.synchronizedMap()方法返回一个新的Map,这个新的Map就是线程安全的,使用了经典的synchronized来进行互斥 ,同时使用了代理模式new了一个新的类,这个类同样页实现了Map接口
- 可以使用线程安全的ConcurrentHashMap 采用分段锁的方式来实现线程安全,将Map分成多个小的部分,并为每个部分分配一个锁。并且提高了它的并发性。
- 使用synchronized给Map加锁
15 ConcurrentHashMap原理,JDK7和JDK8的区别
ConcurrentHashMap是线程安全的,是因为Segment分段锁的分段锁
JDK7中:
数据结构是:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表数据结构。
元素查询:二次hash,第一次定位到Segment,第二次hash定位待元素所在的链表的头部
锁:Segment分段锁 Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影响,并发度为Segment的个数,可以通过构造函数来指定,数组扩容不会影响其他的Segment
get方法无需枷锁,volatile保证。
JDK8中:
数据结构:synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性
查找、替换、赋值操作都是用CAS
锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读和写,并发扩容。
读操作无锁:
node的val和next使用volatile修饰,读写线程堆该变量互相可见。
读数组用volatile修饰,保证扩容时被读写线程感知。
16 HashMap在扩容上有哪些优化?
17 如何实现一个IOC容器?
1、配置一个配置文件,在配置文件中设置包扫描路径,定义一些注解
2、从配置文件中获取需要扫描包的路径,通过递归来获取这个包下的所有.class文件,放在一个Set集合中
3、遍历这个Set集合,通过反射,获取在类上有指定注解的类,交个IOC容器来处理,定义个Map用来存储这些对象。
4、遍历这个IOC容器,获取到每一个类的实例,完后进行依赖注入
18 什么是字节码?采用字节码的好处是什么?
Java中的编译器和解释器
Java中有一个虚拟机的概念,操作系统和编译器之间加入了一层虚拟的机器(JVM),这个JVM给编译程序提供一个共同的接口(所以才有一次编译到处运行的特性)
什么是字节码
供虚拟机(JVM)理解的代码就叫做字节码(.class文件),它只面向虚拟机(JVM)
采用字节码的好处
java语言通过字节码的方式,在一定程度上,解决了传统的效率低的问题(编译期和解释期拆开了,所以效率比较高),跨平台的特性
19 Java类加载器有哪些?
在JDK中,有三个类加载器,分别是:BookStrapClassLoader、ExtClassLoader、AppClassLoader
其中AppClassLoader(加载的是classpath,以及我们引入的架包)
BookStrapClassLoader是ExtClassLoader的父类加载器(不是继承关系),默认加载lib下的jar包和class类。
ExtClassLoader是AppClassLoader的父类加载器JAVA_HOME/lib/text文件夹下的jar包和class类。
AppClassLoader是自定义类的父类加载器,负责加载claspath下的类文件。不仅仅系统类加载器,还是线程上下文加载器
如何实现自定义类加载器?
继承classLoader实现自定义类加载器
20 Java中的异常体系
如何自定义异常类
实现Exception 接口,并实现里面的方法
21 GC如何判断哪些对象可以被回收
- 引用计数法 :每一个对象都有一个引用计数属性,新增一个引用时计数加1,引用为0时计数减一,计数为0时可以回收 (java中不采用)
缺点:就是当出现循环依赖时,那么这俩个对象是无法被回收的
- 可达性分析法:从GCRoots下开始向下搜索,搜索所走过的路径,成为引用链,当一个对象到GCRoots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机判断是可回收对象。(Java采用的)
Java中 GCRoots的对象有:
- 虚拟机的栈中引用的对象(比如:new User() 则User就是一个GCRoots)
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的而对象
22 线程的生命周期
23 sleep() 、wait() 、join()、 yield()的区别
- 锁池
所有需要竞争同步锁的线程都会挡在锁池当中,例如:当前对象的锁,已经被其中一个线程占用,则其他线程需要在这个锁池中进行等待,当前面的线程释放锁这个对象的锁后,锁池中的线程再去竞争同步锁,当某个线程得到当前对象锁以后,就会进入就绪状态分配CPU的时间片
2 . 等待池
当我们调用wait()方法后,当前线程就会放到等待池当中,等待池的线程是不会去竞争同步锁的,只有调用了notify()或notifyAll()方法后,等待池中的线程才会去竞争锁,notify()方法是随机从等待池中选出一个放到锁池中,而notifyAll()方法是将等待池中的所有线程放到锁池中
区别
- sleep是Tread类的中的方法 , wait是Objetc类中的方法
- sleep方法不会释放lock(锁 ) , 但是wait会释放(锁),而且会加入到等待队列中
- sleep():可以在任何地方使用;wait():必须在同步代码块中使用;
- sleep不需要被唤醒 ,但是wait是需要被唤醒的
- sleep一般用于当前线程的休眠 ,而wait是用于多线程之间的通信
- sleep():需要捕获异常; wait():不需要捕获异常;
yield() 让位方法,暂停当前正在执行的线程,让给其他线程执行。 执行后线程直接进入就绪状态,马上释放 cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到继续执行权的
join() 执行后,线程进入阻塞状态 。调用该方法的线程合并到当前线程中,在当前线程执行后,再执行当前线程
例如:在线程B中执行线程A的join()方法,那么线程B就会进入阻塞状态。
24 线程的相关属性
- Thread.currentThread();,就是获取当前线程,完后再调用其getName()方法就能获得当前线程的名字。
【例子】:Thread thread = Thread.currentThread();
System.out.println(thread.getName());
- 如何修改当前线程对象的名字
修改/设置当前线程的名字使用 线程对象.setName();方法,完后把线程名字传进去就可以了
- 什么情况下会发生多线程安全问题呢?
条件一:多线程并发
条件二:有共享数据
条件三:共享数据有修改行为
25 谈谈你对线程安全的理解
线程安全其实不是线程的安全,而是指的是内存安全,堆内存是共享内存,可以被所有线程访问
在没有限制的情况下,存在被意外修改的风险
26 如何解决线程安全呢
如果是单线程模式的话,就不存在线程安全的问题
1、私有化 可以把一些 数据放到栈内存当中 如:局部变量
2、不共享 通过ThreadLocal类,可以把数据复制n份,每个线程各自都占有一份,互不影响
3、只读,不能修改 使用final修饰变量,让其不能被修改
4、给数据上锁 当一个线程访问当前数据时,给当前数据上锁,只有访问完毕后,释放锁,另一个线程才有权访问。
5、失败重试 (CAS) 乐观锁 就是线程1获取数据,线程2也获取到数据,并将数据修改 ,那么此时线程1将数据进行保存,发现数据根之前的不一样,那么此时进行重试(此时将数据重新获取,再进行保存)
27 Thread 和 Runable 以及Callable的区别
- Thread是一个类,并且Thread实现了Runable Runable是一个接口
- Thread是单继承 Runable多实现
- Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
- Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
28 说说你对守护线程的理解
Java中有两种线程:一种是守护线程、另一种是用户线程
守护线程:是为所有非守护线程提供服务的的线程
如何设置一个线程为守护线程:在start() 之前去设置,setDaemon()这个方法就是一个守护线程。括号里面填的就是true,那么这个线程就是守护线程,
守护线程的特点:一般一个守护线程是一个死循环;所有的用户线程结束,守护线程自动结束。
典型的守护线程:就是垃圾回收线程
应用场景:(1) 来为其他线程提供服务的
(2) 任何情况下,这个程序结束,这个线程立即关闭
29 synchronized和lock的区别
- synchronized是一个关键字 ,可以修饰实例方法、静态方法和代码块。 lock是一个接口
- synchronized在发生异常时会自动释放锁(发生异常、执行完毕), lock需要手动释放锁
- synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
- synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平
30 并发、并行、串行的区别
串行:串行在时间上不可能发生重叠,在前一个任务没有搞定,下一个任务只能等待
并行:在时间上是重叠的,两个任务在同一个时刻互不干扰的同时执行
并发:允许两个任务彼此干扰,同一时间点,只有一个任务运行,交替执行
31 并发的三大特性
- 原子性 :原子性是指,当一个线程执行的时候,另一个不会影响到它
如何保证原子性:
-
- synchronized互斥锁
- CAS(是乐观锁的实现)
- Lock锁
2、可见性:一个线程对数据的操作,对另一个线程是可见的
如何保证可见性:
-
- 使用volatile关键字 ,说明不能使用CPU缓存
- synchronized关键字,在加锁的一瞬间同步数据
- final 在运行期间,不能修改数据
3、有序性:程序编写的指令顺序和CPU执行顺序一致
如何保证有序性:
-
- 使用volatile关键字,通过内存屏障实现(在两个操作之间,添加上一道指令)
32 悲观锁和乐观锁
首先,悲观锁和乐观锁是用来解决我们并发所带来的数据安全问题
悲观锁:一个事务执行时如果采用悲观锁, 那么另一个事务只能时等待(类似于线程同步),效率低
乐观锁 :所谓乐观锁 其实并不是上锁,乐观锁是在数据库创建表的时候,给表加了一个字段,这个字段其实就是版本号字段 ,当两个事务同时操作这个记录,当其中一个事务对数据进行了更新操作,则,这个事务就会把这个版本号改变,在此时另一个事务也去更新这个数据,同时也会更新版本号,另一个事务发现版本号被改变,则会执行回滚操作。
33 JDK1.8新特性都有哪些
- JDK8后允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可
- Lambda表达式
- 函数式接口 (包括Cosumer接口、Optional接口 、Steam接口)
- JDK8后支持了多重注解 (就是一个类上可以标注多个注解)
34 单例模式的含义
定义:某个类的实例在多线程的环境下只会被创建一次
大致分为两种:
- 饿汉式:一开始就初始化
- 懒汉式:非线程安全,延迟初始化
如何实现单例模式?
- 通过双重检查锁的方式
- 通过静态内部类的方式实现
- 通过枚举类的方式实现
35 深拷贝和浅拷贝的理解
浅拷贝:只会拷贝基本数据类型的值,浅拷贝是指复制对象时,简单地复制对象的引用;
深拷贝:即会拷贝基本数据类型的值,是指复制对象时,复制对象的所有引用和对象本身。
36 对象克隆的实现
- 实现Cloneable接口,重写clone方法
- 实现serializable接口,通过对象的序列化和反序列化来实现克隆
37 Java中异常处理机制
- 使用 try 、catch、finally捕获异常,finally中的代码是一定会执行的,捕获异常以后程序会继续执行
- 使用throws把异常抛出去,谁调用谁处理这个异常
38 throw 和throws的区别
- throw作用在方法内,表示抛出具体的异常类,由方法体内的语句执行,一定抛出了异常
- throws作用在方法的声明上,表示抛出异常,由调用者来处理,不一定会发生异常
39 什么是序列化? 什么情况下需要序列化
定义:序列化就是一种用来处理对象流的机制,将对象的内容流化,将流化后的对象传输网络之间
如何实现: 序列化是通过实现serializable接口
目的:传递和保存对象、保存对象的完整性和可传递性
40 实例化对象由哪几种方式
- new
- clone()
- 通过反射机制创建
- 序列化反序列化
41 Collection和Collections的区别
- Collection是最基本的集合的接口,下面有两个实现类分别是Set 和List
- Collections是一个包装类,它包含各种有关集合操作的方法,不能实例化,是一个工具类
42 队列和栈的定义?有什么区别
- 队列是先进先出,栈先进后出
- 遍历数据的速度不同
-
- 栈只能从头部取数据,最先放入的最后被取出来,而且在遍历数据时,还得为数据开辟临时空间,保证数据在遍历前后的一致性
- 队列时基于指针进行遍历,而且可以从头部也可以从尾部开始遍历,但是不能同时遍历,遍历过程中,无序开辟临时空间,速度块
43 线程的run()和start()有什么区别?
- start()方法用于启动线程 run()用于执行线程运行时代码
- run()可以重复调用, start()方法只能调用一次
44 什么是死锁?
定义:死锁就是一组互相竞争资源的线程,因互相等待而导致”永久“阻塞的现象
发生死锁的原因:
- 互斥条件:共享资源只能被一个线程占用
- 占有且等待:一个线程已经取得某个资源后,在等待另一个共享资源时,不释放已经占有的资源。
- 不可抢占:其他线程不能强行抢占一个线程已经占有的资源
- 循环等待:一个线程T1等待另一个线程T2占有的资源,另一个线程T2在等待T1占有的资源
如何避免死锁?
- 对于占有且等待这个条件:我们可以一次性获取所有的资源,就可以打破这个条件
- 对于不可抢占这个条件:已经占有资源的一个线程取申请其他的资源,如果申请不到,则可以主动释放已经占有的资源
- 对于循环等待这个条件:可以有序的申请资源
45 Java反射的定义以及优点和缺点
定义:在程序运行的过程中,动态的获取构造对象,并任意获取一个类成员变量等
使用场景:
- 动态代理,
- 在Spring框架中大量使用反射,比如:通过反射来实例化对象Bean等
优点:
- 增加程序的灵活性,在运行的过程中取动态对类进行修改和操作
- 提高代码的复用率 比如:动态代理
- 在运行时,可以轻松的获取一个类的方法和属性,并且还可以动态的调用
缺点:
- 反射会涉及到动态类型的解析,所以JVM无法对这些代码进行优化,性能下降
- 代码的可读性下降
- 反射可以绕过一些限制访问的属性或方法,肯会破坏代码的抽象性和安全性问题
46 策略模式和观察者模式
策略模式和观察者模式都属于行为型模式,
策略模式最要是根据上下文动态控制类的行为的一个场景,
一方面可以解决多个if...else判断带来的代码复杂性和维护性问题,
另一方面,把类的不同行为进行封装,使得程序可以进行动态的扩展和替换,增加了程序的灵活性
观察者模式只要用在一对多的对象依赖关系中,实现某一个对象动态变更之后的感知的场景,
一方面可以降低对象关系的耦合度,弱化依赖关系。
另一方面,通过这种状态机制,可以保证这些依赖对象之间的状态协调,在spring中大量用到观察者模式
47 Interger和int的区别
- Interger的初始化值是 null ,int的初始值是0
- Interger是存储在堆内存中的,int是直接存储在栈内存中的
- Interger是一个对象类型,它封装了很多方法,使我们使用时更加灵活
为什么设计成封装类型
主要因为Java本身一个面向对象对象的语言
48 通过反射机制如何获取类上的注解呢?
- 首先要获取到类
Class<?> aClass = Class.forName("com.powernode.bean.User");
- 判断类上面是否有这个注解
if (aClass.isAnnotationPresent(Component.class)) {
- 获取类上的注解
Component annotation = aClass.getAnnotation(Component.class);
49 谈谈你对AQS的理解
AQS是多线程同步器,它是JUC包中多个组件的底层实现,比如lock,AQS提供了两种锁机制,分别是共享锁和排他锁,所谓排他锁:就是存在多个线程去竞争同一资源的时候,同一个时刻只允许一个线程去访问这样的共享资源;共享锁也称为:“读锁”,就是在同一个时刻允许多个线程同时获得这样一个锁的资源
50 final 、finally 、finalize 区别
- final关键字
-
- 修饰类的时候,表示该类是最终类,不能被继承
- 修饰方法的时候,此方法不能被重写,可以被重载
- 修饰变量的时候,此变量一旦赋值不能被修改
- 修饰对象的时候,该对象的引用不能被修改。
- fianlly 关键字:异常处理机制中的一部分,用于定义在try -catch -fianlly 语句块中
-
- 不论是否发送异常,fianlly语句块中的代码都会执行
- 主要用于释放资源、关闭连接等必须确保执行的操作。
- finalize :是一个对象的方法,定义在Object类中。
-
- 在垃圾回收器将对象回收之前调用
- 可以重写fianlize方法,在其中编写对象在被回收前需要进行的清理操作,如释放资源等。
51 java中的参数传递值的时,啥时候传递值,啥时候传递引用?
- 对于基本数据类型:传递的是其值的拷贝。任何对参数值的修改都不会影响原始变量
- 对于引用类型(如:对象、数组)传递的是引用的值的拷贝,也就是说,方法内部的参数和原始变量将引用同一个对象。虽然我们可以通过方法内部的参数修改对象的状态,但是对于引用本省的修改是不会影响原始变量的。
52 Threadlocal的原理
ThreadLocal:为共享变量在每个线程中创建一个副本,每个线程都可以访问自己内部的副本变量。通过threadlocal保证线程的安全性。
其实在ThreadLocal类中有一个静态内部类ThreadLocalMap(其类似于Map),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本。
ThreadLocal 本身并不存储值,它只是作为一个 key保存到ThreadLocalMap中,
53 集合和数组的区别
区别:数组长度固定 集合长度可变
数组中存储的是同一种数据类型的元素,可以存储基本数据类型,也可以存储引用数据类型;
集合存储的都是对象,而且对象的数据类型可以不一致。在开发当中一般当对象较多的时候,使用集合来存储对象。
54 使用线程池的好处?
1、使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建销毁时造成的消耗
2、由于没有线程创建和销毁时的消耗,可以提高系统响应速度
3、通过线程可以对线程进行合理的管理,根据系统的承受能力调整可运行线程数量的大小等
55 线程池的工作过程
56 线程池的关闭
关闭线程池,可以通过shutdown和shutdownNow两个方法
原理:遍历线程池中的所有线程,然后依次中断
1、shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;
2、shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程
57 JDK1.8 JVM运行时内存
程序计数器: 线程私有的(每个线程都有一个自己的程序计数器), 是一个指针. 代码运行, 执行命令. 而每个命令都是有行号的,会使用程序计数器来记录命令执行到多少行了.记录代码执行的位置
Java虚拟机栈: 线程私有的(每个线程都有一个自己的Java虚拟机栈). 一个方法运行, 就会给这个方法创建一个栈帧, 栈帧入栈执行代码, 执行完毕之后出栈(弹栈)存引用变量,基本数据类型
本地方法栈: 线程私有的(每个线程都有一个自己的本地方法栈), 和Java虚拟机栈类似, Java虚拟机栈加载的是普通方法,本地方法加载的是native修饰的方法. native:在java中有用native修饰的,表示这个方法不是java原生的.
堆: 线程共享的(所有的线程共享一份). 存放对象的,new的对象都存储在这个区域.还有就是常量池.
元空间: 存储.class 信息, 类的信息,方法的定义,静态变量等.而常量池放到堆里存储
JDK1.8和JDK1.7的jvm内存最大的区别是, 在1.8中方法区是由元空间(元数据区)来实现的, 常量池. 1.8不存在方法区,将方法区的实现给去掉了.而是在本地内存中,新加入元数据区(元空间).
58 JDK1.8堆内存结构
Young 年轻区(代): Eden+S0+S1, S0和S1大小相等, 新创建的对象都在年轻代
Tenured 年老区: 经过年轻代多次垃圾回收存活下来的对象存在年老代中.
Jdk1.7和Jdk1.8的区别在于, 1.8将永久代中的对象放到了元数据区, 不存永久代这一区域了.
59 JVM调优参数
60 Get和Post的区别?
1. Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。
2. Get传送的数据量较小,Post传送的数据量较大,一般被默认为不受限制。
3. Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。
4. Get执行效率却比Post方法好。Get是form提交的默认方法。
61 cookie和session的区别?
1.存储位置不同
cookie存放在客户端电脑,是一个磁盘文件。Ie浏览器是可以从文件夹中找到。
session是存放在服务器内存中的一个对象。 chrome浏览器进行安全处理,只能通过浏览器找到。Session是服务器端会话管理技术,并且session就是cookie实现的。
2.存储方式不同
cookie中只能保管ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据。
session中能够存储任何类型的数据,包括且不限于string,integer,list,map等。
3 .隐私策略不同
cookie对客户端是可见的
session存储在服务器上,不存在敏感信息泄漏的风险。
4. 有效期上不同
开发可以通过设置cookie的属性,达到使cookie长期有效的效果。
session依赖于名为JSESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需关闭窗口该session就会失效,因而session不能达到长期有效的效果。
62 什么是匿名类?
匿名类是没有名字的内部类,它通常用于直接创建对象并重写父类或接口的方法。
63 Java中如何防止继承?
通过将类声明为final,可以防止其他类继承该类。
64 super关键字和this关键字有何区别?
super关键字用于访问父类的成员(属性和方法),this关键字用于访问当前对象的成员。
65 如何访问数组中的元素?
可以使用索引来访问数组中的元素,索引从0开始,例如:int element = array[0];
数组只能存储相同类型的元素。
66 如何查找数组中的最大值和最小值?
可以使用循环遍历数组,记录最大值和最小值
67 如何对数组进行排序?
可以使用Arrays类的sort()方法对数组进行排序,例如:Arrays.sort(array);
68 如何判断两个数组是否相等?
可以使用Arrays类的equals()方法来判断两个数组是否相等,
例如:boolean isEqual = Arrays.equals(array1, array2);
69 如何复制数组?
可以使用Arrays类的copyOf()方法或System类的arraycopy()方法来复制数组,
70 数组和集合有何区别?
数组是一种固定大小的数据结构,而集合是动态大小的数据结构。数组可以存储基本数据类型和对象,而集合只能存储对象
71 数组和链表有何区别?
数组是连续的内存空间,访问元素的速度快,但插入和删除元素的效率较低。
链表是非连续的内存空间,插入和删除元素的效率较高,但访问元素的速度较慢。
72 如何在数组中添加和删除元素?
数组的大小不可变,无法直接添加和删除元素。但可以通过创建一个新的数组,将原数组中的元素复制到新数组中,来实现添加和删除操作。
73 描述一下try-catch-finally语句块的执行流程。
当try块中的代码出现异常时,会立即跳转到对应的catch块进行处理。如果发现catch块中可以处理该异常,会执行相应的代码,然后继续执行finally块中的代码;如果没有匹配的catch块,当前方法会立即结束,异常会被抛给上一级调用者或者由虚拟机处理。不论是否发生异常,finally块中的代码总会被执行。
74 什么是异常链?
异常链是指在异常处理过程中,可以通过在catch块中传递异常对象来保留先前抛出的异常信息。这样可以将多个异常相关联,方便后续的异常分析和处理。
75 什么是断言(assertion)和断言异常?
断言用于在程序中进行验证和调试,通常用于在开发过程中检查程序的正确性。断言异常是一种特殊的异常,如java.lang.AssertionError,用于表示断言失败。
76 什么是同步(Synchronized)和异步(Asynchronous)?
同步是指多个线程按照一定的顺序执行;异步是指多个线程可以同时执行,无需等待。
77 如何将一个集合转换为数组?
可以使用集合的toArray()方法将集合转换为数组。
78 什么是fail-fast机制?
在使用迭代器遍历集合的过程中,如果集合的结构发生了改变,会抛出ConcurrentModificationException异常,保证遍历的安全性。
79 Java集合中的HashMap如何处理哈希冲突?
HashMap使用链地址法解决哈希冲突,即在哈希桶中使用链表或红黑树来存储冲突的元素。
80 什么是弱引用(WeakReference)和软引用(SoftReference)?
弱引用和软引用都可以在内存不足时被垃圾回收器回收,但软引用的回收策略相对宽松一些。
81 字节流和字符流的区别是什么?
字节流以字节为单位进行读写操作,而字符流以字符为单位进行读写操作,并提供了编码解码的功能。
82 字节流和字符流之间的转换如何进行?
可以使用字节流和字符流之间的桥接类,如InputStreamReader和OutputStreamWriter。
83 什么是缓冲流?有什么作用?
缓冲流是一种高效的IO流,通过缓冲区来提高读写操作的性能。
84 什么是线程池的拒绝策略?
线程池的拒绝策略指当线程池中的任务队列已满并且线程数达到最大线程数时,线程池如何处理无法继续执行的任务。
85 Java中有几种锁的类型?请列举并简要解释。
Java中有两种锁的类型,分别为对象锁(也称为内部锁或监视器锁)和类锁(也称为静态锁)。对象锁是在对象上的锁,使只有一个线程可以访问对象的同步代码块。类锁是在类上的锁,使只有一个线程可以访问类的同步代码块。
86 请解释一下volatile关键字的作用。
volatile关键字用于保证被修饰的变量在多线程环境下的可见性和禁止指令重排序。它是一种轻量级的同步机制,适用于变量的读多写少的场景。
87 反射中如何获取Class对象?
可以使用三种方式获取Class对象:通过对象的getClass()方法、通过类名.class语法、通过Class.forName()方法。
88 Java中万能的Object类是如何实现反射的?
Object类中的getClass()方法以及Class类中的其他方法,通过JNI调用底层的C++代码来实现反射。
89 Java中的Garbage Collection的作用?
当对象的所有引用都消失后,对象使用的内存将自动回收
90当用户登录成功以后,我们要把用户的某个字段放入session中
request.getSession.setAttribuye("hzk" ,hzk.getId())
当我们放入成功 以后,要获取的代码是
request.getSession.getAttribute("hzk")
Thread.sleep() 和 Object.wait(),都可以抛出 InterruptedException。这个异常是不能忽略的,因为它是一个检查异常(checked exception)
91 对于基本数据类型和引用基本类的比较
public class Demo {
public static void main(String[] args) {
//Interger也是基本数据类型的值,也会比较的是基本数据类型的值,但是当超过-128 --127 时候,
// 就会创建一个新的对象,就会比较的是引用数据类型
Integer i1 = 128;
Integer i2 = 128;
System.out.println((i1==i2) +","); //flase -128 - 127
// 在这个区域以内的就会直接使用内存中的值,当超过以后,就会创建一个新的对象,就会返回flase
//i3指向的是字符串常量池的100
String i3 ="100";
//i4 执行堆内存中的
String i4 = "1"+new String("00");
System.out.println((i3 == i4) +",");
Integer i5 =100;
Integer i6 = 100;
System.out.println((i5 == i6)); //true
String i7 ="aaa"; //首先会去查看字符串常量池中是否会有aaa如果有的话,就直接赋值给i7
//如果没有就会创建aaa并赋值i7,,完后将aaa这个对象的引用地址返回给字符串常量池,
// 这样i7就指向的是字符串常量池的aaa,
String i8 = "aaa";
//当i8去创建对象aaa时候,字符串常量池已经存在aaa,
// 直接将对象aaa的引用地址赋值给i8,所以i8也是指向的字符串常量池中的i8
System.out.println(i7 == i8);
}
}
92 线程池的七大参数
1、最大线程数
2、核心线程数
3、空闲线程的存活时间
4、存活时间的单位
5、创建线程的工厂
6、阻塞队列
7、拒绝策略