所有资料来源于各方论坛博客,只做收集整理归纳
题目地址https://blog.csdn.net/sufu1065/article/details/88051083
Java基础
- JDK与JRE之间的区别
- JDK: Java Development Kit;Java开发工具,JDK包含三部分: 1. JRE:Java运行时环境; 2. Java基础类库; 3. Java开发工具;
- JRE: Java Runtime Environment;只是Java运行环境; JVM就是在JRE当中;是Java程序的虚拟运行环境(Java虚拟机)
- == 和 equals的区别
- ==:
基本类型: 比较的是值是否相等
引用类型: 比较的是对象的地址是否一致 - equals:
基本类型: 不能用于基本类型的比较,可以先转换成包装器类型
引用类型: equals是Object的方法;没重写之前,比较的是对象的地址;如String类,Date类等,对equals进行了重写,因此比较的是对象的值是否相等
- 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
- 两个对象用equals()比较返回true,那么两个对象的hashCode()方法必须返回相同的结果。
- 两个对象用equals()比较返回false,不要求hashCode()方法也一定返回不同的值,但是最好返回不同值,以提搞哈希表性能。
- 重写equals()方法,必须重写hashCode()方法,以保证equals方法相等时两个对象hashcode返回相同的值。
- final 在 java 中有什么作用?
- final是修饰符关键字: 可以放在变量名,方法以及类前面,起到修饰作用,
- final修饰变量: 该变量为一个常量,不可修改。
- final修饰方法: 该方法不可被子类重写,同时加载的时候只需要加载一次
- final修饰类: 该类不可被继承
- java 中的 Math.round(-1.5) 等于多少?
Math.round()四舍五入的原理,小数:
- 大于0.5,舍去小数,绝对值+1;
- 小于0.5,仅舍去小数;
- 等于0.5,取原数字+0.5
这里就成了-1.5+0.5 = -1
扩展: Math.floor() 求一个最接近它的整数,它的值小于或等于这个浮点数。(正数去掉小数,负数去小数-1)
String 属于基础的数据类型吗?
String属于引用数据类型
基本数据类型|包装器类型|长度|
---|---|---|
byte | Byte | -128~+127 |
short | Short | -32768~32767 |
int | Integer | -2147483648~2147483647 |
long | Long | (-263)~(263) |
float | Float | |
double | Double | |
char | Character | |
boolean | Boolean | |java 中操作字符串都有哪些类?它们之间有什么区别?
首先;这三个类都是以char[]的形式保存的字符串
- String: String类型的字符串是不可变的,对String类型的字符床做修改操作都是相当于重新创建对象
- StringBuilder: 线程不安全,性能更高
- StringBuffer: StringBuffer中的方法大部分都使用synchronized关键字修饰,所以StringBuffer是线程安全的, 性能较StringBuilder更弱
String str="i"与 String str=new String(“i”)一样吗?
不一样;
String str="i"会将i存放到常量池中,常量池中不会重复创建对象,也就是说如果有一个新的String str1 = "i"; 这个str1会指向常量池中的i对象,而不会新建一块内存地址
String str = new String("i")会在堆内存中开辟一块新的内存区域用于储存i;如何将字符串反转?
- 最简单的方法: StringBuilder的reverse()方法,StringBuffer(s).reverse().toString();
- 字符串数组,倒序输出:用split分离字符串,存到String[] 数组中,然后倒序输出数组
- String的charAt()方法;
- 递归
- String 类的常用方法都有那些?
- length() 返回字符串长度
- getByte() 将字符串转换成字节数组
- toCharArray() 将字符串转换成字符数组
- split(string) 将字符串按string切割
- equals() 比较两个字符串内容是否相等
- equalsIsIgnoreCase() 忽略大小写比较两个字符串内容是否相等
- contains(String) 判断字符串是否包含String
- startsWith(String) 判断一个字符是否以指定字符开头
- endsWith(String) 判断一个字符是否以指定字符结尾
- toUpperCase()/toLowerCase() 将一个字符串转换成大小写
- replace(oldString, newString) 将旧字符串替换成新字符串
- repalceFirst(String,String) 将第一次出现的某个内容替换成指定的内容
- substring(int) 从指定下标开始一直截取到字符串的最后
- substring(int,int) 从下标x截取到下标y-1对应的元素
- trim() 去除一个字符串的前后空格
- charAt(int) 得到指定下标位置对应的字符
- indexOf(String) 得到指定内容第一次出现的下标
- lastIndexOf(String) 得到指定内容最后一次出现的下标
抽象类必须要有抽象方法吗?
抽象类可以没有抽象方法;但有抽象方法的类一定是抽象类;
抽象类不能直接实例化;抽象类如果要实例化,抽象类必须指向实现所有抽象方法的子类对象(抽象类可以直接实例化,直接重写自己的抽象方法)普通类和抽象类有哪些区别?
最大的差别就在于是否可以直接实例化;抽象类不可以直接实例化,实例化抽象类有两种方式,
- 指向实现了所有抽象方法的子类
- 在实例化的过程中直接实现所有抽象方法
另外,抽象类可以没有抽象方法,但是普通类中一定没有抽象方法
final不可以修饰抽象类;final修饰普通类时,代表该类不可被继承;而抽象类设计的目的就是为了给其它类作为基类继承,添加final的话会导致该抽象类无法继承,失去作为基类的作用。
接口和抽象类的区别
Java由于是单继承模式的,因此如果需要实现多个“父类”中的方法时,无法通过单根继承来实现,而接口的设计,一方面是为了解耦,另一方面就是为了弥补Java单根继承的缺陷;
一个类可以实现多个接口,变相的实现了多继承的目的-
java 中 IO 流分为几种?
IO流分类
【IO流实现文件的读取与写出,文件,文件夹的拷贝删除等】:https://blog.csdn.net/weixin_42924812/article/details/105118865 BIO、NIO、AIO 有什么区别?
BIO: 同步阻塞
NIO: 同步非阻塞
AIO: 异步非阻塞Files的常用方法都有哪些?
Files. exists():检测文件路径是否存在。
Files. createFile():创建文件。
Files. createDirectory():创建文件夹。
Files. delete():删除一个文件或目录。
Files. copy():复制文件。
Files. move():移动文件。
Files. size():查看文件个数。
Files. read():读取文件。
Files. write():写入文件。
容器
-
Java容器有哪些
容器分类.png
- Set
- HashSet: 基于哈希表实现, 不允许重复;允许值为null,但是只能有一个;无序的;没有索引,所以不包含索引操作的方法
- LinkedHashSet: 同样基于哈希表实现,但是在HashSet上多了一个链表,用链表来维护每个元素的顺序; 不允许重复; 允许值为null,但是只能有一个; 有序的; 没有索引,所以不包含索引操作的方法
HashSet和LinkedHashSet的区别就在于是否有序 - TreeSet: 基于二叉树实现,可以确保元素的有序性,支持两种排序模式(自然排序/定制排序),默认为自然排序,TreeSet中加入的对象应该是同一个类的对象; 不允许重复;不允许null值;没有索引,所以不包含索引操作的方法
- List
- ArrayList: 基于数组实现,查询快,增删慢;
- LinkedList: 基于链表实现, 查询慢,增删快;
- Vector: 基于数组实现, 查询快,增删慢(跟ArrayList类似;两者的区别在于Vector是线程安全的;不考虑线程安全的情况的话,ArrayList性能更好)
- Map
- HashMap和HashTable都实现了Map接口,区别在于线程安全性; 同步(synchronzation);以及速度。
HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和 值(value),而Hashtable则不行)。
HashTable是线程安全的;Java 5提供了ConcurrentHashMap作为HashTable的替代;
单线程的模式下,HashMap的性能要优于HashTable - LinkedHashMap: 用链表来储存元素顺序
- TreeMap: 跟HashMap类似,只是TreeMap内部的数据结构采用的是树的结构
通常情况下使用HashMap就可以了
- HashMap和HashTable都实现了Map接口,区别在于线程安全性; 同步(synchronzation);以及速度。
- Collection 和 Collections 有什么区别?
- java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
List,Set,Queue接口都继承Collection。
直接实现该接口的类只有AbstractCollection类,该类也只是一个抽象类,提供了对集合类操作的一些基本实现。List和Set的具体实现类基本上都直接或间接的继承了该类。 - java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),大多数方法都是用来处理线性表的。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
说一下 HashMap 的实现原理?
https://blog.csdn.net/pyfysf/article/details/106913564
https://blog.csdn.net/qq_41973594/article/details/104072361说一下 HashSet 的实现原理?
ArrayList 和 LinkedList 的区别是什么?
- ArrayList是线性表(数组);因此它的查询速度快,但是插入删除的速度较慢(涉及到元素移动以及数组扩容)
- LinkedList是链表结构;因此它的查询速度较慢(做一次查询要从头开始遍历),但是插入速度快;
因此,对于不经常修改变动的数据建议用ArrayList储存;反之则用LinkedList储存;
- 如何实现数组和 List 之间的转换?
数组转List: java.util.Arrays工具类的asList()方法;
String[] strs = new String[] {"aaa", "bbb", "ccc"};
List list = Arrays.asList(strs);
List转数组: List 的toArray()方法;无参toArray方法返回Object数组,传入初始化长度的数组对象,返回该对象
List list = Arrays.asList("aaa", "bbb", "ccc");
String[] array = list.toArray(new String[list.size()]);
ArrayList 和 Vector 的区别是什么?
ArrayList 线程不安全; Vector线程安全;
在单线程模式下,ArrayList的性能要优于Vector;Array 和 ArrayList 有何区别?
Array是数组
ArrayList是容器;虽然底层也是用数组实现的;
Array的容量在创建时就已经确定了;同时数组中只能存放相同类型的元素
ArrayList是容器,容器的容量可以随时扩充,同时,容器中可以存放不同类型的元素;不过通常情况下,会通过泛型规定存放同一类型或者(父子类)的数据在 Queue 中 poll()和 remove()有什么区别?
- 相同点:都是返回第一个元素,并在队列中删除返回的对象。
- 不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。
哪些集合类是线程安全的?
Vector/ HashTable/ ConcurrentHashMap/ Stack迭代器 Iterator 是什么?
Iterator 是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现,从而解耦。
缺点是增加新的集合类需要对应增加新的迭代器类,迭代器类与集合类成对增加。
- 使用:
- 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返 回序列的第一个元素。
- 使用next()获得序列中的下一个元素。
- 使用hasNext()检查序列中是否还有元素。
- 使用remove()将迭代器新返回的元素删除。
- 如何确保一个集合不被修改
Collections. unmodifiableCollection(Collection c)
Arrays.asList()
通过这两种方法创建的List无法被修改;源码解读:
https://blog.csdn.net/fanbaodan/article/details/103237298
多线程
并发与并行的区别
并发:是指多个线程任务在同一个CPU上快速地轮换执行,由于切换的速度非常快,给人的感觉就是这些线程任务是在同时进行的,但其实并发只是一种逻辑上的同时进行;
并行:是指多个线程任务在不同CPU上同时进行,是真正意义上的同时执行。进程与线程的区别:
官方的解释:
进程:资源分配的最小单位
线程: CPU调度的最小单位
通俗的解释(知乎的回答;只做记录):
进程==火车; 线程==车厢
- 线程在进程下行进;(车厢无法单独运行,需要跟着火车走)
- 一个进程可以包含多个线程;(一列火车==n节车厢)
- 不同进程间的数据很难共享;(A火车上的乘客很难直接到B火车上)
- 同一进程下的线程间的数据可以很容易的实现共享;(乘客在车厢间走动)
- 进程要比线程消耗更多的计算机资源;(采用多列火车相比多个车厢更耗资源)
- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(也可能不会挂掉,大概率还是会挂)(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
- 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
- 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
- 守护线程是什么?
守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。
4 创建线程有哪几种方式?
- 继承Thread类创建线程类; 创建Thread类的子类并实现run()方法;实例化该类获得线程对象;调用start()方法启动线程(如果要加入守护线程的话需要再start()方法前调用setDaemon(true)方法)
// 这是用lambda表达式直接实例化线程对象了
Thread thread = new Thread(
()-> System.out.println(Thread.currentThread().isDaemon()?"守护线程":"用户线程")
);
// 普通的做法
// 先创建一个Thread类的子类;并重写run方法
public class ThreadDemo1 extends Thread{
@Override
public void run(){
System.out.println(getName()+"...");
}
}
// 创建ThreadDemo1的实例,并调用start()方法
public class Test {
public static void main(String[] args) {
ThreadDemo1 td = new ThreadDemo1();
td.run();
}
}
- 实现Runnable接口
// 实现Runnable接口后也需要重写run()方法;
public class RunnableDemoTest implements Runnable{
public void run() {
System.out.println("实现Runnable开启线程!");
}
public static void main(String[] args) {
Thread thread = new Thread(new RunnableDemoTest());
thread.start();
}
}
- 实现Callable接口
// Callable接口和Runnable接口的区别在于Runnable接口没有返回值;Callable接口可以有返回值
public class CallableDemoTest implements Callable {
public Object call() {
return "HelloCallable!";
}
@Test
public void test() throws ExecutionException, InterruptedException {
CallableDemoTest callableDemoTest = new CallableDemoTest();
FutureTask futureTask = new FutureTask(callableDemoTest);
Thread thread = new Thread(futureTask);
thread.start();
//获取返回值
futureTask.get();
}
}
Runnable接口和Callable接口的区别
1、两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
2、Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
要获取到Callable的返回结果,要通过FutureTask.get()方法获取,FutureTask.get()会阻塞主线程直到获得线程返回的结果;不调用FutureTask.get()方法的话,不会对主线程造成阻塞;-
线程有哪些状态?
5种基本状态: 新建,就绪,运行,阻塞,死亡
新建: Thread1 td = new Thread1();
就绪: td.start(); 调用start()方法并不意味之此线程就开始运行了,只说明该线程处于可以运行的就绪状态,何时运行,由CPU决定,什么时候拿到时间片了,就什么时候运行
运行: 线程获得时间片开始运行;随时可能会因为失去时间片导致线程返回就绪状态,这个看获得的时间片是不是够线程处理完自己的任务;也可以主动要求线程退出运行状态,返回就绪状态;调用yield()方法
死亡: 线程运行完毕,或者中途遇到异常,或者主动调用stop()方法;死亡状态下的线程是不会再次获得时间片的,要重启线程只能重新调用start()方法
阻塞: 主动调用sleep()方法,线程进阻塞状态,sleep时间到了就转成就绪状态; 主动调用 suspend 方法,进阻塞状态,然后只能通过主动调用resume()方法使线程进就绪状态; 调用了阻塞式 IO 方法。调用完成后,会进入就绪状态;试图获取锁。成功的获取锁之后,会进入就绪状态;线程在等待某个通知。其它线程发出通知后,会进入就绪状态
还有超时和等待两个状态
线程状态 sleep() 和 wait() 有什么区别?
1、这两个方法来自不同的类分别是Thread和Object,sleep方法属于Thread类中的静态方法,wait属于Object的成员方法。
2、sleep()是线程类(Thread)的方法,不涉及线程通信,调用时会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,用于线程间的通信,调用时会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才进入对象锁定池准备获得对象锁进入运行状态。
3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)。
4、sleep()方法必须捕获异常InterruptedException,而wait()\notify()以及notifyAll()不需要捕获异常。
Notice
1、sleep方法只让出了CPU,而并不会释放同步资源锁。
2、线程执行sleep()方法后会转入阻塞状态。
3、sleep()方法指定的时间为线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
4、notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度。notify()和 notifyAll()有什么区别?
唤醒一个线程还是唤醒所有线程的区别
唤醒后就会进入对应对象的锁池去争夺对象锁的使用权;谁抢到了对象锁的使用权,谁就进入就绪状态准备运行线程的 run()和 start()有什么区别?
run()方法不能启动线程,run()方法中的代码就是此线程将要执行的代码;一个线程只有run方法并不能让他运行;start()方法是用于启动线程的。创建线程池有哪几种方式?
Executors: 一个与线程相关的工具类,
1、newCachedThreadPool
2、newFixedThreadPool
3、newSingleThreadExecutor
4、newScheduledThreadPool
5、newSingleThreadScheduledExecutor线程池都有哪些状态?
1、RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。
2、SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。调用线程池的shutdown()方法时,线程池由RUNNING -> SHUTDOWN。
3、STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。调用线程池的shutdownNow()方法时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
4、TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。因为terminated()在ThreadPoolExecutor类中是空的,所以用户想在线程池变为TIDYING时进行相应的处理;可以通过重载terminated()函数来实现。
当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
5、TERMINATED:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。线程池中 submit()和 execute()方法有什么区别?
1、接收的参数不一样。exucute只能执行实现Runnable接口的线程,submit可以执行实现Runnable接口或Callable接口的线程
2、submit有返回值,而execute没有在 java 程序中怎么保证多线程的运行安全?
- 原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
- 可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
- 有序性:程序执行的顺序按照代码的先后顺序执行
如何保证: 简单点,加锁吧 - JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
- synchronized、volatile、LOCK,可以解决可见性问题
- Happens-Before 规则可以解决有序性问题
- 多线程中锁的升级原理是什么?
锁的级别从低到高:
无锁——偏向锁——轻量级锁——重量级锁,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
没有优化以前,sychronized是重量级锁(悲观锁),使用 wait 和 notify、notifyAll 来切换线程状态非常消耗系统资源;线程的挂起和唤醒间隔很短暂,这样很浪费资源,影响性能。
所以为了减少获得和释放锁带来的性能消耗,JVM 对 sychronized 关键字进行了优化,把锁分为 无锁、偏向锁、轻量级锁、重量级锁状态。
无锁:没有对资源进行锁定,所有线程都可以访问并修改该资源;但是同一时间只能有一个线程对其修改成功,其余线程会反复尝试修改,直到成功
偏向锁:指偏向第一个加锁线程,后续获取锁的时候,如果没有其他线程争抢这个对象,那这个对象会优先分配给有偏向锁的线程
轻量级锁:当线程B试图访问具有偏向锁的线程A时,偏向锁会自动升级成轻量锁,而线程B会通过自旋尝试获取锁,此时,线程B不会阻塞,从而提高性能
重量级锁:当线程A拥有轻量级锁,而线程B自旋超过一定次数时,线程A拥有的轻量级锁会自动升级成重量级锁;或者,当线程A拥有轻量级锁时,线程B在自旋,此时有第三个线程C尝试获取锁,则线程A会升级成重量级锁
- 死锁是什么?如何避免死锁
死锁定义:线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
我的理解: 线程A有一个资源b1;线程B有一个资源a1;b1被线程B需要,a1被线程A需要;当这两个线程同时执行时,由于彼此获取不到互相需要的资源,所以两个线程都进入等待状态,如果线程都不主动释放对方需要的资源,则两个线程就会进入死锁的状态。
产生死锁的必要条件:
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。
预防死锁: - 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
- 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
- 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
- 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
ThreadLocal 是什么?有哪些使用场景?
字面上看,就是线程的局部变量,也就是说一个ThreadLocal的变量只有当前线程可以访问,别的线程访问不了,从根本上防止线程竞争
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
实现原理:微信搜三太子敖丙;他有文章写了这个;我收藏了synchronized 和 volatile 的区别是什么?
- volatile只能作用于变量,使用范围较小。synchronized可以用在变量、方法、类、同步代码块等,使用范围比较广。
- volatile只能保证可见性和有序性,不能保证原子性。而可见性、有序性、原子性synchronized都可以包证。
- volatile不会造成线程阻塞。synchronized可能会造成线程阻塞。
- Lock和synchronized的区别
- Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
- Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
- synchronized 和 ReentrantLock 区别是什么?
- synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
- synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
- synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
- synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();
- ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
- synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
- synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放
- 说一下 atomic 的原理?
反射
1. 什么是反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
简单的说: 在运行过程中,动态的获取某个类的所有方法和属性
使用方式:
// 根据一类的全名字符串来获得一个类的类对象
// a: 一个描述了全名类的字符串;com.***.***.***.ReflactionTest
Class> clazz = Class.forName(a);
// 获得传递过来的类的所有方法
Method[] methods = clazz.getDeclaredMethods();
// 获得类的所有属性
Field[] declaredFields = clazz.getDeclaredFields();
// 获得类的所有构造器
Constructor>[] constructors = clazz.getDeclaredConstructors();
2. 什么是Java序列化,什么情况下需要用到Java序列化
序列化:将 Java 对象转换成字节流的过程。
反序列化:将字节流转换成 Java 对象的过程。
当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。
序列化的实现: 类实现 Serializable 接口,这个接口(Serializable)没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化。
3. 动态代理
- 动态代理的定义:
当我们需要给某个类或者接口中的方法添加一些额外的功能比如日志、事务的时候,可以通过创建一个代理类来实现这些功能;该代理类既包含了原有类的完整功能,同时在这些功能的基础上添加了其他的逻辑。这个代理类不是事先定义好的,而是动态生成的,比较灵活
简单来说: 在保留原类功能的基础上,新增一些功能 - 动态代理的类型
- java动态代理
java动态代理有个缺点就是要被代理的类必须实现一个接口,否则没法代理 - cglib动态代理
cglib动态代理可以对没有实现接口的类进行代理
- 动态代理的应用:
最熟悉的应该就是AOP了 - 动态代理实现:
// 暂时不写;
对象拷贝
对象拷贝是指复制一个新的对象,在内存中开辟一块新的内存空间用来保存拷贝后的对象;而不仅仅是将两个对象引用指向同一块内存地址。具体实现看下面的代码
对象拷贝需要用到Class.clone()
1. 为什么要使用克隆
克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。
提个醒,我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。
而通过clone方法赋值的对象跟原来的对象时同时独立存在的。
2. 如何实现对象克隆
// 创建一个类,用于复制
package com.ricardo.classclone;
public class User implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
// 重写Objcet的clone方法,实现clone对象的功能
@Override
public Object clone(){
User user = null;
try {
user = (User)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
}
}
- 对象引用的复制
public class ClassClone {
public static void main(String[] args) {
User user = new User();
user.setNumber(12345);
User user1 = user; // 这种方式只是将user1的引用指向user的内存地址上,并没有实现对象拷贝的功能
System.out.println("复制引用后(未修改user.number):user1.number="+user1.getNumber()); // 输出的是user中的number
// 修改user中的number值, user1的number也会随之改变
user.setNumber(678910);
System.out.println("修改user.number后: user.number="+user.getNumber());
System.out.println("修改user.number后: user1.number="+user1.getNumber());
}
}
// 结果
复制引用后(未修改user.number):user1.number=12345
修改user.number后: user.number=678910
修改user.number后: user1.number=678910
- 浅克隆
public class ClassClone {
public static void main(String[] args) {
User user = new User();
user.setNumber(12345);
// 利用Object.clone()方法进行对象克隆
User user2 = (User) user.clone();
System.out.println("克隆对象后(未修改user.number): user2.number="+user2.getNumber()); // 这里将输出678910;
user.setNumber(1234);
System.out.println("修改user.number后:user.number= "+user.getNumber());
System.out.println("修改user.number后:user2.number= "+user2.getNumber());
}
}
// 结果
克隆对象后(未修改user.number): user2.number=12345
修改user.number后:user.number= 1234
修改user.number后:user2.number= 12345
- 深克隆
实现深克隆有两种方式
1 对每一个属性都实现Cloneable接口,
2 对每一个属性实现Serializable接口,使该类可以序列化,通过序列化的形式实现多层克隆的问题
如果嵌套的克隆很多的话,建议用第二种方式
3. 浅拷贝与深拷贝之间的区别
浅拷贝只能拷贝当前类,当当前类存在某些属性是其他类的时候,如果这些属性没有实现Cloneable接口的话,那么这些属性无法实现拷贝;拷贝当前类的时候,其实是创建了一个该属性的引用指向原来的地址
Java Web
jsp与servlet的区别
Servlet:
Servlet 是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面。它担当客户请求(Web浏览器或其他HTTP客户程序)与服务器响应(HTTP服务器上的数据库或应用程序)的中间层。 Servlet是位于Web服务器内部的服务器端的Java应用程序,与传统的从命令行启动的Java应用程序不同,Servlet由Web服务器进行加载,该Web服务器必须包含支持Servlet的Java虚拟机。
jsp:
JSP 全名为Java Server Pages,中文名叫java服务器页面,其根本是一个简化的Servlet设计。JSP技术使用Java编程语言编写类XML的tags和scriptlets,来封装产生动态网页的处理逻辑。网页还能通过tags和scriptlets访问存在于服务端的资源的应用逻辑。JSP将网页逻辑与网页设计的显示分离,支持可重用的基于组件的设计,使基于Web的应用程序的开发变得迅速和容易。 JSP(JavaServer Pages)是一种动态页面技术,它的主要目的是将表示逻辑从Servlet中分离出来。
区别
jsp的底层就是servlet;jsp页面会经过web容器的编译转变成Java文件(jvm只能读取Java文件)
jsp内置了9大对象,但是servlet没有;
简单来说:jsp就是在html里写Java代码,servlet就是在Java代码里写html代码
jsp 有哪些内置对象?作用分别是什么?
类 | 对象 | 作用 |
---|---|---|
HttpServletRequest | Request | 代表请求对象;用于接受客户端通过HTTP协议连接传输服务器端的数据。 |
HttpServletResponse | Response | 代表响应对象,主要用于向客户端发送数据。 |
JspWriter | out | 主要用于向客户端输出数据,out的基类是jspWriter |
HttpSession | session | 主要用来分别保存每个月的信息与请求关联的会话;会话状态的维持是web应用开发者必须面对的问题。 |
ServletContext | application | 主要用于保存用户信息,代码片段的运行环境;它是一个共享的内置对象,即一个容器中的多个用户共享一个application |
PageContext | PageContext | 管理网页属性,为jsp页面包装页面的上下文,管理对属于jsp的特殊可见部分中已经命名对象的访问,它的创建和初始化都是由容器来完成的。 |
ServletConfig | Config | 代码片段配置对象,标识Servlet的配置。 |
Object | Page | 处理jsp页面,是object类的一个实例,指的是jsp实现类的实例 |
Exception | 处理jsp文件执行时发生的错误和异常,只有在错误页面里才使用,前提是在页面指令里要有isErrorPage=true。 |
JSP的四个作用域
所谓作用域,指的是信息共享的范围。
名称 | 作用域 |
---|---|
application | 在整个应用程序中有效 |
session | 在当前会话中有效 |
request | 在当前请求中有效 |
page | 在当前页面有效 |
cookie和session的区别
cookie存放在客户端,用于保存一些客户数据
session存放在服务器端,用于保存会话信息
转发与重定向的区别
// 转发
request.getRequestDispatcher("/two").forward(request, response);
// 重定向
response.sendRedirect("two");
转发发生在一次请求里;如果有个数据存放在request作用域里,那么转发的时候可以带上这个数据
而重定向会启动第二次请求,同样放在request作用域里的数据时,转发后的请求是获取不到的
session的实现原理
客户端登录后,在服务器端创建一个session,然后在服务器端维护一个session表,以key-value的格式记录创建好的session,key为session的id,value为session保存的内容,服务器端创建完session后,会返回给客户端一个sessionId;(通常是存放在cookie中,如果cookie被禁用的话,可以考虑直接放在url的参数列表里,或者header里;)客户端持有sessionId后,之后的每次请求服务器都会带上这个sessionId,服务器会判断sessionId在自己维护的session表中是否存在,存在的话说明是同一个session,就可以获取到sessio中的内容了。