java面试整理(一)—— 小问题总结

1.&和&&的区别

在java中,&和&&都可以是逻辑运算符一般这个问题都是问的&&的短路运算符这个点,
也就是说a&&b和a&b的区别,
&&称为短路运算符,也就是说如果a表达式为false(如果是多个表达式&&,则只要遇到false),则整个表达式直接返回false,不在执行后面的表达式,而&这个逻辑运算符则是无论a表达式是否为false,后面的表达式依然会执行,所以在逻辑运算符这块,&&比&执行速度会更快。

当然&这个运算符,在java中还表示按位与运算,即将a和b都转换成二进制进行按位与运算,比如1010&0010 = 0010,在对应位上,只有两个都为1时结果为1,如果两位中出现0,则结果为0.

2.什么叫多态

多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定,即一个引用倒底会指向哪个类的实例对象,该引用发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)

多态存在的三个必要条件:

一、要有继承;
二、要有重写;
三、父类引用指向子类对象

在继承链中对象方法的调用存在一个优先级:this.show(O)(子类中方法)、super.show(O)(父类方法)、this.show((super)O)(子类中含有参数的父类的方法)、super.show((super)O)(父类中含有参数父类的方法)。即先查this对象的父类,没有就重头再查参数的父类

Java中多态的实现方式:接口实现,继承父类进行方法重写,同一个类中进行方法重载。

多态的好处:

1.可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
2.可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
3.接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如图8.3所示。图中超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。
4.灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
5.简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

示例

public class A {    
    public String show(D obj) {    
        return ("A and D");    
    }    

    public String show(A obj) {    
        return ("A and A");    
    }     

}    

public class B extends A{    
    public String show(B obj){    
        return ("B and B");    
    }    

    public String show(A obj){    
        return ("B and A");    
    }     
}    

public class C extends B{    

}    

public class D extends B{    

}    

public class Test {    
    public static void main(String[] args) {    
        A a1 = new A();    
        A a2 = new B();    
        B b = new B();    
        C c = new C();    
        D d = new D();    

        System.out.println("1--" + a1.show(b));    
        System.out.println("2--" + a1.show(c));    
        System.out.println("3--" + a1.show(d));    
        System.out.println("4--" + a2.show(b));  //4--B and A .首先a2是A引用,B实例,调用show(B b)方法,此方法在父类A中没有定义,所以B中方法show(B b)不会调用(多态必须父类中已定义该方法),再按优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O),即先查this对象的父类,没有重头再查参数的父类。查找super.show((super)O)时,B中没有,再向上,找到A中show(A a),因此执行。  

        System.out.println("5--" + a2.show(c));  //同上  
        System.out.println("6--" + a2.show(d));  //A and D .查找B中没有show(D d)方法,再查A中,有,执行。  
        System.out.println("7--" + b.show(b));    
        System.out.println("8--" + b.show(c));  //B and B .  
        System.out.println("9--" + b.show(d));          
    }    
}    

运行结果

 1--A and A    
2--A and A    
3--A and D    
4--B and A    
5--B and A    
6--A and D    
7--B and B    
8--B and B    
9--A and D   

3.Collection 和 Collections的区别

Collection

java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set

Collections

java.util.Collections 是一个包装类(工具类/帮助类)。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,用于对集合中元素进行排序、搜索、取最小值、最大值以及线程安全等各种操作服务于Java的Collection框架

4.GC是什么?为什么要有GC

GC是java的垃圾回收器。

内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的

Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc()Runtime.getRuntime().gc(),但JVM可以屏蔽掉显示的垃圾回收调用。

垃圾回收可以有效的防止内存泄露有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收

回收机制有分代复制垃圾回收标记垃圾回收增量垃圾回收

5.String s = new String(xyz);创建了几个String Object?

首先让我们了解几个概念:

:由JVM分配区域,用于保存线程执行的动作和数据引用(临时数据,线程完成之后失效)。
:由JVM分配的,用于存储对象等数据的区域。
常量池:在中分配出来的一块存储区域,用于存储显式 的String,float或者integer.这是一个特殊的共享区域,可以在内存中共享的不经常改变的东西,都可以放在这里。

String a = “abc”; //代码执行后在常量池中创建了一个值为abc的String对象
String b = “abc”; //执行时,因为常量池中存在”abc”所以就不在创建新的String对象了。
使用String a = “abc”的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象

String c = new String(“xyz”);① //创建二个对象,一个引用 (对象 “xyz” 、对象new String(“xyz”)、String a引用)
String d = new String(“xyz”);② //创建一个对象,并且以后每执行一次创建一个对象,一个引用 (对象new String(“xyz”)、String b引用)

①Class被CLassLoader加载时,你的”xyz”被作为常量读入,在常量池里创建了一个共享的”xyz”对象,然后当调用到new String(“xyz”)的时候,会在里创建这个new String(“xyz”)对象;
②由于常量池l中存在”xyz”所以不再创建”xyz”,然后创建新的new String(“xyz”)对象。

对于String c = new String(“xyz”)与String a = “abc”不同的是:其一定会在在堆中创建新对象,不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。 而String a = “abc”则是直接在常量池中创建对象,如果常量池中已经有相等的对象,便不会在创建,直接使用已有的对象。

6.Math.round(11.5)等於多少? Math.round(-11.5)等於多少?

这个主要考round()方法的知识点:它表示“四舍五入”,其为向上舍入
所以Math.round(11.5) = 12 , Math.round(-11.5) = -11

7.short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?

这个问题主要是看大家对于返回值的理解:

1.精度小雨int的数据计算都会默认转换成int类型(所以即使两个short类型的数据运算也会返回int类型)
2.两个精度不一致的数据运算,返回值类型以精度大的为准(short < int < long < float < double)
3. ‘+=’ 运算符为java语言规定的运算符,其内部会对数据类型进行处理。
4. 高精度数据不能直接赋值给低精度的数据,比如float f = 3.4,是错误的,因为3.4默认是double类型,低精度赋值给高精度没有问题。注:float类型数据如 :3.4f ,long类型数据 如 50L。

所以上面的第一个算式,因为其返回值类型为int ,但是s1是short类型,类型不一致。
第二个算式因为使用的是+=运算符,java内部会对其类型自动转换,所以其能够编译运行成功。

8.public,private,protected

private:修饰的成员变量和函数只能在类本身和内部类中被访问。
protected:修饰的成员变量和函数能被类本身、子类及同一个包中的类访问。
public:修饰的成员变量和函数可以被类、子类、同一个包中的类以及任意其他类访问。
默认情况(不写)下,属于一种包访问,即能被类本身以及同一个包中的类访问。

下面这个表能清楚的说明java中作用域操作符的作用:

作用域 当前类 同一package 子孙类 其他package
public
protected ×
friendly × ×
private × × ×

9.volatile

可以参考这篇博文,这个比较详细。

volatile也是变量修饰符,只能用来修饰变量。volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

两个作用

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即对volatile修饰的变量进行读取、修改操作只能从主存中读取最新的值,并且修改之后必须及时的刷新到主存中。这样新值对其他线程来说是立即可见的。注:虽然volatile保证了对变量的可见性,但是如果对变量的修改操作不能保证原子性,那么volatile就不能保证变量的线程安全
2)一定程度上禁止进行指令重排序。

1. 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2. 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
3. 例:flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。

Java的内存机制
Java使用一个主内存来保存变量当前值,而每个线程则有其独立的工作内存。线程访问变量的时候会将变量的值拷贝到自己的工作内存中,这样,当线程对自己工作内存中的变量进行操作之后,就造成了工作内存中的变量拷贝的值与主内存中的变量值不同。

Java语言规范中指出
为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。

而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。
使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。
由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。

10.char型变量中能不能存贮一个中文汉字?为什么?

是能够定义成为一个中文的,因为java中以unicode编码,一个char占16个字节,所以放一个中文是没问题的

11.几种常用的数据结构及内部实现原理?

链表:一个链表由很多节点组成,每个节点包含表示内容的数据域和指向下一个节点的链域两部分。正是通过链域将节点连接形成一个表。围绕链表的基本操作有添加节点、删除节点和查找节点等。
堆栈:限定了只能从线性表的一端进行数据的存取,这一端称为栈顶,另一端为栈底。 入栈操作是先向栈顶方向移动一位,存入数据;出栈操作则是取出栈顶数据,然后向栈底移动一位。体现LIFO/FILO的思想。
队列:限定了数据只能从线性表的一端存入,从另一端取出。存入一端成为队尾,取出一端称为队首。体现LILO/FIFO的思想。
二叉树:树中的每个节点最多有两个子节点。这两个子节点分别称为左子节点和右子节点。在建立二叉搜索树时,要求一个节点的左子节点的关键字值小于这个节点而右子节点的关

12.java中8大排序

请至少记住一种排序,

参考这篇博文

13.面向对象的特征有哪些方面

抽象:抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。

继承:继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。

封装:封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。

多态性:多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。

14.int 和 Integer 有什么区别

Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int 是 java 的原始数据类型,Integer 是 java 为 int 提供的封装类。Java 为每个原始类型提供了封装类。

引用类型和原始类型的行为完全不同,并且它们具有不同的语义。引用类型和原始类型具有不同的特征和用法,它们包括:大小和速度问题,这种类型以哪种类型的数据结构存储,当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为 null,而原始类型实例变量的缺省值与它们的类型有关。

每个基本类型都有其封装类:
boolean – Boolean
char – Character
byte – Byte
short – Short
int – Integer
long – Long
float – Float
double – Double

15.Overload 和 Override 的区别

方法的重写 Overriding 和重载 Overloading 是 Java 多态性的不同表现。

重写 Overriding 是父类与子类之间多态性的一种表现,重载 Overloading 是一个类中多态性的一种表现。

如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded 的方法是可以改变返回值的类型。

16.error 和 exception 有什么区别

Error(错误):表示系统级的错误程序不必处理的异常,是java运行环境中的内部错误或者硬件问题。比如:内存资源不足等。对于这种错误,程序基本无能为力,除了退出运行外别无选择,它是由Java虚拟机抛出的。

Exception(违例)表示需要捕捉或者需要程序进行处理的异常,它处理的是因为程序设计的瑕疵而引起的问题或者在外的输入等引起的一般性问题,是程序必须处理的

Exception又分为运行时异常,受检查异常。

  • 运行时异常,表示无法让程序恢复的异常,导致的原因通常是因为执行了错误的操作,建议终止程序,因此,编译器不检查这些异常。
  • 受检查异常,是表示程序可以处理的异常,也即表示程序可以修复(由程序自己接受异常并且做出处理), 所以称之为受检查异常。

17.abstract class(抽象类) 和 interface (接口类)有什么区别

声明方法的存在而不去实现它的类被叫做抽象类(abstract class)。它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建 abstract 类的实例。然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类为。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。

接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义 static final 成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。然后,它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。

18.Java 有没有 goto?

java 中的保留字,现在没有在 java 中使用。

19.常见的异常

RuntimeException:运行时出现的异常,下面是其子类:
java面试整理(一)—— 小问题总结_第1张图片
检查异常(Check Exception)
java面试整理(一)—— 小问题总结_第2张图片

20、接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?

接口可以继承接口。
抽象类可以实现(implements)接口,
抽象类是否可继承实体类,主要是看实体类是否有明确的构造函数。

21、数组有没有 length()这个方法? String 有没有 length()这个方法?

数组没有 length()这个方法,有 length 的属性。String 有有 length()这个方法。

22、Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是 equals()? 它们有何区别?

Set 里的元素是不能重复的,那么用 equals()方法来区分重复与否。equals()是判读两个 Set 是否相等。

equals()和==方法决定引用值是否指向同一对象 equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。

== 和 equals比较的都是两个引用中存储的内容是否相同,当引用指向的是基础数据类型是,其存的基础数据类型的值,也就是说int a = 32; a引用里面存储的就是32的值,当指向的是对象时,内部存储的是存储对象的地址,比如new String(“xyz”);其值为xyz,但是其地址却不是这个,我们比作0xadsfsfss,如果我们另外 new String(“xyz”);,其值也是xyz,但是其地址却和上一个不是同一个,我们比作0xsjdhfssd。所以当比较的数据是基础类型时,两个相同的值比较返回时true,但是两个对象比较,其返回值是false。但是两个字符串对象比较返回确实true,这样看来 == 和 equals是一样的,这是因为equals是java提供的一个方法,用于让我们来进行重写,让我们根据自己需要判断两个对象是否相等。String就重写了equals方法,判断字符串对象的值相等时返回true。

23、swtich 是否能作用在 byte 上,是否能作用在 long 上, 是否能作用在 String 上?

switch(expr1)中,expr1 是一个整数表达式。因此传递给 switch 和 case 语句的参数应该是 int、 short、 char 或者 byte(精度小于整数的类型)。long,string 都不能作用于 swtich。java中对于精度小于int的运算都会默认转换成int进行计算。

24.写一个 Singleton(单例) 出来。

Singleton 模式主要作用是保证在 Java 应用程序中,一个类 Class 只有一个实例存在。

单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。

懒汉式单例

//懒汉式单例类.在第一次调用的时候实例化自己   
public class Singleton {  
    private Singleton() {}  
    private static Singleton single = null;  
    //静态工厂方法   
    public static Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
} 

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题:
1、在getInstance方法上加同步

public static synchronized Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
}  

2、双重检查锁定

public static Singleton getInstance() {  
        if (singleton == null) {    
            synchronized (Singleton.class) {    
               if (singleton == null) {    
                  singleton = new Singleton();   
               }    
            }    
        }    
        return singleton;   
    }  

3、静态内部类

public class Singleton {    
    private static class LazyHolder {    
       private static final Singleton INSTANCE = new Singleton();    
    }    
    private Singleton (){}    
    public static final Singleton getInstance() {    
       return LazyHolder.INSTANCE;    
    }    
}    

饿汉式单例

//饿汉式单例类.在类初始化时,已经自行实例化   
public class Singleton1 {  
    private Singleton1() {}  
    private static final Singleton1 single = new Singleton1();  
    //静态工厂方法   
    public static Singleton1 getInstance() {  
        return single;  
    }  
}  

饿汉式和懒汉式区别

从名字上来说,饿汉和懒汉,

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例

另外从以下两点再区分以下这两种方式:

1.、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。

2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

25.描述一下 JVM 加载 class 文件的原理机制?

   Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。

类装载方式,有两种
1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
2.显式装载, 通过class.forname()等方法,显式加载需要的类

Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

Java的类加载器有三个,对应Java的三种类:(java中的类大致分为三种: 1.系统类 2.扩展类 3.由程序员自定义的类 )

Bootstrap Loader // 负责加载系统类 (指的是内置类,像是String,对应于C#中的System类和C/C++标准库中的类)
|
- - ExtClassLoader // 负责加载扩展类(就是继承类和实现类)
|
- - AppClassLoader // 负责加载应用类(程序员自定义的类)

三个加载器各自完成自己的工作,但它们是如何协调工作呢?哪一个类该由哪个类加载器完成呢?为了解决这个问题,Java采用了委托模型机制。

委托模型机制的工作原理很简单:当类加载器需要加载类的时候,先请示其Parent(即上一层加载器)在其搜索路径载入,如果找不到,才在自己的搜索路径搜索该类。这样的顺序其实就是加载器层次上自顶而下的搜索,因为加载器必须保证基础类的加载。之所以是这种机制,还有一个安全上的考虑:如果某人将一个恶意的基础类加载到jvm,委托模型机制会搜索其父类加载器,显然是不可能找到的,自然就不会将该类加载进来。

我们可以通过这样的代码来获取类加载器:

ClassLoader loader = ClassName.class.getClassLoader();
ClassLoader ParentLoader = loader.getParent();

注意一个很重要的问题,就是Java在逻辑上并不存在BootstrapKLoader的实体!因为它是用C++编写的,所以打印其内容将会得到null。

前面是对类加载器的简单介绍,它的原理机制非常简单,就是下面几个步骤:

1.装载:查找和导入class文件;
2.连接:

  -  检查:检查载入的class文件数据的正确性;
  -  准备:为类的静态变量分配存储空间;
  -  解析:将符号引用转换成直接引用(这一步是可选的)

3.初始化:初始化静态变量,静态代码块。
这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类的构造器也会引发该动作。

Java 详解 JVM 工作原理和流程

26.简述 synchronized 和 java.util.concurrent.locks.Lock 的异同 ?

主要相同点:Lock 能完成 synchronized 所实现的所有功能
主要不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能。synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且必须在 finally 从句中释放。

27.什么是 java 序列化,如何实现 java 序列化?

可以参考文章

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。用于将对象序列化为二进制文件,可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题

序列化的实现:将需要被序列化的类实现 Serializable 接口,该接口没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流。

28.String,StringBuilder,StringBuffer三者的区别

String 是一个字符串常量,也就是说我们每次新建一个String常量,那么JVM则会新建一个String对象,如果我们使用String对象进行运算,那么其会创建多个对象,比如:

String str = "abc" + "dv";

大家可以说下上面的语句一共创建了几个对象吗?答案最多可能是三个对象和一个引用:对象”abc”、对象”dv” 、对象”abcdv”和引用str,之所以说是最多,是因为常量池中可能存在这几个字符串常量,存在的话就不会再创建了。当然如果我们使用new String()创建,那么就必定创建三个对象了。因为String的这个机制,所以其比StringBuilder,StringBuffer运算是要慢的

StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。我们可以使用其对象不停的对字符串进行修改,而不用在创建其他对象,只需要我们在最后转换成字符串对象即可。

而StringBuilder,StringBuffer他们的区别则在于:StringBuilder是线程不安全的,而StringBuffer是线程安全的

所以:
String:适用于少量的字符串操作的情况
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

29.用最有效率的方法算出2乘以8等於几

2 << 3

<< : 左移位运算符,将数值的二进制码向左移动,后面补0,移动一位相当于*2,上面的2<<3,表示左移位三位,也就是2*2*2*2 = 16 ,也就是2*8。二进制为0000 0010 -> 0001 0000

30.两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。

Java对于eqauls方法和hashCode方法是这样规定的:
(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同
(2)如果两个对象的hashCode相同,它们并不一定相同

当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。

补充:关于equals和hashCode方法,很多Java程序都知道,但很多人也就是仅仅知道而已,在Joshua Bloch的大作《Effective Java》(很多软件公司,《Effective Java》、《Java编程思想》以及《重构:改善既有代码质量》是Java程序员必看书籍,如果你还没看过,那就赶紧去亚马逊买一本吧)中是这样介绍equals方法的:

首先equals方法必须满足:

自反性(x.equals(x)必须返回true)
对称性(x.equals(y)返回true时,y.equals(x)也必须返回true)
传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true)
一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值),而且对于任何非null值的引用x,x.equals(null)必须返回false

实现高质量的equals方法的诀窍包括:

  1. 使用==操作符检查”参数是否为这个对象的引用”;
  2. 使用instanceof操作符检查”参数是否为正确的类型”;
  3. 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;
  4. 编写完equals方法后,问自己它是否满足对称性、传递性、一致性;
  5. 重写equals时总是要重写hashCode;
  6. 不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉@Override注解。

31.JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗?

Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它是Throwable类或其它子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并进行处理。Java的异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally。一般情况下是用try来执行一段程序,如果出现异常,系统会抛出(throws)一个异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理。

try用来指定一块预防所有”异常”的程序。紧跟在try程序后面,应包含一个catch子句来指定你想要捕捉的”异常”的类型。
throw语句用来明确地抛出一个”异常”。
throws用来标明一个成员函数可能抛出的各种”异常”。
Finally为确保一段代码不管发生什么”异常”都被执行一段代码。

可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部写另一个try语句保护其他代码。每当遇到一个try语句,”异常”的框架就放到堆栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种”异常”进行处理,堆栈就会展开,直到遇到有处理这种”异常”的try语句。

32.一个”.java”源文件中是否可以包括多个类(不是内部类)?有什么限制?

可以。但必须只有一个类名与文件名相同。

33、说出一些常用的类,包,接口,请各举5个

答:常用的类:BufferedReader BufferedWriter FileReader FileWirter String Integer
常用的包:java.lang java.awt java.io java.util java.sql
常用的接口:Remote List Map Document NodeList

34.如果系统要使用超大整数(超过long长度范围),请你设计一个数据结构来存储这种超大型数字以及设计一种算法来实现超大整数加法运算)

请参考这篇博文

35.abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?

都不能
  
三个关键字说明

  1. abstract是抽象的,指的是方法只有声明而没有实现,他的实现要放入声明该类的子类中实现。
  2. static是静态的,是一种属于类而不属于对象的方法或者属性
  3. synchronized 是同步,是一种相对线程的锁。
  4. native 本地方法,这种方法和抽象方法及其类似,它也只有方法声明,没有方法实现,但是它与抽象方法不同的是,它把具体实现移交给了本地系统的函数库,而没有通过虚拟机,可以说是Java与其它语言通讯的一种机制。
  5. 那么我们就来谈谈这些关键字为什么不能和abstract混用。

首先abstract与static,声明static说明可以直接用类名调用该方法;声明abstract说明需要子类重写该方法;如果同时声明static和abstract,用类名调用一个抽象方法肯定不行。

synchronized 是同步,然而同步是需要有具体操作才能同步的,如果像abstract只有方法声明,那同步一些什么东西就会成为一个问题了,当然抽象方法在被子类继承以后,可以添加同步。

native,这个东西本身就和abstract冲突,他们都是方法的声明,只是一个吧方法实现移交给子类,另一个是移交给本地操作系统,如果同时出现,就相当于即把实现移交给子类,又把实现移交给本地操作系统,那到底谁来实现具体方法呢!

不能放在一起的修饰符:final和abstract,private和abstract,static和abstract,因为abstract修饰的方法是必须在其子类中实现(覆盖),才能以多态方式调用,以上修饰符在修饰方法时期子类都覆盖不了这个方法,final是不可以覆盖,private是不能够继承到子类,所以也就不能覆盖,static是可以覆盖的,但是在调用时会调用编译时类型的方法,因为调用的是父类的方法,而父类的方法又是抽象的方法,又不能够调用,所以上的修饰符不能放在一起。

36.${}和#{}的区别

在这里用到了#{},使用#时:
1、用来传入参数,sql在解析的时候会加上” “,当成字符串来解析 ,如这里 role_id = “roleid”;
2、#{}能够很大程度上防止sql注入;

延伸:
1、用${}传入数据直接显示在生成的sql中,如上面的语句,用role_id = ${roleId,jdbcType=INTEGER},那么sql在解析的时候值为role_id = roleid,执行时会报错;
2、${}方式无法防止sql注入;
3、$一般用入传入数据库对象,比如数据库表名;
4、能用#{}时尽量用#{};

注意:
mybaties排序时使用order by 动态参数时需要注意,使用${}而不用#{};

37.MVC的各个部分都有那些技术来实现?如何实现?

答:MVC是Model-View-Controller的简写。”Model” 代表的是应用的业务逻辑(通过JavaBean,EJB组件实现), “View” 是应用的表示面(由JSP页面产生),”Controller” 是提供应用的处理过程控制(一般是一个Servlet),通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。

38.Spring MVC请求流程

参考这篇博文

39.Spring有哪些优点?

轻量级:Spring在大小和透明性方面绝对属于轻量级的,基础版本的Spring框架大约只有2MB。
控制反转(IOC):Spring使用控制反转技术实现了松耦合。依赖被注入到对象,而不是创建或寻找依赖对象。
面向切面编程(AOP): Spring支持面向切面编程,同时把应用的业务逻辑与系统的服务分离开来。
容器:Spring包含并管理应用程序对象的配置及生命周期。
MVC框架:Spring的web框架是一个设计优良的web MVC框架,很好的取代了一些web框架。
事务管理:Spring对下至本地业务上至全局业务(JAT)提供了统一的事务管理接口。
异常处理:Spring提供一个方便的API将特定技术的异常(由JDBC, Hibernate, 或JDO抛出)转化为一致的、Unchecked异常。

40.解释核心容器(应用上下文)模块

这是Spring的基本模块,它提供了Spring框架的基本功能。BeanFactory是所有Spring应用的核心。Spring框架是建立在这个模块之上的,这也使得Spring成为一个容器。

41.Servlet的生命周期

Servlet运行在Servlet容器中,其生命周期由容器来管理。

Servlet的生命周期通过javax.servlet.Servlet接口中的init()、service()和destroy()方法来控制。

Servlet的生命周期包含了下面4个阶段:

(1)加载和实例化:Servlet容器负责加载和实例化Servlet。当Servlet容器启动时或者在容器检测到需要这个Servlet来响应第一个请求时,创建Servlet实例。当Servlet容器启动后,它必须要知道所需的Servlet类在什么位置,Servlet容器可以从本地文件系统、远程文件系统或者其他的网络服务中通过类加载器加载Servlet类,成功加载后,容器创建Servlet的实例。因为容器是通过Java的反射API来创建Servlet实例,调用的是Servlet的默认构造方法(即不带参数的构造方法),所以我们在编写Servlet类的时候,不应该提供带参数的构造方法。

(2)初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象。初始化的目的是为了让Servlet对象在处理客户端请求前完成一些初始化的工作,如建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只被调用一次。在初始化期间,Servlet实例可以使用容器为它准备的ServletConfig对象从Web应用程序的配置信息(在web.xml中配置)中获取初始化的参数信息。在初始化期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常来通知容器。ServletException异常用于指明一般的初始化失败,例如没有找到初始化参数;而UnavailableException异常用于通知容器该Servlet实例不可用。例如,数据库服务器没有启动,数据库连接无法建立,Servlet就可以抛出UnavailableException异常向容器指出它暂时或永久不可用。

(3)请求处理:Servlet容器调用Servlet的service()方法对请求进行处理。要注意的是,在service()方法调用之前,init()方法必须成功执行。在service()方法中,Servlet实例通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。在service()方法执行期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常。如果UnavailableException异常指示了该实例永久不可用,Servlet容器将调用实例的destroy()方法,释放该实例。此后对该实例的任何请求,都将收到容器发送的HTTP 404(请求的资源不可用)响应。如果UnavailableException异常指示了该实例暂时不可用,那么在暂时不可用的时间段内,对该实例的任何请求,都将收到容器发送的HTTP 503(服务器暂时忙,不能处理请求)响应。

(4)服务终止:当容器检测到一个Servlet实例应该从服务中被移除的时候,容器就会调用实例的destroy()方法,以便让该实例可以释放它所使用的资源,保存数据到持久存储设备中。当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。如果再次需要这个Servlet处理请求,Servlet容器会创建一个新的Servlet实例。在整个Servlet的生命周期过程中,创建Servlet实例、调用实例的init()和destroy()方法都只进行一次,当初始化完成后,Servlet容器会将该实例保存在内存中,通过调用它的service()方法,为接收到的请求服务。

面试时记住以下几点就OK:实例化,初始化,service调用,然后destory销毁

42.什么是面向切面编程AOP?

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为”横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

我们可以先了解几个AOP的相关概念:

切面
定义:切面是通知切点的集合,通知和切点共同定义了切面的全部功能——它是什么,在何时何处完成其功能。你可以把它想象成一个我们抽出来用于处理公共逻辑的一个类,其内部含有通知、切点、处理逻辑等。

连接点
定义:连接点是一个应用执行过程中能够插入一个切面的。也就是我们什么地方可以从这个点进入切面,并执行里面的逻辑。连接点可以是调用方法时、抛出异常时、甚至修改字段时、切面代码可以利用这些点插入到应用的正规流程中,程序执行过程中能够应用通知的所有点。

通知
定义:切面也需要完成工作。在 AOP 术语中,切面的工作被称为通知。
工作内容:通知定义了切面是什么(公共处理逻辑是什么)以及何时使用(什么时候运行这些处理逻辑)。除了描述切面要完成的工作,通知还解决何时执行这个工作。

Spring 切面可应用的 5 种通知类型:

  • Before——在方法调用之前调用通知
  • After——在方法完成之后调用通知,无论方法执行成功与否
  • After-returning——在方法执行成功之后调用通知
  • After-throwing——在方法抛出异常后进行通知
  • Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

切点
定义:如果通知定义了“什么”和“何时”。那么切点就定义了“何处”。切点会匹配通知所要织入的一个或者多个连接点。通常使用明确的类或者方法来指定这些切点。注:切点和连接点的关系在于:切点主要用于定义什么范围内的连接点可以进入该切面,比如说都是一个包范围内的所有连接点、一个类下面的连接点、具有某个注解的连接点,代码上可以看:@Pointcut(value = "@annotation(com.binggou.fa.core.common.annotion.BussinessLog)")pointcut="execution(* com.binggou.cloud.api.controller..*.*(..)) && @annotation(logAnnotation)"上面两个第一个是具有BussinessLog注解的方法,第二个是controller包下所有的类的所有方法中含有logAnnotation注解的方法都可以进入切面逻辑。那么注解了这些注解的地方就是连接点。
作用:定义通知被应用的位置(在哪些连接点)

引入
不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

织入
织入是将切面应用到目标对象来创建的代理对象过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有多个点可以织入。

从上面的几个定义中,我想大家应该有个大概的AOP的概念了,就是说AOP允许我们可以在我们的应用程序中将公共处理逻辑部分单独拉出来定义一个切面(其实就是一个类,用于处理一些公共逻辑),然后在需要切面逻辑的地方定义连接点,来进行解耦操作。AOP切面中的异常并不影响主处理逻辑的返回结果,当然如果在切面中对返回信息进行处理就另说了。

Spring 对 AOP 的支持
并不是所有的 AOP 框架都是一样的,他们在连接点模型上可能有强弱之分。 有些允许对字段修饰符级别应用通知,有些支持方法调用连接点

Spring 提供的 4 种各具特色的 AOP 支持:
1)基于代理的经典 AOP;
2)@AspectJ 注解驱动的切面;
3)纯 POJO 切面;
4)注入式 AspectJ 切面; Spring

在运行期间通知对象
通过在代理类中织入包裹切面,Spring 在运行期间将切面织入到 Spring 管理的 Bean 中。 代理类封装了目标类,并拦截被通知的方法调用,再将调用转发给真正的目标 Bean。Spring代理机制 当拦截到方法调用时,在调用目标 Bean 方法之前,代理会执行切面逻辑。 当真正应用需要被代理的 Bean 时,Spring 才创建代理对象。如果使用 ApplicationContext,在 ApplicationContext 从 BeanFactory 中加载所有 Bean 时,Spring 创建代理对象,因为 Spring 在运行时候创建代理对象,所以我们不需要特殊的编译器来织入 Spring AOP 的切面。

Spring 支持方法创建连接点
因为 Spring 基于动态代理,所以 Spring 只支持方法连接点。
Spring 缺失对字段连接点的支持,无法让我们更加细粒度的通知,例如拦截对象字段的修改
Spring 缺失对构造器连接点支持,无法在 Bean 创建时候进行通知。

Spring AOP的实现原理

43.Spring IoC底层运行原理?

IoC,直观地讲,就是容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。这也就是所谓“控制反转”:程序中对象的获取方式发生反转,由最初的new方式创建,转变为由第三方框架创建、注入,第三方框架一般通过配置方式指定注入哪一个具体实现,从而降低了对象之间的耦合度

IoC的主要实现技术途径:“依赖注入(Dependency Injection)”:就是将两个具有依赖关系的对象,通过构造方法或方法参数传入建立联系,因此容器的工作就是创建bean时,注入哪些依赖关系

简单的说,原本我们的的应用程序如果想要引用其他的类,我们就需要告知容器或框架,让它们找到自身所需要的类,然后在代码中创建待使用的类的对象实例。因此,应用代码在使用实例之前,需要创建对象实例。然而,IoC模式中,创建对象实例的任务交给IoC容器或框架(实现了IoC设计模式的框架也被称为IoC容器),所以我们可以在应用程序中直接使用实例,而不再需要管理对象

相对IoC而言,“依赖注入”的确更加准确的描述了这种设计理念。所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。任何重要的系统都需要至少两个相互合作的类来完成业务逻辑。通常,每个对象都要自己负责得到它的合作(依赖)对象。你会发现,这样会导致代码耦合度高而且难于测试。使用IoC,对象的依赖都是在对象创建时由负责协调系统中各个对象的外部实体提供的,这样使软件组件松散连接成为可能。

Spring IOC负责创建对象管理对象(通过依赖注入)、整合对象配置对象以及管理这些对象的生命周期

两种依赖注入

构造器依赖注入:通过调用带有参数的构造方器来实现,容器在实例化bean对象时,根据参数类型执行相应的构造器,构造器注入可以强制给bean注入某些参数,比setter注入严格。
形如:

"phone" class = "*.*.ClassName">
    "0" value="ARM"/>
    "1" value="ARM"/>

Setter方法依赖注入:首先容器会调用一个无参构造函数或无参静态工厂方法实例化bean对象,之后容器调用bean中的setter方法完成Setter方法依赖注入。
形如:

id = "phone" class = "*.*.ClassName">
    <property name ="age" value = "30"/>

setter注入优点:

  1. setter注入需要该Bean包含这些属性的setter方法
  2. 与传统的JavaBean的写法更相似,程序开发人员更容易理解、接收。通过setter方法设定依赖关系显得更加只管。
  3. 对于复杂的依赖关系,如果采用构造注入,会导致构造器国语臃肿,难以阅读。Spring在创建Bean实例时,需要同时实例化器依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题
  4. 尤其是在某些属性可选的情况况下,多参数的构造器显得更加笨重

构造注入优点:

  1. 构造注入需要该Bean包含带有这些属性的构造器
  2. 构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。例如,组件中其他依赖关系的注入,常常要依赖于DataSrouce的注入。采用构造注入,可以在代码中清晰的决定注入顺序。
  3. 对于依赖关系无需变化的Bean,构造注入更有用处。因为没有Setter方法,所有的依赖关系全部在构造器内设定。因此,无需担心后续的代码对依赖关系产生破坏。
  4. 依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。

两种注入的选择:
你可以同时使用两种方式的依赖注入,最好的选择是使用构造器参数实现强制依赖注入,使用setter方法实现可选的依赖关系

IOC优点
IOC和依赖注入减少了应用程序的代码量。它使得应用程序的测试很简单,因为在单元测试中不再需要单例或JNDI查找机制。简单的实现以及较少的干扰机制使得松耦合得以实现。IOC容器支持勤性单例及延迟加载服务

44.解释抽象JDBC和DAO模块

通过使用抽象JDBC和DAO模块保证了与数据库连接代码的整洁与简单,同时避免了由于未能关闭数据库资源引起的问题。它在多种数据库服务器的错误信息之上提供了一个很重要的异常层。它还利用Spring的AOP模块为Spring应用程序中的对象提供事务管理服务。

45. Spring bean的作用域

定义

Spring Beans是构成Spring应用核心的Java对象。这些对象由Spring IOC容器实例化、组装、管理。这些对象通过容器中配置的元数据创建,例如,使用XML文件中定义的创建。在spring中,任何的java类和java bean都被当做Bean处理,这些bean通过容器来管理和应用.

javaBean:一种简单规范的java对象,其含有描述对象的一些属性,并提供设置和获取属性的set和get方法的一种类。

在Spring中创建的beans都是单例的beans。在bean标签中有一个属性为”singleton”,如果设为true,该bean是单例的,如果设为false,该bean是原型bean。Singleton属性默认设置为true。因此,spring框架中所有的bean都默认为单例bean。

实例化
Spring容器创建Bean对象的方法有三种:

构造器实例化:形如
id或那么属性用于指定Bean名称,用于从Spring中查找这个Bean对象的标识
class用于指定Bean类型,会自动调用无参构造器创建对象

静态工厂实例化 : 形如
factory-method用于指定工厂中创建Bean对象的方法,必须使用static修饰的方法,Spring**会自动调用静态方法进行对象的实例化**

实例工厂实例化 : 形如 ,
factory-bean 用于指定工厂的bean对象。
这里定义了两个bean,其中test用于创建test1对象的实例工厂,另外一个bean标记中id属性test1用于定义bean名称,Spring会自动调用工厂类的getInstance方法创建bean对象实例

作用域

Spring 中为Bean定义了5中作用域,分别为singleton(单例)、prototype(原型)、request、session和global session,5种作用域说明如下:

singleton:单例模式,Spring IoC容器中只会存在一个共享的Bean实例,无论有多少个Bean引用它,始终指向同一对象。Singleton作用域是Spring中的缺省作用域,默认作用域,也可以显式的将Bean定义为singleton模式,配置为:

"userDao" class="com.ioc.UserDaoImpl" scope="singleton"/>

prototype:原型模式,每次通过Spring容器获取prototype定义的bean时,容器都将创建一个新的Bean实例,每个Bean实例都有自己的属性和状态,而singleton全局只有一个对象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。

request:在一次Http请求中,容器会返回该Bean的同一实例。而对不同的Http请求则会产生新的Bean,而且该bean仅在当前Http Request内有效。

"loginAction" class="com.cnblogs.Login" scope="request"/>

针对每一次Http请求,Spring容器根据该bean的定义创建一个全新的实例,且该实例仅在当前Http请求内有效,而其它请求无法看到当前请求中状态的变化,当当前Http请求结束,该bean实例也将会被销毁。
session:在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。

"userPreference" class="com.ioc.UserPreference" scope="session"/>

同Http请求相同,每一次session请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的session请求内有效,请求结束,则实例将被销毁。
global Session:在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效。

spring bean生命周期
1. 实例化一个Bean,也就是我们通常说的new
2. 按照Spring上下文对实例化的Bean进行配置,也就是IOC注入
3. 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的ID
4. 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(),传递的是Spring工厂本身(可以用这个方法获取到其他Bean)
5. 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文,该方式同样可以实现步骤4,但比4更好,以为ApplicationContext是BeanFactory的子接口,有更多的实现方法
6. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用After方法,也可用于内存或缓存技术
7. 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法
8. 如果这个Bean关联了BeanPostProcessor接口,将会调用postAfterInitialization(Object obj, String s)方法
注意:以上工作完成以后就可以用这个Bean了,那这个Bean是一个single的,所以一般情况下我们调用同一个ID的Bean会是在内容地址相同的实例
9. 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean接口,会调用其实现的destroy方法
10. 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法

46.Spring框架中单例beans是线程安全的吗?

不是,Spring框架中的单例beans不是线程安全的。

Spring框架并没有对单例bean进行任何多线程的封装处理,但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。

最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。

47.什么是Spring的内部bean?

当一个bean被用作另一个bean的属性时,这个bean可以被声明为内部bean。在基于XML的配置元数据中,可以通过把元素定义在 或元素内部实现定义内部bean。内部bean总是匿名的并且它们的scope总是prototype

48.bean自动装配(autowire)

参考

前面我们已经说了自动注入,依赖注入的本质就是装配,装配是依赖注入的具体行为。

首先,确定一下装配的概念:创建应用对象之间协作关系的行为称为装配。也就是说当一个对象的属性是另一个对象时,实例化bean时,需要为这个对象属性进行实例化。这就是装配。在传统的依赖注入配置中,我们必须要明确要给bean的属性装配哪一个bean的引用,一旦bean很多,就不好维护了。基于这样的场景,spring使用注解来进行自动装配,Spring容器可以自动配置相互协作beans之间的关联关系。这意味着Spring可以自动配置一个bean和其他协作bean之间的关系,通过检查BeanFactory 的内容里没有使用和< property>相同的元素。Spring会自动通过setter方法将其装配
配置如:

id="student" class="*.*.student" autowire = "byType"/>

注意:自动装配功能和手动装配要是同时使用,那么自动装配就不起作用。

自动装配模式
下列自动装配模式,它们可用于指示 Spring 容器为来使用自动装配进行依赖注入。你可以使用 元素的 autowire 属性为一个 bean 定义指定自动装配模式。

模式 描述
no 这是默认的设置,它意味着没有自动装配,你应该使用显式的bean引用来连线。你不用为了连线做特殊的事。在依赖注入章节你已经看到这个了。
byName 属性名自动装配。如果一个bean的名称与其他bean属性的名称是一样的,那么将自动装配它。
byType 属性数据类型自动装配。如果一个bean的数据类型与其它bean属性的数据类型相同,将自动兼容装配它。注:在类型的自动装配模式,就必须确保Bean 只有一个唯一的数据类型声明。也就是说,在Spring容器中不能有两个相同数据类型的Bean。
constructor 类似于 byType,但该类型适用于构造函数参数类型。通过构造函数参数的数据类型按属性自动装配Bean。 这意味着,如果一个bean的数据类型与其他bean的构造器参数的数据类型是相同的,那么将自动装配。
autodetect Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。

可以使用 byType 或者 constructor 自动装配模式来连接数组和其他类型的集合。

自动装配的局限性
当自动装配始终在同一个项目中使用时,它的效果最好。如果通常不使用自动装配,它可能会使开发人员混淆的使用它来连接只有一个或两个 bean 定义。不过,自动装配可以显著减少需要指定的属性或构造器参数,但你应该在使用它们之前考虑到自动装配的局限性和缺点。

限制 描述
重写的可能性 你可以使用总是重写自动装配的 设置来指定依赖关系。
原始数据类型 你不能自动装配所谓的简单类型包括基本类型,字符串和类。
混乱的本质 自动装配不如显式装配精确,所以如果可能的话尽可能使用显式装配。

49.你可以在Spring中注入null或空字符串吗?

完全可以。
Spring注入null值可以使用

50.什么是组件扫描

指定一个包路径,Spring会自动扫描该包及其子包中所有的组件类,当发现组件类定义前有特定的注解标记时,就将该组件纳入到Spring容器。等价于原有XML配置中的定义功能

组件扫描可以替代大量XML配置的bean定义

51.重定向和转发的区别

转发 重定向
转发在服务器端完成的 重定向是在客户端完成的
转发的速度快 重定向速度慢
转发的是同一次请求 重定向是两次不同请求
转发不会执行转发后的代码 重定向会执行重定向之后的代码
转发地址栏没有变化 重定向地址栏有变化
转发必须是在同一台服务器下完成 重定向可以在不同的服务器下完成

转发是在服务器端的跳转,就是客户端一个请求发给服务器,服务器直接将请求相关的参数的信息原封不动的传递到该服务器的其他jsp或servlet去处理,而重定向是在客户端的跳转,服务器会返回给客户端一个响应报头和新的URL地址,原来的参数什么的信息如果服务器端没有特别处理就不存在了,浏览器会访问新的URL所指向的servlet或jsp,这可能不是原先服务器上的webservce了。

52.jsp中,session对象存在哪个地方,如何进行存取,存在服务器哪个地方,如何存的?

session在何时被创建

一 个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用 HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 <% @page session=”false”%> 关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句HttpSession session=HttpServletRequest.getSession(true);这也是JSP中隐含的 session对象的来历。由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。

session何时被删除
综合前面的讨论,session在下列情况下被删除
a.程序调用HttpSession.invalidate();
b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;
c.服务器进程被停止(非持久session)

如何做到在浏览器关闭时删除session
严格的讲,做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。

有个HttpSessionListener是怎么回事
你可以创建这样的listener去监控session的创建和销毁事件,使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener,而不是相反。类似的与HttpSession有关的listener还有HttpSessionBindingListener,HttpSessionActivationListener和 HttpSessionAttributeListener。

存放在session中的对象必须是可序列化的吗
不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在WeblogicServer的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果 session中有不可序列化的对象,在session销毁时会有一个Exception,很奇怪。

53.什么是事务控制?

事务控制的作用是:保证数据的一致性和完整性。事务控制就是将一系列操作当成一个不可拆分的逻辑单元,保证这些操作要么都成功,要么都失败。在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。事务是恢复和并发控制的基本单位。

事务应该具有4个属性:原子性、一致性、隔离性、持续性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持续性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

事务有两种操作:提交,回滚

深入理解Spring事务原理

理解事务的4种隔离级别

分布式事务

54.过滤器的作用?

过滤器是一个对象,可以传输请求或修改响应。它可以在请求到达Servlet/JSP之前对其进行预处理,而且能够在响应离开Servlet/JSP之后对其进行后处理。所以如果你有几个Servlet/JSP需要执行同样的数据转换或页面处理的话,你就可以写一个过滤器类,然后在部署描述文件(web.xml)中把该过滤器与对应的Servlet/JSP联系起来。你可以一个过滤器以作用于一个或一组servlet,零个或多个过滤器能过滤一 个或多个servlet。一个过滤器实现java.servlet.Filter接口并定义它的三个方法:1. void init(FilterConfig config) throws ServletException:在过滤器执行service前被调用,以设置过滤器的配置对象。2. void destroy();在过滤器执行service后被调用。3. Void doFilter(ServletRequest req,ServletResponse res,FilterChain chain) throws IOException,ServletException;

55.线程的基本概念、线程的基本状态以及状态之间的关系

什么是线程

一个线程是进程的一个顺序执行流。同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。一个进程中可以包含多个线程。

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。Java在语言层面对多线程提供了卓越的支持,它也是一个很好的卖点。

进程与线程的区别

线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。

从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用来实现进程的调度和管理以及资源分配。

并发原理
多个线程或进程”同时”运行只是我们感官上的一种表现。事实上进程和线程是并发运行的,OS的线程调度机制将时间划分为很多时间片段(时间片),尽可能均匀分配给正在运行的程序,获取CPU时间片的线程或进程得以被执行,其他则等待。而CPU则在这些进程或线程上来回切换运行。微观上所有进程和线程是走走停停的,宏观上都在运行,这种都运行的现象叫并发,但是不是绝对意义上的“同时发生。

线程状态

1.新建
new语句创建的线程对象处于新建状态,此时它和其他java对象一样,仅被分配了内存。

2.等待
当线程在new之后,并且在调用start方法前,线程处于等待状态

3.就绪
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态。处于这个状态的线程位于Java虚拟机的可运行池中,等待cpu的使用权。

4.运行状态
处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU,那么任何时刻只会有一个线程处于这个状态。
只有处于就绪状态的线程才有机会转到运行状态。

5.阻塞状态
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才会有机会获得运行状态。

阻塞状态分为三种:

1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
2、同步阻塞:运行的线程在获取对象同步锁时,若该同步锁被别的线程占用,则JVM会把线程放入锁池中。
3、其他阻塞:运行的线程执行Sleep()方法,或者发出I/O请求时,JVM会把线程设为阻塞状态。当Sleep()状态超时、或者I/O处理完毕时,线程重新转入就绪状态。

6.死亡状态
当线程执行完run()方法中的代码,或者遇到了未捕获的异常,就会退出run()方法,此时就进入死亡状态,该线程结束生命周期。

56.多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?

多线程有两种实现方式:继承Thread类实现Runnable接口

public class Demo2_Thread {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();  // 注意调用的start()方法,而不是run()方法
        for(int i = 0; i < 1000; ++i)
            System.out.println("222");
    }
}
class MyThread extends Thread{    // 继承Thread类
    @Override
    public void run() {          // 重写run方法
        for(int i = 0; i < 1000; ++i)    // 将将要执行的代码写到run方法中
            System.out.println("1111111111");

    }
}
public class Demo3_Thread {

    public static void main(String[] args) {
        new Thread(new MyRunnable()).start();
        for(int i = 0; i < 1000; ++i)
            System.out.println("bb");
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i = 0; i < 1000; ++i)
            System.out.println("aaaaaaaaaaaaaaaaa");
    }

}

两种方式的区别
1、继承Thread:由于子类重写父类的run(),当调用start()时,直接找子类的run()方法
2、实现Runnable接口:Thread的构造函数中传入了Runnable引用,成员变量记住它,start()调用Thread中的run()方法时,判断成员变量Runnable的引用是否为空,不为空则在Thread的run()方法中调用Runnable的run()方法。编译看Runnable的run(),运行看子类run()方法。
继承Thread:(首选这个,如果有了父类就用接口)
好处是:直接使用Thread类中的方法,代码简单
弊端是:如果已经有了父类,就不能使用这种方法
实现Runnable接口:
好处是:即使自己定义的线程类有了父类也没有关系,因为有了父类也可以实现接口,而接口是可以多实现的
弊端是:不能直接使用Thread类中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂

注:线程的实现方式还有一种:实现callable接口,重写call方法
Callable接口实际上是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更加强大的功能。

  • Callable可以在任务结束的时候提供一个返回值,Runnable无法提供这个功能
  • Callable的call方法可以抛出异常,而Runnable的run方法不能抛出异常。
public class CallableAndFuture {
    static class MyThread implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "Hello world";
        }
    }

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        Future future = threadPool.submit(new MyThread());

        try {
            System.out.println(future.get());
        } catch (Exception e) {

        } finally {
            threadPool.shutdown();
        }
    }
}

同步的实现方式

1.同步方法
即有synchronized关键字修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

2.同步代码块
即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
代码如:

    synchronized(object){}

注:同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

3.使用特殊域变量(volatile)实现线程同步

1. volatile关键字为域变量的访问提供了一种免锁机制, 
2. 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新, 因此每次使用该域就要重新从主内存取,而不是使用本地的缓存值
3. volatile不会提供任何原子操作,它也不能用来修饰final类型的变量 

4.使用重入锁实现线程同步
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用
5.使用线程局部变量ThreadLocal实现同步
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,只是名字相同而已。所以就会发生上面的效果。可以参考这篇博文

57.sleep() 和 wait() 的区别

这两个都是线程的方法,用于暂停线程的运行的功能。但是虽然都是暂停线程,但是其运行逻辑完全不同。他们最大本质的区别是:sleep()不释放同步锁,wait()释放同步锁

sleep()方法继承与Thread类的静态方法,让调用线程进入睡眠状态。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。其唤醒方法有两种,一种是设置睡眠时间,时间到后,会自动唤醒,另外一种是直接使用interreput()方法强制打断睡眠状态。sleep()可以在任何地方使用

wait()是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到直到超时或其他线程调用同一个对象的notify()、notifyAll()方法才会重新激活调用者。其用于暂时当前线程的执行,并释放其持有的同步锁,以及释放其占有的CPU片段。当其重新激活后,会同其他线程一起竞争CPU,而不是直接获取CPU的控制权。wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用

58.启动一个线程是用 run()还是 start()?

start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。

run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。
记住:多线程就是分时利用CPU,宏观上让所有线程一起执行 ,也叫并发

59.为什么要引入连接池?

在Web开发中,如果使用 JDBC连接数据库,那么每次访问请求都必须建立连接——打开数据库——存取数据库——关闭连接等一系列步骤。但是我们知道数据库的连接打开不仅费时,而且消耗比较多的系统资源。如果进行数据库操作的次数比较少,那么还不至于有多大的影响,但是假如频繁的进行数据库操作,那么系统的性能将会受到很大影响。其次,是造成数据库连接泄漏。数据库连接泄漏这个词是第一次听说,指的是如果在某次使用或者某段程序中没有正确地关闭Connection、Statement和ResultSet资源,那么每次执行都会留下一些没有关闭的连接,这些连接失去了引用而不能得到重新使用,因此就造成了数据库连接的泄漏。数据库连接的资源是宝贵而且是有限的,如果在某段使用频率很高的代码中出现这种泄漏,那么数据库连接资源将被耗尽,影响系统的正常运转。为了解决上述问题,因此就引入了数据库连接池技术。用一句话概括数据库连接池技术那就是负责分配、管理和释放数据库连接。

60、说出数据连接池的工作机制是什么?

J2EE 服务器启动时会建立一定数量的池连接,并一直维持不少于此数目的池连接。客户端程序需要连接时,池驱动程序会返回一个未使用的池连接并将其表记为忙。如果当前没有空闲连接,池驱动程序就新建一定数量的连接,新建连接的数量有配置参数决定。当使用的池连接调用完成后,池驱动程序将此连接表记为空闲,其他调用就可以使用这个连接。

线程池相关文章:Java并发编程:线程池的使用

61.常用的线程池有几种?

newCachedThreadPool
(1)缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse,如果没有,就建立一个新的线程加入池中;
(2)缓存型池子,通常用于执行一些生存周期很短的异步型任务;因此一些面向连接的daemon型server中用得不多;
(3)能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
(4)注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止

newFixedThreadPool
(1)newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程
(2)其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子
(3)和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器
(4)从方法的源代码看,cache池和fixed 池调用的是同一个底层池,只不过参数不同:
fixed池线程数固定,并且是0秒IDLE(无IDLE)
cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE

ScheduledThreadPool
(1)调度型线程池
(2)这个池子里的线程可以按schedule依次delay执行,或周期执行
SingleThreadExecutor
(1)单例线程,任意时间池中只能有一个线程
(2)用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)

62.Java中Runnable和Callable有什么不同?

Callable Runnable
Callable规定的方法是call() Runnable规定的方法是run()
Callable的任务执行后可返回值,运行Callable任务可以拿到一个Future对象 而Runnable的任务是不能返回值得
call方法可以抛出异常 run方法不可以

Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如果线程没有执行完,Future.get()方法可能会阻塞当前线程的执行;如果线程出现异常,Future.get()会throws InterruptedException或者ExecutionException;如果线程已经取消,会抛出CancellationException。取消由cancel 方法来执行。isDone确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明Future

63.什么是线程安全?Vector是一个线程安全类吗?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。

64. Java中什么是竞态条件? 举个例子说明。

一些操作是需要先检测后执行的,若两个以上的操作同时检测一个东西,判断可以操作,这时候都去操作,就可能会出现一些bug
比如,两个程序都要检测一个文件是否存在,然后创建,这时候一二都检测到没有文件,这时候一先创建,而二也会创建这时候就会把一创建的覆盖掉。
竞态条件会导致程序在并发情况下出现一些bugs。多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了,那么整个程序就会出现一些不确定的bugs。这种bugs很难发现而且会重复出现,因为线程间的随机竞争。一个例子就是无序处理。

65. Java中如何停止一个线程?

Java提供了很丰富的API但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的JDK版本中他们被弃用了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

66. Java中notify 和 notifyAll有什么区别?

这又是一个刁钻的问题,因为多线程可以等待单监控锁,Java API 的设计人员提供了一些方法当等待条件改变的时候通知它们,但是这些方法没有完全实现。notify()方法不能唤醒某个指定的线程,只是通知一个等待的线程,不过这个线程具体是哪一个就不能指定了,所以只有一个线程在等待的时候它才有用武之地。而notifyAll()唤醒所有线程,然后所有线程被唤醒并争夺锁,获得锁的线程开始执行,其他线程等待锁。这样就确保了至少有一个线程能继续运行

67.为什么wait, notify 和 notifyAll这些方法不在thread类里面?

这是个设计相关的问题,它考察的是面试者对现有系统和一些普遍存在但看起来不合理的事物的看法。回答这些问题的时候,你要说明为什么把这些方法放在Object类里是有意义的,还有不把它放在Thread类里的原因。一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

68.什么是ThreadLocal变量?

threadLocal是一个map数据,里面存储每一个线程的变量的副本集合。
ThreadLocal是Java里一种特殊的变量。每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量副本,竞争条件被彻底消除了。它是为创建代价高昂的对象获取线程安全的好方法,比如你可以用ThreadLocal让SimpleDateFormat变成线程安全的,因为那个类创建代价高昂且每次调用都需要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。线程局部变量的另一个不错的例子是ThreadLocalRandom类,它在多线程环境中减少了创建代价高昂的Random对象的个数。可以参考这篇博文

69.Java中interrupted 和 isInterruptedd方法的区别?

interrupted()和isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态但不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有可能被其它线程调用中断来改变。

70.为什么wait和notify方法要在同步块中调用?

若不在同步块中调用,可能会发生这种情况:线程1还没有开始等待,线程2调用notify方法,正好选中了线程1,线程1进入等待,陷入死锁
主要是因为Java API强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。还有一个原因是为了避免wait和notify之间产生竞态条件

71.为什么你应该在循环中检查等待条件?

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出等待。因此,当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用wait()方法效果更好的原因。

也就是说,如果你创建了一个线程并进入了等待状态,当比人调用notify或notifyAll方法要唤醒这个线程时,你要判断一个变量的值满足一个条件时才让线程醒来,如果不满足条件就继续等待。由于这个变量的值在收到notify调用之后和线程醒来之前(竞争锁等情况),其值可能会改变,那么就会出现本来值满足醒来条件,但是当真正醒来之前这个值又改变了,不满足下面要执行的逻辑判断,就会出现错误的结果,所以我们需要在真正执行下面逻辑时,还要再判断一些这个值,如果线程被唤醒了之后,且获得了锁,我们需要再判断一下值是否满足条件,如果不满足,我们就需要再次将线程沉睡。使用while可以很好的完成这样的情况,而使用if则需要多个if判断。

java中并没有强制的让在while中检查等待条件,但是为了安全,建议大家在循环中检查等待条件。

如下面两段等价的代码:

synchronized (monitor) {
    //  判断条件谓词是否得到满足
    while(!locked) {
        //  等待唤醒
        monitor.wait();
    }
    //  处理其他的业务逻辑
}

synchronized (monitor) {
    //  判断条件谓词是否得到满足
    if(!locked) {
        //  等待唤醒
        monitor.wait();
        if(locked) {
            //  处理其他的业务逻辑
        } else {
            //  跳转到monitor.wait(); 
        }
    }
}

72.如何避免死锁?

多线程中的死锁

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

假设有P1和P2两个进程,都需要A和B两个资源,现在P1持有A等待B资源,而P2持有B等待A资源,两个都等待另一个资源而不肯释放资源,就这样无限等待中,这就形成死锁。

这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

73.Java中活锁和死锁有什么区别?

这是上题的扩展,活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行,而后者则是一直在等待一个不会释放的资源,因为那一个资源也在等待当前线程持有的资源

74.怎么检测一个线程是否拥有锁?

我一直不知道我们竟然可以检测一个线程是否拥有锁,直到我参加了一次电话面试。在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。

75.有三个线程T1,T2,T3,怎么确保它们按顺序执行?

在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

public class JoinTest2 {  

    // 1.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行  


    public static void main(String[] args) {  

        final Thread t1 = new Thread(new Runnable() {  

            @Override  
            public void run() {  
                System.out.println("t1");  
            }  
        });  
        final Thread t2 = new Thread(new Runnable() {  

            @Override  
            public void run() {  
                try {  
                    //引用t1线程,等待t1线程执行完  
                    t1.join();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                System.out.println("t2");  
            }  
        });  
        Thread t3 = new Thread(new Runnable() {  

            @Override  
            public void run() {  
                try {  
                    //引用t2线程,等待t2线程执行完  
                    t2.join();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                System.out.println("t3");  
            }  
        });  
        t3.start();  
        t2.start();  
        t1.start();  
    }  
}  

相关文章

76.如果你提交任务时,线程池队列已满。会时发会生什么?

这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException(拒绝执行异常)异常。

77.线程池中submit() 和 execute()方法有什么区别?

execute用于开启线程执行池中的任务,submit用于提交指定的任务去执行并且返回Future对象,即执行的结果。

两个方法都可以向线程池提交任务

execute submit
execute只能接受Runnable类型的任务 submit不管是Runnable还是Callable类型的任务都可以接受,但是Runnable返回值均为void,所以使用Future的get()获得的还是null
execute没有返回值 submit有返回值,所以需要返回值的时候必须使用submit
excute方法会抛出异常。 sumbit方法不会抛出异常。除非你调用Future.get()

78.多线程中的忙循环是什么?

忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。

79.内存泄漏

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存(对象、成员变量等)由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

内存泄漏的产生原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。内存泄漏通常不会直接产生可观察的错误症状,而是逐渐积累,降低系统整体性能,极端的情况下可能使系统崩溃。

java的解决方法就是其自带的自动垃圾回收器(GC):GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题。在Java语言中,判断一个内存空间是否符合垃圾收集标准有两个:一个是给对象赋予了空值null,以下再没有调用过,另一个是给对象赋予了新值,这样重新分配了内存空间。

80.@Transactional 事务机制

查看这篇博文

81.如何知道一个sql是否使用了索引

orcale

监控索引使用情况

1、分析索引
           alter index index_name monitoring usage;
2,从v$object_usage系统表查询数据,看其used是否为YES,当然要观察一段时间才能确定是否被使用。
           select table_name,index_name,used from v$object_usage;

这两条需要一起使用才能看得到使用效果,例如:

alter index INDEX_USER monitoring usage;
select table_name,index_name,used from v$object_usage; 

或者我们也可以先使用SET AUTOTRACE ON;打开执行计划和统计信息的返回,在执行sql后返回的执行计划中,可以看到是否使用到索引。
注:

SET AUTOTRACE OFF ---------------- 不生成AUTOTRACE 报告,这是缺省模式
SET AUTOTRACE ON EXPLAIN ------ AUTOTRACE只显示优化器执行路径报告
SET AUTOTRACE ON STATISTICS -- 只显示执行统计信息
SET AUTOTRACE ON ----------------- 包含执行计划和统计信息
SET AUTOTRACE TRACEONLY ------ 同set autotrace on,但是不显示查询输出

如何监控ORACLE索引使用与否
看懂orcale执行计划

mysql

使用explain+SQL语句看执行计划,mysql执行计划的查看,我们可以根据其执行计划返回的Type、key来判断是否使用了索引。

82.Mysql索引实现原理及相关优化策略

参考文章

83.sql行转列与列转行

列转行

列转行与行转列

84.SpringBoot如何创建自己的starter

参考文章

85.JDK1.8新特性

JDK1.8十大新特性详解

86.CAS

CAS是Central Authentication Server的缩写,中央认证服务,一种独立开放指令协议。CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目。

特点
1、开源的企业级单点登录解决方案。
2、CAS Server 为需要独立部署的 Web 应用。
3、CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。

原理和协议编辑

从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS ServerCAS 最基本的协议过程:
java面试整理(一)—— 小问题总结_第3张图片
CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。对于访问受保护资源的每个 Web 请求,CAS Client 会分析该请求的 Http 请求中是否包含 Service Ticket,如果没有,则说明当前用户尚未登录,于是将请求重定向到指定好的 CAS Server 登录地址,并传递 Service (也就是要访问的目的资源地址),以便登录成功过后转回该地址。用户在第 3 步中输入认证信息,如果登录成功,CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service Ticket,并缓存以待将来验证,之后系统自动重定向到 Service 所在地址,并为客户端浏览器设置一个 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新产生的 Ticket 过后,在第 5,6 步中与 CAS Server 进行身份核实,以确保 Service Ticket 的合法性。
在该协议中,所有与 CAS 的交互均采用 SSL 协议,确保,ST 和 TGC 的安全性。协议工作过程中会有 2 次重定向的过程,但是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的。
另外,CAS 协议中还提供了 Proxy (代理)模式,以适应更加高级、复杂的应用场景,具体介绍可以参考 CAS 官方网站上的相关文档。

你可能感兴趣的:(java,java面试整理)