Java基础
1 JDK和JRE有什么区别
- JDK: Java Develogpment Kit的简称,Java开发工具包,提供了Java的开发环境和运行环境。
- JRE: Java Runtime Environment的简称,Java运行环境,为Java的运行提供了所需环境。
具体来说JDK其实包含了JRE,同时还包含了编译Java源码的编译器javac,还包含了很多Java程序调试和分析的工具。简单来说:如果你需要运行Java程序,只需要安装JRE就可以了,如果你需要编写Java程序,需要安装JDK。
2 ==和equals的区别是什么
"== "对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
两个对象的hashCode()相同,则equals()也一定为true,对吗?
不对,两个hashCode()相同,equals()也不一定为true。
3 final在Java中有什么作用
- final修饰的类叫最终类,该类不能被继承。
- final修饰的方法不能被重写。
final修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。
4 Java中的Math.round(-1.5)等于多少
等于-1。因为在数周上取值时,中间值(0.5)向右取整,所以正0.5是往上取整,负0.5是直接舍弃。
Math.round()函数的作用是可把一个数字舍入为最接近的整数。
5 String属于基础的数据类型吗
String不属于基础类型,基础类型有8种:byte, boolean, char, short, int, float, long, double 而String属于对象。
6 Java中操作字符串都有哪些类,他们之间有什么区别
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
7 String str="i"与 String str=new String(“i”)一样吗
不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
8 String类的常用方法有哪些
-
indexof()
-
charAt()
-
replace()
-
trim()
-
split()
-
getBytes():返回字符串的byte类型数组
-
length()
-
toLowerCase():将字符串转成小写字母
-
toUpperCase():将字符串转成大写字母
-
substring()
-
equals()
9 普通类和抽象类有哪些区别
-
普通类不能包含抽象方法,抽象类可以包含抽象方法。
-
抽象类不能直接实例化,普通类可以直接实例化。
10 抽象类能使用final修饰吗
不能,定义抽象类就是为了让其他类继承的,如果定义为final该类就不能被继承,这样彼此就会产生矛盾,所以final不能修饰抽象类,如下图所示,编辑器也会提示错误信息。
11 接口和抽象类有什么区别
- 实现:抽象类的子类使用extends来继承;接口必须使用implements来实现接口。
- 构造函数:抽象类可以有构造函数;接口不能有。
- main方法:抽象类可以有很多main方法,并且我们能运行它;接口不能有main方法。
- 实现数量:类可以实现很多接口;但是只能继承一个抽象类。
- 访问符修饰:接口中的方法默认使用public修饰;抽象类中的方法可以是任意访问修饰符。
12 Java中IO流分为几种
按功能来分:输入流(input)、输出流(output)
按类型来分:字节流和字符流
字节流和字符流的区别是:字节流按8位传输以字节为单位输入输出数据,字符流按16位传输以字符为单位输入输出数据。
13 BIO、BIO、AIO有什么区别?
-
BIO:Block IO同步阻塞时IO,就是我们平常用的传统IO,它的特点是模式简单实用方便,并发处理能力低。
-
NIO:New IO 同步非阻塞IO,是传统IO的升级,客户端和服务器端通过Channel(通道)通讯,实现了多路复用。
-
AIO:Asynchronous IO 是NIO的升级,也叫NIO2,实现了异步非阻塞IO,异步IO的操作基于实践和回调机制。
14 Files的常用方法有那些
- File.exists():检测文件路径是否存在
- File.createFile():创建文件
- Files.createDirectory():创建文件夹
- Files.delete():删除一个文件或目录
- Files.copy():复制文件
- Files.move():移动文件
- Files.size():查看文件个数
- Files.read():读取文件
- Files.write():写入文件
15 Collection和Collections有什么区别
- java.util.Collection是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List和Set。
- Collections则是集合类的一个工具类/帮助类,体重提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
16 HashMap和Hashtable有什么区别
- hashMap去掉了HashTable的contains方法,但是加上了containsValue()和containsKey()方法。
- hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。
- hashMap允许空键值,而hashTable不允许。
17 如何决定实用HashMap还是TreeMap
对于在Map中插入,删除和定位元素这类操作,HashMap是最好的选择。然而,加入你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
18 HashMap的实现原理
HashMap概述:HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap的数据结构:在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
但我们往HashMap中put元素时,首先根据key的hashcode重新计算hash值,根据hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾,如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。
需要主义的是jdk 1.8中对HashMap的实现做了优化,当链表中的节点数超过了八个之后,该链表就会转为红黑树来提高查询效率,从原来的O(n)到O(logn)。
19 HsahSet的实现原理
-
HashSet底层由HashMap实现
-
HashSet的值存放于HashMap的key上
-
HashMap的value统一为PRESENT
20 ArrayList和LinkedList的区别
最明显的区别是ArrayList底层的数据结构是数组,支持随机访问,而LinkedList得底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList得实践复杂度是O(1),而LinkedLis是O(n)。
21 如何实现数组和List之间得转换
-
List转换为数组:调用ArrayList的toArray方法。
-
数组转换成List:调用Arrays的asList方法。
22 Array和ArrayList有什么区别
-
Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
-
Array是指定大小的,而ArrayList大小是不固定的。
-
Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。
23 在Queue中poll()和remove()有什么区别
poll()和remove()都是从队列中去除一个元素,但是poll()在获取元素失败的时候会返回空,但是remove()失败的时候会抛出异常。
24 哪些集合类是线程安全的
-
vector:就比arraylist多了个同步机制,因为效率比较低,现在不建议使用,在Web应用中,特别是前台页面,往往效率是优先考虑的。
-
statck:堆栈类,先进后出。
-
hashtable:就比hashmap多了个线程安全。
-
enumeration:枚举,相当于迭代器。
25 迭代器Iterator是什么
迭代器是一种设计模式,他是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
26 并行和并发有什么区别
-
并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
-
并行是在不同实体上多个事件,并发是在同一实体上的多个事件。
-
在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群。
27 进程和线程的区别
简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。
28 守护线程是什么
守护线程(Daemon thread) 是个服务线程,准确来说就是服务其他的线程。
29 创建线程有哪几种方式
-
继承Thread类创建线程类
- 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
- 创建Thread子类的实例,即创建了线程对象。
- 调用线程对象的start()方法来启动该线程。
30 通过Runnable接口创建线程
- 定义Runnable接口的实现类,并重写该接口的run方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象次啊是真正的线程对象。
- 调用线程对象的start()方法来启动线程。
31 通过Callable和Future创建线程
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
32 Runnable和Callable的区别
- Runnable接口中的run()方法的返回值是void,它做的事情知识纯粹地去执行run()方法中地代码而已。
- Callable接口中的call()方法是有返回值地,是一个泛型,和Future、FutureTask配合可以用来获得异步执行地结果。
33 线程有哪些状态
线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
- 创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
- 就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
- 运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
- 阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
- 死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪 。
34 sleep()和wait()有什么区别
sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。
35 notify()和notifyAll()有什么区别
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
36 线程的run()和start()有什么区别
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
37 创建线程池有哪些方式
-
newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
-
newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
-
newSingleThreadExecutor()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
-
newScheduledThreadPoll(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
38 线程池都有哪些状态
Running、ShutDown、Stop、Tidying、Terminated
线程池各个状态切换框架图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZLilrEL-1591666225824)(D:\面试准备\面试题(待完善)]\images\06.png)
39 线程池中submit()和execute()方法有什么区别
-
接收的参数不一样:
execute()参数 Runnable;submit()参数(Runnable)或(Runnable和结果 T)或(Callable)。
-
submit有返回值,而execute()没有返回值。
-
submit方便Exception处理,submit的返回值Future调用get方法时,可以捕获处理异常。
40 Java程序中怎么保证多线程程序的运行安全
- 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作。
- 可见性:一个线程对主内存的修改可以及时地被其他线程看到。
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无章。
41 多线程锁升级原理是什么
在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
锁升级的图示过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PUDdhHWN-1591666225831)(D:\面试准备\面试题(待完善)]\images\04.jpg)
42 锁升级
偏向锁
所谓的偏向锁是指在对象实例的Mark Word(说白了就是对象内存中的开头几个字节保留的信息,如果把一个对象序列化后明显可以看见开头的这些信息),为了在线程竞争不激烈的情况下,减少加锁及解锁的性能损耗(轻量级锁涉及多次CAS操作)在Mark Word中有保存这上次使用这个对象锁的线程ID信息,如果这个线程再次请求这个对象锁,那么只需要读取该对象上的Mark Word的偏向锁信息(也就是线程id)跟线程本身的id进行对比,如果是同一个id就直接认为该id获得锁成功,而不需要在进行真正的加解锁操作。其实说白了就是你上次来过了,这次又来,并且在这两次之间没有其他人来过,对于这个线程来说,锁对象的资源随便用都是安全的。这次用缓存来换取性能的做法,不过偏向锁在锁竞争不激烈的情景下使用才能获取比较高的效率。当使用CAS竞争偏向锁失败,表示竞争比较激烈,偏向锁升级为轻量级锁。
轻量级锁
所谓轻量级锁是比偏向锁更耗资源的锁,实现机制是,线程在竞争轻量级锁前,在线程的栈内存中分配一段空间作为锁记录空间(就是轻量级锁对应的对象的对象头的字段的拷贝),拷贝好后,线程通过CAS去竞争这个对象锁,试图把对象的对象头子段改成指向所记录空间,如果成功则说明获取轻量级锁成功,如果失败,则进入自旋(说白了就是循环)取试着获取锁。如果自旋到一定次数还是不能获取到锁,则进入重量级锁。
自旋锁
说白了就是获取锁失败后,为了避免直接让线程进入阻塞状态而采取的循环一定次数去试着获取锁的行为。(线程进入阻塞状态和退出阻塞状态是涉及到操作系统管理层面的,需要从用户态进入内核态,非常消耗系统资源),为什么能这样做呢,是因为试验证明,锁的持有时间一般是非常短的,所以一般多次尝试就能竞争到锁。
重量级锁
所谓的重量级锁,其实就是最原始和最开始java实现的阻塞锁。在JVM中又叫对象监视器。这时的锁对象的对象头字段指向的是一个互斥量,所有线程竞争重量级锁,竞争失败的线程进入阻塞状态(操作系统层面),并且在锁对象的一个等待池中等待被唤醒,被唤醒后的线程再次去竞争锁资源。
所谓的锁升级,其实就是从偏向锁à轻量级锁(自旋锁)à重量级锁,之前一直被这几个概念困扰,网上的 文章解释的又不通俗易懂,其实说白了,一切一切的开始源于java对synchronized同步机制的性能优化,最原始的synchronized同步机制是直接跳过前几个步骤,直接进入重量级锁的,而重量级锁因为需要线程进入阻塞状态(从用户态进入内核态)这种操作系统层面的操作非常消耗资源,这样的话,synchronized同步机制就显得很笨重,效率不高。那么为了解决这个问题,java才引入了偏向锁,轻量级锁,自旋锁这几个概念。拿这几个锁有何优化呢?网上也没有通俗易懂的解释,其实说白了就是,偏向锁是为了避免CAS操作,尽量在对比对象头就把加锁问题解决掉,只有冲突的情况下才指向一次CAS操作,而轻量级锁和自旋锁呢,其实两个是一体使用的,为的是尽量避免线程进入内核的阻塞状态,这对性能非常不利,试图用CAS操作和循环把加锁问题解决掉,而重量级锁是最终的无奈解决方案,说白了就是能通过内存读取判断解决加速问题优于〉通过CAS操作和空循环优于〉CPU阻塞,唤醒线程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Ype7MZw-1591666225838)(D:\面试准备\面试题(待完善)]\images\07.png)
锁升级初步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b49G2RUB-1591666225874)(D:\面试准备\面试题(待完善)]\images\08.png)
43 什么是死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操作系统层面的一个错误,是进程死锁的简称,最早在 1965 年由 Dijkstra 在研究银行家算法时提出的,它是计算机操作系统乃至整个并发程序设计领域最难处理的问题之一。
44 怎么防止死锁
死锁发生的必要条件:
- 互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源。
- 请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
- 不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
- 环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,就不会发生死锁。理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和 解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确 定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
45 ThreadLocal是什么,有哪些使用场景
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
46 synchronized底层实现原理
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
- 普通同步方法,锁是当前实例对象
- 静态同步方法,锁是当前类的class对象
- 同步方法块,锁是括号里面的对象
47 synchronized和volatile的区别是什么
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
48 synchronized和Lock有什么区别
- 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
- synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
- synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
- 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
- synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
49 synchronized和ReentrantLock区别是是什么
synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:
- ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
- ReentrantLock可以获取各种锁的信息
- ReentrantLock可以灵活地实现多路通知
另外,二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word。
50 atomic的原理
Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。
Atomic系列的类中的核心方法都会调用unsafe类中的几个本地方法。我们需要先知道一个东西就是Unsafe类,全名为:sun.misc.Unsafe,这个类包含了大量的对C代码的操作,包括很多直接内存分配以及原子操作的调用,而它之所以标记为非安全的,是告诉你这个里面大量的方法调用都会存在安全隐患,需要小心使用,否则会导致严重的后果,例如在通过unsafe分配内存的时候,如果自己指定某些区域可能会导致一些类似C++一样的指针越界到其他进程的问题。
51 什么是反射
反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力
Java反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
52 什么是Java序列化,什么情况下需要序列化
简单说就是为了保存再内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。
什么情况下需要序列化:
a). 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b). 当你想用套接字在网络上传送对象的时候;
c). 当你想通过RMI传输对象的时候;
53 动态代理是什么,有哪些应用
当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。
应用:
- Spring的AOP
- 加事务
- 加权限
- 加日志
54 怎么实现动态代理
首先必须定义一个接口,还要有一个InvocationHandler(将实现接口的类的对象传递给它)处理类。再有一个工具类Proxy(习惯性将其称为代理类,因为调用他的newInstance()可以产生代理对象,其实他只是一个产生代理对象的工具类)。利用到InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回。
55 jsp和servlet有什么区别
- jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)
- jsp更擅长表现于页面显示,servlet更擅长于逻辑控制。
- Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到。
- Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如何镶嵌到一个类中,由Jsp容器完成。而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。
56 jsp有哪些内置对象,作用分别是什么
-
JSP有九个内置对象
-
request:封装客户端的请求,其中包含来自GET或POST请求的参数;
-
response:封装服务器对客户端的响应;
-
pageContext:通过该对象可以获取其他对象;
-
session:封装用户会话的对象;
-
application:封装服务器运行环境的对象;
-
out:输出服务器响应的输出流对象;
-
config:Web应用的配置对象;
-
page:JSP页面本身(相当于Java程序中的this);
-
exception:封装页面抛出异常的对象。
57 jsp的四种作用域
JSP中的四种作用域包括page、request、session和application,具体来说:
- page代表与一个页面相关的对象和属性。
- request代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个Web组件;需要在页面显示的临时数据可以置于此作用域。
- session代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。
- application代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。
58 session和cookie有什么区别
- 由于http协议是无状态协议,所以服务端需要记录用户的状态时,就需要某种机制赖识别具体的用户,这个机制就是session。典型的场景就是比如购物车,当你点击下单按钮时,由于http协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建特定的session,用于标识这个用户,并且跟踪用户,这样才知道购物车里有几本书。这个session是保存在服务端的,有一个唯一标识。在服务端保存session的方法很多,内存、数据库、文件都有。集群的时候也要考虑session的转移,在大型网站,一般会有专门的session服务集群,用来保存用户的会话,这个时候session信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放session。
- 思考一下服务端如何识别特定的客户,这个时候cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的cookie信息到服务端。实际上大多数的应用都是用cookie来实现session跟踪的,第一次创建session的时候,服务端会在HTTP协议中告诉客户端,需要在cookie里面记录一个session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了cookie怎么办,一般这种情况,会使用一种叫做url重写的技术来进行会跟踪,即每次HTTP交互,url后面都会被附加上一个注入sid=xxx这样的参数,服务端据此来识别用户。
- cookie其是还可以用在一些方便用户的场景下,设想你某次登录过一个网站,下次登录的时候不想再次输入账号了怎么办,这个信息可以写到cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户给填了,能够方便一下用户。这也是cookie名称的由来,给用户的一点甜头。所以,总结一下:session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件钟;cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现session的一种方式。
59 session的工作原理
其实session是一个存在服务器上的类似于一个散列表格的文件。里面存有我们需要的信息,在我们需要用的时候可以从里面取出来。类似于一个大号的map吧,里面的键存储的是用户的sessionID,用户向服务器发送请求的时候会带上这个sessionID。这是就可从中取出对应的值了。
60 如果客户端禁止cookie能实现session
Cookie与 Session,一般认为是两个独立的东西,Session采用的是在服务器端保持状态的方案,而Cookie采用的是在客户端保持状态的方案。但为什么禁用Cookie就不能得到Session呢?因为Session是用Session ID来确定当前对话所对应的服务器Session,而Session ID是通过Cookie来传递的,禁用Cookie相当于失去了Session ID,也就得不到Session了。
假定用户关闭Cookie的情况下使用Session,其实现途径有以下几种:
- 设置php.ini配置文件中的“session.use_trans_sid = 1”,或者编译时打开打开了“–enable-trans-sid”选项,让PHP自动跨页传递Session ID。
- 手动通过URL传值、隐藏表单传递Session ID。
- 用文件、数据库等形式保存Session ID,在跨页过程中手动调用。
61 如何避免sql注入
- PreparedStatement(简单又有效的方法)
- 使用正则表达式过滤传入参数
- 字符串过滤
- JSP中调用该函数检查是否包含非法字符
- JSP页面判断代码
62 什么是xss攻击,如何避免
xss攻击又称“跨站脚本攻击”,其原理是攻击者向有xss漏洞的网站中输入恶意的HTML代码,当用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。xss攻击类似于SQL注入攻击,SQL注入攻击中以SQL语句作为用户输入,从而达到查询/修改/删除数据的目的,然而在xss攻击中,通过插入恶意脚本,实现对用户浏览器的控制,获取用户的一些信息。xss时web程序中常见的漏洞,xss属于被动式且用于客户端的攻击方式。
xss防范的总体思路是:对输入(和URL参数)进行过滤,对输出进行编码。
63 什么是CSRF攻击,如何避免
CRSF(Cross-site request forgery)也被称为one-Click attack或者session riding,中文全称是叫跨站请求伪造。一般来说,攻击者通过伪造用户的浏览器请求,向访问一个用户自己曾经认证访问过的网站发送出去,使目标网站接收并误以为是用户的真实操作而去执行命令。常用于盗取账号、转账、发送虚假消息等。攻击者利用网站对请求的验证漏洞而实现这样的攻击行为,网站能够确认请求来源于用户的浏览器,却不能验证请求是否源于用户的真实意愿下的操作行为。
如何避免:
-
验证HTTP Referer字段
HTTP头中的Referer字段记录了该HTTP请求的来源地址。在通常情况下,访问一个安全受限的页面的请求来自同一个网站,而如果黑客要对其实施CRSP攻击,他一般只能在他自己的网站构造请求。因此,可以通过验证Referer值来防御CSRF攻击。
-
使用验证码
关键操作页面加上验证码,后台收到请求后通过判断验证码可以防御CSRF。但这种方法对用户不太友好。
-
在请求地址中添加token并验证
CRSP攻击之所以能成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于cookie中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有token或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于session之中,然后在每次请求时把token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。
对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。
而对于 POST 请求来说,要在 form 的最后加上 ,这样就把token以参数的形式加入请求了。 -
在HTTP头中自定义属性并验证
这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。
64 throw和throw的区别
throws是用来声明一个方法可能抛出的所有异常信息,将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理。而throw则是指抛出的一个具体的异常类型。
65 final、finally、finalize有什么区别
- final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
- finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
- finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System的gc()方法的时候,由垃圾回收器调用finalize(),回收垃圾。
66 try-catch-finally中哪个部分可以省略
catch可以省略
原因:
更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。
理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。
67 try-catch-finally中,如果catch中return了,finally还会执行吗
会执行,在return之前执行
68 常见的异常类
- NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
- SQLException:提供关于数据库访问错误或其他错误信息的异常。
- IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
- NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
- FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。
- IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
- ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
- ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。
- IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。
- ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
- NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。
- NoSuchMethodException:无法找到某一特定方法时,抛出该异常。
- SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。
- UnsupportedOperationException:当不支持请求的操作时,抛出该异常。
- RuntimeExceptionRuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。