Day4
1. 请你说说泛型、泛型擦除
- Java在1.5版本中引入了泛型,在没有泛型之前,每次从集合中读取对象都必须进行类型转换,而这么做带来结果就是:如果有人不小心插入了类型错误的对象,那么在运行时转换处理阶段就会出错。在提出泛型后,我们可以告诉编译器集合中接受哪些对象类型,编译器会自动地为你的插入进行转化,并在编译时告知是否插入了类型错误的对象,这将使程序变得更加安全更加清楚
- Java泛型是伪泛型,因为Java代码在编译阶段,所有泛型信息会被擦除,Java的泛型基本上都是在编辑器这个层次上实现的,在生成的字节码文件中是不包含泛型信息的。这是为了保持与Java旧版本的兼容性,因为泛型是在Java5引入的,而在此之前的版本是没有泛型支持的。总之,在使用泛型的时候加上类型,在编译阶段会被擦除掉,这个过程被称为泛型擦除
- 泛型擦除对代码的一些方面有一定的影响。例如,无法在运行时检查泛型类型,因为类型信息已经被擦除。这就是为什么无法创建泛型数组或使用“instanceof”运算符检查泛型类型的原因。
- 为了在擦除后仍然提供一定的类型安全性,Java引入了通配符和泛型边界的概念。通配符允许你在不知道具体类型的情况下使用泛型,而泛型边界则限制了可以作为类型参数的具体类型,如:
public static <T extends Comparable<T>> T max(List<T> list) {
// ...
}
2. 谈谈你对反射的了解
- Java反射机制是在程序运行期间,对任意一个类,能够获取其所有的属性和方法,对任意一个对象,能够调用其任意一个方法和属性,这种动态获取类信息和调用对象方法的功能称为反射。
- 在程序运行过程中,通过反射可以获取任意一个类的Class对象,创建任意一个类的实例,并访问该实例的成员,生成任意一个类的动态代理类或动态代理对象
- 在实例的开发案例中,反射机制的应用有,jdbc创建数据库连接时,通过反射机制加载数据库驱动;Spring的xml配置模式,AOP的动态代理
- 使用发射破坏了Java内部细节不对外部公开的封装特性,所以滥用可能会导致一系列安全问题。同时使用反射会降低性能,因为Java不会对反射代码进行优化
3. 请你说说多线程
- 线程是操作系统调度的最小单元,他可以让一个进程并发地处理多个任务,也叫轻量级进程。所以,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈、局部变量,并且能够共享进程内的资源。由于共享资源,处理器便可以在线程之间快速切换,从而让使用者感觉这些线程在同时执行。
- 总的来说,操作系统可以同时执行多个任务,每一个任务就是一个进程。进程可以同时执行多个任务,没有任务就是一个线程。一个程序运行之后至少有一个进程,而一个进程可以包含多个线程,但至少要包含一个线程。使用多线程会给开发人员带来显著的好处
- 更多的CPU核心:现代计算机处理器性能的提升方式已经从追求更高的主频向追求更多的核心发展,所以处理器的核心数量会越来越多,充分地利用处理器的核心则会显著地提高程序性能。而程序使用多线程技术,就可以将计算逻辑分配到多个处理器核心上,显著减少程序的处理时间,从而随着更多处理器核心的加入而变得更有效率
- 更快的响应时间:我们经常要针对复杂的业务编写出复杂的代码,如果使用多线程技术,就可以将数据一致性不强的操作派发给其他线程处理,如上传图片、发送邮件、生成订单等。这样响应用户请求的线程就能够尽快地完成处理,大大缩短了响应时间,从而提升了用户体验
- 更好的编程模型:Java为多线程提供了良好且一致的编程模型,使开发人员能够更加专注于问题的解决,开发者只需为此问题建立合适的业务模型,而无需绞尽脑汁地考虑如何实现多线程。一旦开发人员建立好了业务模型,稍作修改就可以将其方便地映射到Java提供的多线程模型上
4. 说说线程的创建方式
- 创建线程一般有三种方式,分别是继承Thread类、实现Runnable接口、实现Callable接口。
- 通过继承Thread类来创建线程的步骤如下:定义Thread类的子类,并重写该类的run()方法,该方法将作为线程执行体;创建Thread子类的实例,即创建了线程对象;调用线程对象的start()方法来启动该线程
- 通过事先Runnable接口来创建线程的步骤如下:定义Runnable接口的实现类,并实现该接口的run()方法,该方法将作为线程执行体;创建Runnable实现类的实例,并将其作为参数来创建Thread对象,Thread对象为线程对象;调用线程对象的start()方法来启动该线程
- 通过事先Callable接口来创建线程的步骤如下:定义Callable接口的实现类,并实现call()方法,该方法将作为线程执行体;创建Callable实现类的实例,并以该实例作为参数,创建FutureTask对象;使用FutureTask对象作为参数,创建Thread对象,然后启动线程;
- 采用接口的方式创建线程,优点是线程类还可以继承于其他类,并且多个线程可以共享一个线程体,适合多个线程处理同一份资源的情况;缺点是编程稍微麻烦一点点
- 采用继承的方式创建线程,优点是编程稍微简单一点点。缺点是因为线程类已经继承了Thread类,所以就不能继承其他父类了。所以,通常情况下,更推荐采用接口的方式来创建线程,如果需要放回值,就使用Callable接口,否则使用Runnable接口即可
5. 说说线程的状态
- Java线程在运行的生命周期中,在任意给定的时刻,只能处于下列6种状态之一:New:初始状态,线程被创建,但是还没有调用start方法;Runnable:可运行状态,线程正在JVM中执行,也有可能在等待操作系统的调度;Blocked:阻塞状态,线程正在等待获取监视器锁;Wait:等待状态,线程正在等待其他线程的通知或者中断;Time_waiting:超时等待状态,在wait的基础上增加了超时时间,即超出时间自动返回;Terminated:终止状态,线程已经执行完毕
- 线程在创建之后默认为初始状态,在调用start方法之后进入可运行状态,可运行状态不代表线程正在运行,它有可能正在等待操作系统的调度
- 进入等待状态的线程需要其他线程的通知才能返回到可运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,除了其他线程的唤醒,在超时时间到达时也会返回运行状态;此外,线程在执行同步方法时,在没有获取到锁的情况下,会进入到阻塞状态;线程在执行完run方法之后,会进入到终止状态
- Java将操作系统中的就绪和运行两个状态合并为可运行状态(Runnable)
- 线程阻塞于synchronized的监视器锁时会进入阻塞状态,而线程阻塞于Lock锁时进入的确实等待状态,这是因为Lock接口实现类对于阻塞的实现均使用了LockSupport类中的相关方法