(欢迎各位大佬添加新的面试知识,本文纯属博主手打,如有错误,欢迎各位大佬批评在下方,小弟一定及时改正)
基础篇
1、一个".java"源文件可以有多个类,但只能有一个public类,并且public的类名必须和文件名一致。
2、goto是java中的保留字,现在没有在java中使用
3、&和&&的区别:
两者都是逻辑与的运算符,表示逻辑与。当运算符两边的运算结果都为true时,结果才为true,否则,当一方为false时,结果为false。。。。。。其中&&具有短路的功能,如果第一个表达式为false时,则不会计算第二个表达式,结果为false。。如if(x==33&++y>0)y会增长,if(x==33&&++y>0)y不会增长。
4、switch语句可以作用在byte上,不能作用在long和String上。
5、short s1= 1;s1 = s1 + 1; 由于s1+1运算时会自动提升表达式的类型,所以结果是int型。 编译会报告需要强制转换的类型。
short s1= 1;s1 += 1; 正确编译。
6、char型变量能存储一个中文汉字。因为char型变量是用来存储Unicode编码的字符的,unicode编码字符包含了汉字。
7、最有效的方法算出2乘以8等于几 ,, 2<<3 因为将一个数左移n位,就相当于乘以2的n次方。
8、使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指的对象中的内容还是可以改变的。
9、"=="和equals()方法区别
== 比较Stack中的值(引用数据类型stack中存放的是对象的堆内存地址), == 比较的是两个引用在内存中指向的是不是同一对象(即同一内存空间),也就是说在内存空间中的存储位置是否一致。如果两个对象的引用相同时(指向同一对象时),“==”操作符返回true,否则返回flase。
equals()通常被覆写为比较对象的值。
1)对于==,比较的是值是否相等
如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;
如果作用于引用类型的变量,则比较的是所指向的对象的地址
2)对于equals方法,注意:equals方法不能作用于基本数据类型的变量,equals继承Object类,比较的是是否是同一个对象
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
1 public class Test {
2 public static void main(String[] args) {
3 String str1 = "abc";
4 String str2 = "abc";
5 System.out.println(str1==str2);//true
6
7
8 String str3 = new String("abc");
9 String str4 = new String ("abc");
10 System.out.println(str3==str4);//false
11 System.out.println(str3.equals(str4));//true
12 }
13 }
10、静态变量和实例变量的区别
在语法定义上的区别:静态变量前要加static关键字,而实例变量前不加。
在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例对象才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例变量,而是属于类,所以也被称为类变量,只要程序加载了类的字节码,不用创建任务实例对象,静态变量就会被分配空间,就可以被使用了。
总之,实例变量必须创建对象才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
11、Math.round(11.5)等于12 , ,Math.round(-11.5)=-11;
12、private、protected、public和default的区别
public:可以被所有其他类所访问
private:只能被自己访问和修改
protected:自身、子类及同一个包中类可以访问
default:同一包中的类可以访问,声明时没有加修饰符,认为是friendly。
13、Overload和Override的区别
Overload是重载的意思,Override是覆盖的意思,也就是重写。
重载Overload:在同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数和类型不同)。
重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中定义的方法,这相当于把父类中定义的那个完全相同的方法给覆盖掉了,这也是面向对象编程的多态的一种表现。子类覆盖父类方法时只能抛出父类的异常或者异常的子类或者父类异常的子集,因为子类可以解决父类的一些问题,但不能比父类有更多的问题。还有,子类方法的访问权限只能比父类的更大,不能更小。如果父类的方法是private类型,则子类中根本不存在覆盖,即子类中和父类的private的同名的方法没有覆盖的关系,因为private的访问权限只限于同一类中,而子类就不会访问到private的方法,所以是子类中增加的一个全新的方法。
重载overload的特点就是与返回值无关,只看参数列表,所以重载的方法是可以改变返回值类型的。所以,如果两个方法的参数列表完全一样,是不能通过让他们的返回值类型不同来实现重载的。我们可以用反证法来说明这个问题,因为我们有时候调用一个方法时也可以不定义返回结果变量,即不要关心其返回结果,例如,我们调用map.remove(key)方法时,虽然remove方法有返回值,但是我们通常都不会定义接收返回结果的变量,这时候假设该类中有两个名称和参数列表完全相同的方法,仅仅是返回类型不同,java就无法确定编程者倒底是想调用哪个方法了,因为它无法通过返回结果类型来判断。
14、构造器Constructor不能被继承,因为不能重写Override,但可以被重载Overload。
15、接口可以继承接口,抽象类可以实现接口,抽象类可以继承具体类,抽象类可以有静态的main方法。
抽象类与普通类唯一的区别就是不能创建实例对象和允许有abstract方法。
两者的语法区别:
抽象类可以有构造方法,接口中不能有构造方法。
抽象类可以有普通成员变量,接口中不能有普通成员变量。
抽象类可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
抽象类的抽象方法的访问类型可以是public ,protected 和默认类型,但接口中的方法必须是public类型的,默认即为public abstract类型。
抽象类中可以包含静态方法,接口中不能包含静态方法。
抽象类和接口都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意访问,但接口中定义的变量只能是public static final 类型,并且默认即为public static final 类型。
一个类可以实现多个接口,但是只能继承一个类。
两者在应用上的区别:
接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约,而抽象类在代码方面发挥作用,实现代码的重写。
16、abstract的method不可以是static的,因为抽象类的方法是要被子类实现的。
17、内部类就是在一个类的内部定义的类,内部类中不能定义静态成员,内部类可以直接访问外部类中的成员变量,内部类可以定义在外部类的方法外面,也可以定义在外部类的方法体中。
内部类完全可以引用它的包含类的成员。如果不是静态内部类,那没有什么限制!静态内部类不能访问外部类的成员!!
匿名内部类可以继承其他类或者实现其他接口。
18、super.getClass()方法调用
import java.util.Date;
public class Test1 extends Date{
public static void main(String[] args) {
new Test1().test();
}
public void test(){
System.out.println(super.getClass().getName());
}
}
//控制台输出:
//com.he.test.Test1
这属于脑筋急转弯的题目,,在test方法中,直接调用了getClass().getName()方法,返回的是Test1类名,,由于getClass()在Object类中定义成了fianl,子类不能覆盖该方法,所以在test方法中调用getClass().getName()方法,其实就是在调用从父类继承的getClass()方法,等效于调用super.getClass().getName()方法。。所以super.getClass().getName()方法返回的也是Test1。如果想得到父类的名称,应该:getClass().getSuperClass().getName();
19、String是不可变的类,所以他的所有对象都是不可变对象。因为是不可变类,所以不可以继承!
20、String s = new String("xyz"); 创建了两个或者一个String Object 。。。 "xyz"对应一个对象,这个对象放在字符串常量缓冲区,常量"xyz"不管出现多少遍,都是缓冲区中的那一个,New String 每写一遍,就会创建一个新的对象,它依据那个常量"xyz"对象的内容来创建一个新的String对象。如果以前就用过"xyz",这句代表就不会创建"xyz"自己了,直接从缓冲区拿。
21、String 和StringBuffer
public class Test2 {
public static void main(String[] args) {
System.out.println(new String("abc").equals(new String("abc")));//true
System.out.println(new StringBuffer("abc").equals(new StringBuffer("abc")));//false
}
}
String 方法实现了equals 方法,而StringBuffer没实现equals 方法。
String是不可变的,而StringBuffer是可变的线程安全的。当字符数据要改变的时候使用StringBuffer。
22、把一段逗号分隔的字符串转换成一个数组
package com.he.test;
import java.util.StringTokenizer;
public class Test2 {
// 把一段逗号分隔的字符串转换成一个数组
public static void main(String[] args) {
String str = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z";
String[] strArray = null;
// 思路1:使用String的split方法
System.out.println("调用convertStrToArray结果:");
strArray = convertStrToArray(str);
printArray(strArray);
// 思路2:使用StringTokenizer实现
// api说明:StringTokenizer 是出于兼容性的原因而被保留的遗留类
System.out.println("调用convertStrToArray2结果:");
strArray = convertStrToArray2(str);
printArray(strArray);
}
// 使用String的split 方法
private static String[] convertStrToArray(String str) {
String[] strArray = null;
strArray = str.split(","); // 拆分字符为"," ,然后把结果交给数组strArray
return strArray;
}
// 使用StringTokenizer实现
private static String[] convertStrToArray2(String str) {
StringTokenizer st = new StringTokenizer(str, ",");// 把","作为分割标志,然后把分割好的字符赋予StringTokenizer对象。
String[] strArray = new String[st.countTokens()];
int i = 0;
while (st.hasMoreTokens()) {// 看看此 tokenizer 的字符串中是否还有更多的可用标记。
strArray[i++] = st.nextToken();// 返回此 string tokenizer 的下一个标记。
}
return strArray;
}
// 输出数组
private static void printArray(String[] array) {
System.out.print("[");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]);
if (i == array.length - 1) {
System.out.print("]");
} else {
System.out.print(",");
}
}
}
}
23、找了一些链接
24、int 和 Integer 有什么区别
int 是基本数据类型
Integer是其包装类,注意是一个类。
为什么要提供包装类呢???
一是为了在各种类型间转化,通过各种方法的调用。否则 你无法直接通过变量转化。
比如,int要转为String
int a=0;
String result=Integer.toString(a);
在java中包装类,比较多的用途是用在于各种数据类型的转化中。
int num=Integer.valueOf("12");
int num2=Integer.parseInt("12");
double num3=Double.valueOf("12.2");
double num4=Double.parseDouble("12.2");
//其他的类似。通过基本数据类型的包装来的valueOf和parseXX来实现String转为XX
String a=String.valueOf("1234");//这里括号中几乎可以是任何类型
String b=String.valueOf(true);
String c=new Integer(12).toString();//通过包装类的toString()也可以
String d=new Double(2.3).toString();
使用泛型
List<Integer> nums;
这里<>需要类。如果你用int。它会报错的。
25、说说反射的用途及实现
Java反射机制主要提供了以下功能:在运行时构造一个类的对象;判断一个类所具有的成员变量和方法;调用一个对象的方法;生成动态代理。反射最大的应用就是框架。
Java反射的主要功能:
确定一个对象的类
取出类的modifiers,数据成员,方法,构造器,和超类.
找出某个接口里定义的常量和方法说明.
创建一个类实例,这个实例在运行时刻才有名字(运行时间才生成的对象).
取得和设定对象数据成员的值,如果数据成员名是运行时刻确定的也能做到.
在运行时刻调用动态对象的方法.
创建数组,数组大小和类型在运行时刻才确定,也能更改数组成员的值.
http://uule.iteye.com/blog/1423512
26、说说自定义注解的场景及实现
登陆、权限拦截、日志处理,以及各种Java框架,如Spring,Hibernate,JUnit 提到注解就不能不说反射,Java自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志注解,就进行日志记录。
27、HTTP 请求的 GET 与 POST 方式的区别
GET方法会把名值对追加在请求的URL后面。因为URL对字符数目有限制,进而限制了用在客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。
POST方法通过把请求参数值放在请求体中来克服GET方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过POST请求传递的敏感信息对外部客户端是不可见的。
https://www.cnblogs.com/wangli-66/p/5453507.html
28、session 与 cookie 区别
Cookie和Session都是会话技术,Cookie是运行在客户端,Session是运行在服务器端。
Cookie有大小限制以及浏览器在存cookie的个数也有限制,Session是没有大小限制和服务器的内存大小有关。
Cookie有安全隐患,通过拦截或本地文件找得到你的cookie后可以进行攻击。
Session是保存在服务器端上会存在一段时间才会消失,如果session过多会增加服务器的压力。
无论客户端浏览器做怎么样的设置,session都应该能正常工作。客户端可以选择禁用 cookie,但是, session 仍然是能够工作的,因为客户端无法禁用服务端的 session。
29、List 和 Set 区别
List,Set都是继承自Collection接口
List特点:元素有放入顺序,元素可重复。Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉。
(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。)
Set和List对比:
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
30、List 和 Map 区别
List是对象集合,允许对象重复。Map是键值对的集合,不允许key重复。
31、Arraylist 与 LinkedList 区别
Arraylist:
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。
LinkedList:
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景
缺点:因为LinkedList要移动指针,所以查询操作性能比较低。
当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。
32、HashMap 和 Hashtable 的区别
1.hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
2.hashTable同步的,而HashMap是非同步的,效率上比hashTable要高。
3.hashMap允许空键值,而hashTable不允许。
4.HashMap是非线程安全的,HashTable是线程安全的。
参考:http://blog.youkuaiyun.com/qq_22118507/article/details/51576319
33、HashSet 和 HashMap 区别
set是线性结构,set中的值不能重复,hashset是set的hash实现,hashset中值不能重复是用hashmap的key来实现的。
map是键值对映射,可以空键空值。HashMap是Map接口的hash实现,key的唯一性是通过key值hash值的唯一来确定,value值是则是链表结构。
他们的共同点都是hash算法实现的唯一性,他们都不能持有基本类型,只能持有对象
34、为何Map接口不继承Collection接口?
尽管Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map继承Collection毫无意义,反之亦然。
如果Map继承Collection接口,那么元素去哪儿?Map包含key-value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范。
35、Iterator和ListIterator的区别是什么?
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
36、Java中的HashMap的工作原理是什么?
我们知道在Java中最常用的两种结构是数组和模拟指针(引用),几乎所有的数据结构都可以利用这两种来组合实现,HashMap也是如此。实际上HashMap是一个“链表散列”,数据结构:最左侧是一个数组,数组中的每一个元素都是一个链表,链表的每一个元素都是entry。
HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。
37、如果两个键的hashcode相同,你如何获取值对象?
当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
38、HashMap 和 ConcurrentHashMap 的区别
ConcurrentHashMap是线程安全的HashMap的实现。
(1)ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的syn关键字锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。
(2)HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。
39、父类与子类之间的调用顺序(打印结果)
a) 父类静态代码块
b) 子类静态代码块
c) 父类构造方法
d) 子类构造方法
e) 子类普通方法
f) 重写父类的方法,则打印重写后的方法
40、创建线程的方式及实现
Java中创建线程主要有三种方式:
一、继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
public class Thread4 extends Thread{
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i == 20){
new Thread4().start();
Thread.sleep(100);
new Thread4().start();
}
}
}
}
上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。getName()方法返回调用该方法的线程的名字。
二、通过Runnable接口创建线程
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
public class Thread5 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i == 20){
Thread5 thread5 = new Thread5();
new Thread(thread5,"新线程1").start();
new Thread(thread5,"新线程2").start();
}
}
}
}
三、通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
public static void main(String[] args) {
CallableThread call = new CallableThread();
FutureTask<Integer> task = new FutureTask<>(call);
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i == 20){
new Thread(task,"有返回的线程").start();;
}
}
try {
System.out.println("子线程的返回值"+task.get());
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
创建线程的三种方式的对比
采用实现Runnable、Callable接口的方式创见多线程时,优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:线程类已经继承了Thread类,所以不能再继承其他父类。
41、分布式环境下,怎么保证线程安全
https://blog.youkuaiyun.com/cutesource/article/details/5791350?utm_source=blogxgwz4
42、sleep() 、join()、yield()有什么区别
1、sleep()方法
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有Synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常。
比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
2、join()方法
Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。
比如在B线程调用了A的join()方法,直到线程A结束后,才会继续执行线程B。
保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。
3、yield()方法
yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同。
43、说说 CountDownLatch 原理
Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
44、说说 CyclicBarrier 原理
45、说说 Semaphore 原理
46、说说 Exchanger 原理
java.util.concurrent.Exchanger应用范例与原理浅析
47、ThreadLocal 原理分析
48、讲讲线程池的实现原理
主要是ThreadPoolExecutor的实现原理
49、线程池的几种方式
newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程
newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制
newSingleThreadExecutor()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行
newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer
参考:创建线程池的几种方式
50、线程的生命周期
(1)生命周期的五种状态
新建(new Thread)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();
就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();
运行(running)
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
堵塞(blocked)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用motify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)
(2)常用方法:
void run() 创建该类的子类时必须实现的方法
void start() 开启线程的方法
static void sleep(long t) 释放CPU的执行权,不释放锁
static void sleep(long millis,int nanos)
final void wait()释放CPU的执行权,释放锁
final void notify()
static void yied()可以对当前线程进行临时暂停(让线程将资源释放出来)
参考:线程的生命周期
51、说说线程安全问题
线程安全是指要控制多个线程对某个资源的有序访问或修改,而在这些线程之间没有产生冲突。
在Java里,线程安全一般体现在两个方面:
1、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体现在关键字synchronized。如ArrayList和Vector,HashMap和Hashtable(后者每个方法前都有synchronized关键字)。如果你在interator一个List对象时,其它线程remove一个element,问题就出现了。
2、每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。
52、悲观锁 乐观锁
乐观锁 悲观锁
是一种思想。可以用在很多方面。
比如数据库方面。
悲观锁就是for update(锁定查询的行)
乐观锁就是 version字段(比较跟上一次的版本号,如果一样则更新,如果失败则要重复读-比较-写的操作。)
JDK方面:
悲观锁就是sync
乐观锁就是原子类(内部使用CAS实现)
本质来说,就是悲观锁认为总会有人抢我的。
乐观锁就认为,基本没人抢。
53、CAS 乐观锁
乐观锁是一种思想,即认为读多写少,遇到并发写的可能性比较低,所以采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。
CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
CAS顶多算是乐观锁写那一步操作的一种实现方式罢了,不用CAS自己加锁也是可以的。
54、ABA 问题
ABA:如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A,当前线程的CAS操作无法分辨当前V值是否发生过变化。
55、乐观锁的业务场景及实现方式
乐观锁(Optimistic Lock):
每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。
乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
56、线程的基本状态以及状态之间的关系?
其中Running表示运行状态,Runnable表示就绪状态(万事俱备,只欠CPU),Blocked表示阻塞状态,阻塞状态又有多种情况,可能是因为调用wait()方法进入等待池,也可能是执行同步方法或同步代码块进入等锁池,或者是调用了sleep()方法或join()方法等待休眠或其他线程结束,或是因为发生了I/O中断。
57、Java中有几种类型的流?
字节流和字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。在java.io 包中还有许多其他的流,主要是为了提高性能和使用方便。关于Java的I/O需要注意的有两点:一是两种对称性(输入和输出的对称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。另外Java中的流不同于C#的是它只有一个维度一个方向。
58、JSP和Servlet面试题
https://blog.youkuaiyun.com/t0404/article/details/51893265/
59、Spring面试题整理
https://blog.youkuaiyun.com/hrbeuwhw/article/details/79476988
60、ssh框架
61、数据库SQL优化大总结之 百万级数据库优化方案
https://blog.youkuaiyun.com/zhushuai1221/article/details/51740846
62、Sql中#和$的区别
#{} 传入值时,SQL解析时,参数是带引号的,而${} 传入值时,SQL解析,参数是不带引号的。
#方式能很大程度防止SQL注入,而$方式无法防止SQL注入。
63、静态包含和动态包含的区别
静态包含<%@include file="xxx.jsp"%>和动态包含<jsp:include page="xxx.jsp">
1.<%@include file="xxx.jsp"%>为jsp中的编译指令,其文件的包含是发生在jsp向servlet转换的时期,
<jsp:include page="xxx.jsp">为jsp中的动作指令,其文件的包含是发生在编译时期,是将java文件编译到class文件的时期
2.使用静态包含只能产生一个class文件,而使用动态包含能产生多个class文件
3.使用静态包含,包含页面和被包含页面的request对象为同一对象,因为静态包含只是将被包含的页面的内容复制到包含的页面中去;而动态包含包含页面和被包含页面不是同一个页面,被包含的页面的request对象可以取到的参数范围要相对大些,不仅可以取到传递到包含页面的参数,同样也能取得在包含页面向下传递的参数 。
64、servlet和jsp的区别
(1)jsp经编译后就成了servlet,(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)
(2)jsp更擅长于页面显示,servlet更擅长于逻辑控制
(3)Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到。Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如何镶嵌到一个类中,由Jsp容器完成。 而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。
(4)JSP是Servlet技术的扩展,本质上就是Servlet的简易方式。JSP编译后是“类servlet”
(5)Servlet和JSP最主要的不同点在于: Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。 而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。 JSP侧重于视图,Servlet主要用于控制逻辑 Servlet更多的是类似于一个Controller,用来做控制。
65、session的特点有哪些?
1.不是线程安全的,所以在设计软件架构的同时,应该避免多个线程共享同一个session实例
2.session实例是轻量级的,所谓轻量级就是指它的创建和销毁不需要消耗太多的资源。
3.在session中,每个数据库操作都是在一个事务中进行的,这样就可以隔离开不同的操作。
66、session的缓存的作用
1.减少访问数据库的频率。应用程序从内存中读取持久化对象的速度显然比到数据库中查询数据的速度快多了,因此Session的缓存可以提高数据访问的性能。
2.保证缓存中的对象与数据库中的相关记录保持同步。当缓存中持久化对象的状态发生了变换,Session并不会立即执行相关的SQL语句,这使得Session能够把几条相关的SQL语句合并为一条SQL语句,以便减少访问数据库的次数,从而提高应用程序的性能
67、struts2和springMVC的区别:
1.springMVC入口是一个servlet前端控制器(DispatcherServlet),struts2入口是一个filter过滤器(StrutsPrepareAndExecuteFilter)
2.struts2通过在action类中定义成员变量接收参数,(属性驱动和模型驱动),它只能使用多例模式管理action.
springmvc通过在coontroller方法中定义形参接收参数,springmvc可以使用单例模式管理controller.
3.springmvc是基于方法开发的,注解开发中使用requestMapping将url和方法进行 映射,如果根据url找到controller类的方法生成一个handler处理器对象(只包括一个method).
struts2是基于类开发的,每个请求过来创建一个action实例,实例对象中有若干个方法.
开发中建议使用springmvc,springmvc方法更类似service业务方法.
4.truts2采用值栈存储请求和相应的数据,通过OGNL存取数据,springmvc通过参数绑定期将request请求内容解析,并给方法形参赋值.
5.struts2和springmvc的速度是相当的,由于struts2的漏洞较多,很多企业使用springmvc
68、拦截器和过滤器的区别
1.拦截器机遇java反射机制的,而过滤器是基于函数回调的。
2.过滤器依赖于servlet容器,而拦截器不依赖于servlet容器
3.拦截器只能对Action请求起作用,而过滤器可以对几乎所有请求起作用
4.拦截器可以访问Action上下文、值栈里的对象,而过滤器不能
5.在Aciton的生命周期里,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次。
69、struts2有哪些优点
1.在软件设计上struts2的应用可以不依赖于Servlet API和struts API。 Struts2的这种设计属于无侵入式设计;
2.拦截器,实现如参数拦截注入等功能;
3.类转换器,可以把特殊的请求参数转换成需要的类型
4.多种表现层技术,如:JSP、freeMarker、Velocity等
5.Struts2的输入校验可以对指定某个方法进行校验;
6.提供了全局范围、包范围和Action范围的国际化资源文件管理实现
70、ActionContext、ServletContext、pageContext的区别?
1.ActionContext是当前的Action的上下文环境,通过ActionContext可以获取到request、session、ServletContext等与Action有关的对象的引用;
2.ServletContext是域对象,一个web应用中只有一个ServletContext,生命周期伴随整个web应用
3.pageContext是JSP中的最重要的一个内置对象,可以通过pageContext获取其他域对象的应用,同时它是一个域对象,作用范围只针对当前页面,当前页面结束时,pageContext销毁。生命周期是JSP四个域对象中最小的。
71、struts2的执行流程
1.客户端浏览器发出HTTP请求。
2.根据web.xml配置,该请求被FilterDispatcher接收。
3.根据struts.xml配置,找到需要调用的Action类和方法, 并通过IoC方式,将值注入给Aciton。
4.Action调用业务逻辑组件处理业务逻辑,这一步包含表单验证。
5.Action执行完毕,根据struts.xml中的配置找到对应的返回结果result,并跳转到相应页面。
6.返回HTTP响应到客户端浏览器。
72、SpringMVC与Struts2的主要区别
(1)springmvc的入口是一个servlet即前端控制器,而struts2入口是一个filter过虑器。
(2)springmvc是基于方法开发,传递参数是通过方法形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。
(3)Struts采用值栈存储请求和响应的数据,通过OGNL存取数据, springmvc通过参数解析器是将request对象内容进行解析成方法形参,将响应数据和页面封装成ModelAndView对象,最后又将模型数据通过request对象传输到页面。 Jsp视图解析器默认使用jstl。
73、线程同步有七种方式
synchronized关键字
有synchronized关键字修饰的代码块
特殊变量volatile
使用重入锁
使用局部变量实现
使用阻塞队列
使用原子变量
74、Linux下查看文件内容的命令
查看文件内容的命令:
cat 由第一行开始显示内容,并将所有内容输出
tac 从最后一行倒序显示内容,并将所有内容输出
more 根据窗口大小,一页一页的现实文件内容
less 和more类似,但其优点可以往前翻页,而且进行可以搜索字符
head 只显示头几行
tail 只显示最后几行
nl 类似于cat -n,显示时输出行号
tailf 类似于tail -f
75、内连接和外连接的区别
1.内连接,显示两个表中有联系的所有数据;
2.左链接,以左表为参照,显示所有数据;
3.右链接,以右表为参照显示数据;
4.全外连接: 包含左、右两个表的全部行,不管另外一边的表中是否存在与它们匹配的行。
5.交叉连接: 生成笛卡尔积-它不使用任何匹配或者选取条件,而是直接将一个数据源中的每个行与另一个数据源的每个行都一一匹配
76、ArrayList list = new ArrayList(20);中的list扩充几次
0次https://blog.youkuaiyun.com/u014236541/article/details/51211644
77、notify()与notifyAll()的区别
notify():
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
notifyAll():
唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。