有谁遇到过这个异常,java.sql包的作用.SQLException: privilege check fail,很匪夷所思,连的是TIDB(mysql)

Effective Java (异常) - Stephen_Liu - 博客园
深入浅出、事半功倍
posts - 180, comments - 782, trackbacks - 0, articles - 0
五十七、只针对异常情况才使用异常:&& && 不知道你否则遇见过下面的代码:
int i = 0;3
while (true)4
range[i++].climb();5
} catch (ArrayIndexOutOfBoundsException e) {6
&&&&& 这段代码的意图不是很明显,其本意就是遍历变量数组range中的每一个元素,并执行元素的climb方法,当下标超出range的数组长度时,将会直接抛出ArrayIndexOutOfBoundsException异常,catch代码块将会捕获到该异常,但是未作任何处理,只是将该错误视为正常工作流程的一部分来看待。这样的写法确实给人一种匪夷所思的感觉,让我们再来看一下修改后的写法:
for (Mountain m : range) {2
m.climb();3
&&&&& 和之前的写法相比其可读性不言而喻。那么为什么又有人会用第一种写法呢?显然他们是被误导了,他们企图避免for-each循环中JVM对每次数组访问都要进行的越界检查。这无疑是多余的,甚至适得其反,因为将代码放在try-catch块中反而阻止了JVM的某些特定优化,至于数组的边界检查,现在很多JVM实现都会将他们优化掉了。在实际的测试中,我们会发现采用异常的方式其运行效率要比正常的方式慢很多。&& && 除了刚刚提到的效率和代码可读性问题,第一种写法还会掩盖一些潜在的Bug,假设数组元素的climb方法中也会访问某一数组,并且在访问的过程中出现了数组越界的问题,基于该错误,JVM将会抛出ArrayIndexOutOfBoundsException异常,不幸的是,该异常将会被climb函数之外catch语句捕获,在未做任何处理之后,就按照正常流程继续执行了,这样Bug也就此被隐藏起来。&& && 这个例子的教训很简单:"异常应该只用于异常的情况下,它们永远不应该用于正常的控制流"。虽然有的时候有人会说这种怪异的写法可以带来性能上的提升,即便如此,随着平台实现的不断改进,这种异常模式的性能优势也不可能一直保持。然而,这种过度聪明的模式带来的微妙的Bug,以及维护的痛苦却依然存在。&& && 根据这条原则,我们在设计API的时候也是会有所启发的。设计良好的API不应该强迫它的客户端为了正常的控制流而使用异常。如Iterator,JDK在设计时充分考虑到这一点,客户端在执行next方法之前,需要先调用hasNext方法已确认是否还有可读的集合元素,见如下代码:
for (Iterator&Foo& i = collection.iterator(); i.hasNext(); ) {2
Foo f = i.next();3
&&&&& 如果Iterator缺少hasNext方法,客户端则将被迫改为下面的写法:
Iterator&Foo& i = collection.iterator();3
while (true)4
Foo f = i.next();5
} catch (NoSuchElementException e) {6
&&&&& 这应该非常类似于本条目开始时给出的遍历数组的例子。在实际的设计中,还有另外一种方式,即验证可识别的错误返回值,然而该方式并不适合于此例,因为对于next,返回null可能是合法的。那么这两种设计方式在实际应用中有哪些区别呢?&& && 1. 如果是缺少同步的并发访问,或者可被外界改变状态,使用可识别返回值的方法是非常必要的,因为在测试状态(hasNext)和对应的调用(next)之间存在一个时间窗口,在该窗口中,对象可能会发生状态的变化。因此,在该种情况下应选择返回可识别的错误返回值的方式。&& && 2. 如果状态测试方法(hasNext)和相应的调用方法(next)使用的是相同的代码,出于性能上的考虑,没有必要重复两次相同的工作,此时应该选择返回可识别的错误返回值的方式。&& && 3. 对于其他情形则应该尽可能考虑"状态测试"的设计方式,因为它可以带来更好的可读性。&&& 五十八、对可恢复的情况使用受检异常,对编程错误使用运行时异常:&&&&& Java中提供了三种可抛出结构:受检异常、运行时异常和错误。该条目针对这三种类型适用的场景给出了一般性原则。&&&&& 1. 如果期望调用者能够适当地恢复,对于这种情况就应该使用受检异常,如某人打算网上购物,结果余额不足,此时可以抛出自定义的受检异常。通过抛出受检异常,将强迫调用者在catch子句中处理该异常,或继续向上传播。因此,在方法中声明受检异常,是对API用户的一种潜在提示。&&&&& 2. 用运行时异常来表明编程错误。大多数的运行时异常都表示"前提违例",即API的使用者没有遵守API设计者建立的使用约定。如数组访问越界等问题。&&&&& 3. 对于错误而言,通常是被JVM保留用于表示资源不足、约束失败,或者其他使程序无法继续执行的条件。&&&&& 针对自定义的受检异常,该条目还给出一个非常实用的技巧,当调用者捕获到该异常时,可以通过调用该自定义异常提供的接口方法,获取更为具体的错误信息,如当前余额等信息。&&& 五十九、避免不必要的使用受检异常:&&& &&&&& 受检异常是Java提供的一个很好的特征。与返回值不同,它们强迫程序员必须处理异常的条件,从而大大增强了程序的可靠性。然而,如果过分使用受检异常则会使API在使用时非常不方便,毕竟我们还是需要用一些额外的代码来处理这些抛出的异常,倘若在一个函数中,它所调用的五个API都会抛出异常,那么编写这样的函数代码将会是一项令人沮丧的工作。&&&&& 如果正确的使用API不能阻止这种异常条件的产生,并且一旦产生异常,使用API的程序员可以立即采用有用的动作,这种负担就被认为是正当的。除非这两个条件都成立,否则更适合使用未受检异常,见如下测试:
dosomething(); 3
} catch (TheCheckedException e) { 4
throw new AssertionError(); 5
donsomething(); 9
} catch (TheCheckedException e) {10
e.printStackTrace();11
System.exit(1);12
&&&&& 当我们使用受检异常时,如果在catch子句中对异常的处理方式仅仅如以上两个示例,或者还不如它们的话,那么建议你考虑使用未受检异常。原因很简单,它们在catch子句中,没有做出任何用于恢复异常的动作。&&& 六十、优先使用标准异常:&& && 使用标准异常,不仅可以更好的复用已有的代码,同时也使你设计的API更加容易学习和使用,因为它和程序员已经熟悉的习惯用法更为一致。另外一个优势是,代码的可读性更好,程序员在阅读时不会出现更多的不熟悉的代码。该条目给出了一些非常常用且容易被复用的异常,见下表:&& && 异常&& &&& &&& &&& &&& &&& &&& &&& &&&&&&&&&&&&&&& 应用场合&& && IllegalArgumentException&& &&& &&& && 非null的参数值不正确。&& && IllegalStateException&& &&& &&& &&& &&&&& 对于方法调用而言,对象状态不合适。&& && NullPointerException&& &&& &&& &&& & & & 在禁止使用null的情况下参数值为null。&& && IndexOutOfBoundsException&& &&& & 下标参数值越界&& && ConcurrentModificationException&& 在禁止并发修改的情况下,检测到对象的并发修改。&& && UnsupportedOperationException&&& 对象不支持用户请求的方法。&& && 当然在Java中还存在很多其他的异常,如ArithmeticException、NumberFormatException等,这些异常均有各自的应用场合,然而需要说明的是,这些异常的应用场合在有的时候界限不是非常分明,至于该选择哪个比较合适,则更多的需要依赖上下文环境去判断。&& && 最后需要强调的是,一定要确保抛出异常的条件和该异常文档中描述的条件保持一致。&&& 六十一、抛出与抽象相对应的异常:&& && 如果方法抛出的异常与它所执行的任务没有明显的关系,这种情形将会使人不知所措。特别是当异常从底层开始抛出时,如果在中间层没有做任何处理,这样底层的实现细节将会直接污染高层的API接口。为了解决这样的问题,我们通常会做出如下处理:
doLowerLeverThings();3
} catch (LowerLevelException e) {4
throw new HigherLevelException(...);5
&& && 这种处理方式被称为异常转译。事实上,在Java中还提供了一种更为方便的转译形式--异常链。试想一下上面的示例代码,在调试阶段,如果高层应用逻辑可以获悉到底层实际产生异常的原因,那么对找到问题的根源将会是非常有帮助的,见如下代码:
doLowerLevelThings();3
} catch (LowerLevelException cause) {4
throw new HigherLevelException(cause);5
&&&&& 底层异常作为参数传递给了高层异常,对于大多数标准异常都支持异常链的构造器,如果没有,可以利用Throwable的initCause方法设置原因。异常链不仅让你可以通过接口函数getCause访问原因,它还可以将原因的堆栈轨迹集成到更高层的异常中。&& && 通过这种异常链的方式,可以非常有效的将底层实现细节与高层应用逻辑彻底分离出来。&&& &&& 六十三、在细节中包含能捕获失败的信息:&& &当程序由于未被捕获的异常而失败的时候,系统会自动地打印出该异常的堆栈轨迹。在堆栈轨迹中包含该异常的字符串表示法,即toString方法的返回结果。如果我们在此时为该异常提供了详细的出错信息,那么对于错误定位和追根溯源都是极其有意义的。比如,我们将抛出异常的函数的输入参数和函数所在类的域字段值等信息格式化后,再打包传递给待抛出的异常对象。假设我们的高层应用捕捉到IndexOutOfBoundsException异常,如果此时该异常对象能够携带数组的下界和上界,以及当前越界的下标值等信息,在看到这些信息后,我们就能很快做出正确的判断并修订该Bug。&& &特别是对于受检异常,如果抛出的异常类型还能提供一些额外的接口方法用于获取导致错误的数据或信息,这对于捕获异常的调用函数进行错误恢复是非常重要的。&&& 六十四、努力使失败保持原子性:&& && 这是一个非常重要的建议,因为在实际开发中当你是接口的开发者时,经常会忽视他,认为不保证的话估计也没有问题。相反,如果你是接口的使用者,也同样会忽略他,会认为这个是接口实现者理所应当完成的事情。&& && 当对象抛出异常之后,通常我们期望这个对象仍然保持在一种定义良好的可用状态之中,即使失败是发生在执行某个操作的过程中间。对于受检异常而言,这尤为重要,因为调用者希望能从这种异常中进行恢复。一般而言,失败的方法调用应该使对象保持在被调用之前的状态。具有这种属性的方法被称为具有"失败原子性"。&& && 有以下几种途径可以保持这种原子性。&& && 1. 最简单的方法是设计不可变对象。因为失败的操作只会导致新对象的创建失败,而不会影响已有的对象。&& && 2. 对于可变对象,一般方法是在操作该对象之前先进行参数的有效性验证,这可以使对象在被修改之前,抛出更为有意义的异常,如:
public Object pop() {2
if (size == 0)3
throw new EmptyStackException();4
Object result = elements[--size];5
elements[size] = null;6
&&&&& 如果没有在操作之前验证size,elements的数组也会抛出异常,但是由于size的值已经发生了变化,之后再继续使用该对象时将永远无法恢复到正常状态了。&& && 3. 预先写好恢复性代码,在出现错误时执行带段代码,由于此方法在代码编写和代码维护的过程中,均会带来很大的维护开销,再加之效率相对较低,因此很少会使用该方法。&& && 4. 为该对象创建一个临时的copy,一旦操作过程中出现异常,就用该复制对象重新初始化当前的对象的状态。&& && 虽然在一般情况下都希望实现失败原子性,然而在有些情况下却是难以做到的,如两个线程同时修改一个可变对象,在没有很好同步的情况下,一旦抛出ConcurrentModificationException异常之后,就很难在恢复到原有状态了。&&& 六十五、不要忽略异常:&& && 这是一个显而易见的常识,但是经常会被违反,因此该条目重新提出了它,如:
dosomething();3
} catch (SomeException e) {4
&&&&& 可预见的、可以使用忽略异常的情形是在关闭FileInputStream的时候,因为此时数据已经读取完毕。即便如此,如果在捕获到该异常时输出一条提示信息,这对于挖出一些潜在的问题也是非常有帮助的。否则一些潜在的问题将会一直隐藏下去,直到某一时刻突然爆发,以致造成难以弥补的后果。&& && 该条目中的建议同样适用于受检异常和未受检的异常。1、在面向对象方法中,实现信息隐蔽是依靠
A.对象的继承 B.对象的多态 C.对象的封装 D.对象的分类
解析:对象的继承:子类拥有父类所有的属性和方法,子类共享父类的数据
对象的多态:解耦,不同的对象在接收到相同的消息时,采用不同的动作
编译的多态就是函数重载(方法名不同,参数个数也不同);
运行时多态就是函数重写(具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法)
对象的封装:实现信息隐蔽
2、对成员的访问控制保护最强的是
A.public B.缺省 C.private D.protected(子类或同package)
3.可用作java标识符的是
A.#123# B.@yahoo.com C._date D.10years
4.属于java输入输出流的、且处理的是char类型的类是
A.Reader类 B.InputStream类 C.OutputStream类 D.File类
解析:学习地址http://blog.csdn.net/hguisu/article/details/7418161
在电脑上的数据有三种存储方式,一种是外存,一种是内存,一种是缓存。比如电脑上的硬盘,磁盘,U盘等都是外存,在电脑上有内存条,缓存是在CPU里面的。外存的存储量最大,其次是内存,最后是缓存,但是外存的数据的读取最慢,其次是内存,缓存最快。
Java类库中,IO部分的内容:标准输入输出,文件的操作,网络上的数据流,字符串流,对象流,zip文件流等等,java中将输入输出抽象称为流
一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。
输入流(Input
Stream):
程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道
程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。
采用数据流的目的就是使得输出输入独立于设备。
Stream不关心数据源来自何种设备(键盘,文件,网络)
Stream不关心数据的目的是何种设备(键盘,文件,网络)
数据流分类:
流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种: 1)
字节流:数据流中最小的数据单元是字节 2)
字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。
Java.io包中最重要的就是5个类和一个接口:5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable
Java I/O主要包括如下几个层次,包含三个部分:
1.流式部分――IO的主体部分;
2.非流式部分――主要包含一些辅助流式部分的类,如:File类、RandomAccessFile类和FileDescriptor等类;
3.其他类--文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。
主要的类如下:
1. File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
2. InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
3. OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。
4. Reader(文件格式操作):抽象类,基于字符的输入操作。
5. Writer(文件格式操作):抽象类,基于字符的输出操作。
6. RandomAccessFile(随机文件操作):它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
Java中IO流的体系结构如图:
非流式文件类--File类
1)public boolean exists( ) 判断文件或目录是否存在
2)public boolean isFile( ) 判断是文件还是目录
3)public boolean isDirectory( ) 判断是文件还是目录
4)public String getName( ) 返回文件名或目录名
5)public String getPath( ) 返回文件或目录的路径。
6)public long length( ) 获取文件的长度
7)public String[ ] list ( ) 将目录中所有文件名保存在字符串数组中返回。
2) public boolean renameTo( File newFile );
重命名文件
2) public void delete( );
public boolean mkdir( ); 创建目录
基本数据流的I/O
Inputstream
OutputStream
5.下列语句中,属于分支语句的是
A.if语句 B.swithc语句 C.do while 语句 D.for语句
6.设x=1,y=2,z=3,则表达式y+=z--/++x-x的值是
A.0 B.1 C.2 D.3
解析 :++x以后即使是同一个表达式里x也成了++x的值
7.在java中,哪一个关键字使类不能派生出子类
A.final B.public C.private D.volatile
解析:volatile关键字的两层语义
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
先看一段代码,假如线程1先执行,线程2后执行:
boolean stop =
while(!stop){
doSomething();
这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。
下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
但是用volatile修饰之后就变得不一样了:
第一:使用volatile关键字会强制将修改的值立即写入主存;
第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
那么在线程2修改stop值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量stop的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。
8.下面答案中a,b结果正确的是 C
StringBuffer stringBuffer1 = new StringBuffer("abc");
StringBuffer stringBuffer2 = new StringBuffer("abc");
boolean a = (stringBuffer1.equals(stringBuffer2));
String empA = new String("abc");
String empB = new String("abc");
boolean b = (empA == empB)
A. true false B.true true C.false false D.false true
String:http://www.cnblogs.com/timecloud/p/6555868.html
9.下列不是Junit4支持的断言方法的是
A.assertNotNull() B.assertEquals() C.assertRight() D.assertSame()
10.为了编程需要现需自己编写一个异常类。一般来说,下面那个最为合适
A.class myClass extends Exception{... B.class myException extends Error{..
C.class myException extends RuntimeException{...
D.class myException extends Exception{..
11.以下说法错误的有
A.在任何情况下都必须使用边界值分析方法,经验表明用这种方法设计出测试用例发现程序错误的能力最强
B.必要时用等价类划分方法补充一些测试用例
C.对照程序逻辑,检查已设计出的测试用例的逻辑覆盖程度,如果没有达到要求的覆盖标准,应当再补充足够的测试用例
D.如果程序的功能说明中含有输入条件的组合情况,则一开始就可选用错误推测法
12.执行下列代码后,哪个结论是正确的String[] s = new String[10];
A.s[10]为"" B.s[9]为0 C.s[0]为未定义 D.s.length为10
13.在下述程序中,判断i&j共执行的次数是
public static void main(String [] args){
int i=0,j=10,k=2,s=0;
System.out.println(s);
A.4 B.7 C.5 D.6
14.下列关于继承的哪项叙述是正确的
A.在Java中允许多重继承 B.在java中一个类只能实现一个接口
C.在java中一个类不能同时继承一个类和实现一个接口 D.java的单一继承使代码更可靠
15.线程的生命周期中正确的状态是
A.新建状态、运行状态、和终止状态
B.新建状态、运行状态、阻塞状态和终止状态
C.新建状态、可运行状态、运行状态、阻塞状态和终止状态
D.新建状态、可运行状态、运行状态、恢复状态和终止状态
解析:1.线程的生命周期线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
(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()可以对当前线程进行临时暂停(让线程将资源释放出来)
3.(1)结束线程原理:就是让run方法结束。而run方法中通常会定义循环结构,所以只要控制住循环即可
(2)方法----可以boolean标记的形式完成,只要在某一情况下将标记改变,让循环停止即可让线程结束
(3)public final void join()//让线程加入执行,执行某一线程join方法的线程会被冻结,等待某一线程执行结束,该线程才会恢复到可运行状态
4. 临界资源:多个线程间共享的数据称为临界资源
(1)互斥锁
a.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
b.Java对象默认是可以被多个线程共用的,只是在需要时才启动“互斥锁”机制,成为专用对象。
c.关键字synchronized用来与对象的互斥锁联系
d.当某个对象用synchronized修饰时,表明该对象已启动“互斥锁”机制,在任一时刻只能由一个线程访问,即使该线程出现堵塞,该对象的被锁定状态也不会解除,其他线程任不能访问该对象。
二、填空题
1,每个java应用程序都要有且只有一个__方法它是程序开始的运行点
2,ORM的全称是__,常见的ORM框架有__,__(至少两个)
解析:Entity EJB、Hibernate、IBATIS、TopLink、OJB
3,如果要在程序中使用键盘输入语句,则需要预先导入__包
4,在MVC中,M是模型,V是__,C是__
5,面向对象的三大基本特征是抽象和__、继承、__
6,java语言中,有一个类是所有类或接口的父类,这个类的名称是__
7,在面向对象概念中,每一个对象都是由__,__两个基本的部分组成的
8,关键字__是用来定义接口的,使用关键字__来定义某个类实现了接口
9,__语句可以终止当前一轮的循环,不再执行其下面的语句,直接进入下一轮的循环,__语句可以使程序从一个语句体的内部跳出去,继续执行该语句体下面的语句
三、分析题
1、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?为什么?
值传递,无论传递的是基本数据类型还是引用数据类型,都是传递的值或引用对象的地址的副本,最终仍然是值传递
2、给定下列代码:
public void test(){
oneMethod();
System.out.println("condition 1");
}catch(ArrayIndexOutofBoundException e){
System.out.println("condition 2");
}catch(Exception e){
System.out.println("condition 3");
System.out.println("finally");
在方法oneMethod()运行正常的情况下程序执行的流程是什么?
3、要求:书写程序,写一个JDBC程序连接数据库,写出连接数据库的步骤和关键代码,并查询所有数据打印。数据库名为test,表名为users,有字段id,username,password ,采用Mysql数据库(数据库名与密码均为root)
连接数据库的步骤:
1、注册驱动(只做一次)
2、建立连接(Connection)
3、创建执行SQL的语句(Statement)
4、执行语句
5、处理执行结果(ResultSet)
6、释放资源
PreparedStatement preparedStatement =
Connection conn =
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://192.168.101.44:3306/test";
String url = "jdbc:oracle:thin:@192.168.8.1:1521:test";
String user = "root";
String password = "root";
//连接到具体的数据库
conn = DriverManager.getConnection(url, user, password);
String sql = "select id,username,password from users";
preparedStatement = conn.prepareStatement(sql);
}catch(Exception e){
e.printStackTrace();
conn.close();
四、论述题
1、简述Rest及其实现框架和作用:
http://blog.csdn.net/cws1214/article/details/
Rest风格:REST 本质上是使用 URL 来访问资源的一种方式,前后端分离(ajax/json)
“无状态”的架构模式:任何时候都可以由客户端发出请求到服务端,最终返回自己想要的数据,当前请求不会受到上次请求的影响。一种“轻量级”的 SOA(面向服务)实现技术
强调HTTP应当以资源为中心,并且规范了资源URI的风格;
规范了HTTP请求动作(PUT,POST等)的使用,具有对应的语义
URL具有很强可读性的,具有自描述性;
资源描述与视图的松耦合;
可提供OpenAPI,便于第三方系统集成,提高互操作性;
如果提供无状态的服务接口,可提高应用的水平扩展性;
统一响应结构:使用Rest实现前后端分离,首先保证统一的json数据结构包含两部分:元数据 与 返回值
实现对象序列化:在服务端从数据库中获取普通的 Java 对象数据,然后需要将这个 Java 对象转换为 JSON 字符串,并将其返回到浏览器中进行渲染,这个转换过程称为序列化;再比如,通过浏览器发送了一个普通的 HTTP 请求,该请求携带了一个 JSON 格式的参数,在服务端需要将该 JSON 参数转换为普通的 Java 对象,这个转换过程称为 反序列化。@RequestBody、@RestController
处理异常行为:编写一个全局的异常处理切面类,用它来统一处理所有的异常行为
支持参数验证:建议将其参数验证行为从 Controller 中剥离出来,放到另外的类中,通过Hibernate Validator 框架实现
解决跨域问题:整个架构包含两个应用,前端应用提供纯静态的 HTML 页面,后端应用发布 REST API,前端需要通过 AJAX 调用后端发布的 REST API,然而 AJAX 是不支持跨域访问的。CORS 全称为 Cross Origin Resource Sharing(跨域资源共享),服务端只需添加相关响应头信息,即可实现客户端发出 AJAX 跨域请求。
提供安全机制:
当用户登录成功后,在服务端生成一个 token,并将其放入内存中(可放入 JVM 或 Redis 中),同时将该 token 返回到客户端。
在客户端中将返回的 token 写入 cookie 中,并且每次请求时都将 token 随请求头一起发送到服务端。
提供一个 AOP 切面,用于拦截所有的 Controller 方法,在切面中判断 token 的有效性。
当登出时,只需清理掉 cookie 中的 token 即可,服务端 token 可设置过期时间,使其自行移除。
2、简述Spring的AOP和IOC
spring 的优点?
1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦
2.可以使用容易提供的众多服务,如事务管理,消息服务等
3.容器提供单例模式支持
4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能
5.容器提供了众多的辅助类,能加快应用的开发
6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等
7.spring属于低侵入式设计,代码的污染极低
8.独立于各种应用服务器
9.spring的DI机制降低了业务对象替换的复杂性
10.Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可以自由选择spring的部分或全部
什么是DI机制?
依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者
因此也称为依赖注入。
spring以动态灵活的方式来管理对象 , 注入的两种方式,设置注入和构造注入。
设置注入的优点:直观,自然
构造注入的优点:可以在构造器中决定依赖关系的顺序。
什么是AOP?
面向切面编程(AOP)完善spring的依赖注入(DI),面向切面编程在spring中主要表现为两个方面
1.面向切面编程提供声明式事务管理
2.spring支持用户自定义的切面
面向切面编程(aop)是对面向对象编程(oop)的补充,
面向对象编程将程序分解成各个层次的对象,面向切面编程将程序运行过程分解成各个切面。
AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop是静态的抽象,aop是动态的抽象,
是对应用执行过程中的步骤进行抽象,,从而获得步骤之间的逻辑划分。
aop框架具有的两个特征:
1.各个步骤之间的良好隔离性
2.源代码无关性
Spring的事务管理机制实现的原理,就是通过这样一个动态代理对所有需要事务管理的Bean进行加载,并根据配置在invoke方法中对当前调用的 方法名进行判定,并在method.invoke方法前后为其加上合适的事务管理代码,这样就实现了Spring式的事务管理。Spring中的AOP实 现更为复杂和灵活,不过基本原理是一致的。
3、简述一下JVM中的垃圾回收机制
GC通过确定对象是否被活动对象引用来确定是否收集该对象。
2.1 触发GC(Garbage Collector)的条件
1)GC在优先级最低的线程中运行,一般在应用程序空闲即没有应用线程在运行时被调用。但下面的条件例外。
2)Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制调用GC线程。若GC一次之后仍不能满足内存分配,JVM会再进行两次GC,若仍无法满足要求,则JVM将报“out of memory”的错误,Java应用将停止。
2.2 两个重要方法
2.2.1 System.gc()方法
      使用System.gc()可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收。在命令行中有一个参数-verbosegc可以查看Java使用的堆内存的情况,它的格式如下:java -verbosegc classfile
由于这种方法会影响系统性能,不推荐使用,所以不详诉。
2.2.2 finalize()方法
      在JVM垃圾回收器收集一个对象之前,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了缺省机制来终止该对象心释放资源,这个方法就是finalize()。它的原型为:protected void finalize() throws Throwable
在finalize()方法返回之后,对象消失,垃圾收集开始执行。原型中的throws
Throwable表示它可以抛出任何类型的异常。
      之所以要使用finalize(),是存在着垃圾回收器不能处理的特殊情况。例如:1)由于在分配内存的时候可能采用了类似 C语言的做法,而非JAVA的通常new做法。这种情况主要发生在native method中,比如native method调用了C/C++方法malloc()函数系列来分配存储空间,但是除非调用free()函数,否则这些内存空间将不会得到释放,那么这个时候就可能造成内存泄漏。但是由于free()方法是在C/C++中的函数,所以finalize()中可以用本地方法来调用它。以释放这些“特殊”的内存空间。2)又或者打开的文件资源,这些资源不属于垃圾回收器的回收范围。
2.3 减少GC开销的措施
1)不要显式调用System.gc()。此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。大大的影响系统性能。
2)尽量减少临时对象的使用。临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。
3)对象不用时最好显式置为Null。一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
4)尽量使用StringBuffer,而不用String来累加字符串。由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
5)能用基本类型如Int,Long,就不用Integer,Long对象。基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。
6)尽量少用静态对象变量。静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
7)分散对象创建或删除的时间。集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。
4、简述一下JVM的类加载机制
使用反射获取类的信息
使用反射创建对象
使用反序列化访问属性和方法
使用Array动态创建和访问数组
二、反射概述
1、反射机制:
Java特性之一,是构建框架技术的基础所在
Java反射机制是指在运行状态中,动态获取信息以及动态调用对象方法的功能
使用反射可以在程序运行时创建类的实例以及访问其属性和方法
Java反射有3个动态性质:
运行时生成对象实例;
运行期间调用方法;
运行时更改属性。
Java程序执行过程:如下所示,要想java程序可运行,java类必须被java虚拟机加载。运行的程序都是在编译时就已经加载了所需要的类。
Person.java ---&编译器---&Person.class---&java虚拟机---&运行程序
反射执行过程:如下所示,Java反射机制在编译时并不知道是哪个类被加载了,而是在程序运行时才加载、探知、使用。
?? ---&编译器&--- ---&运行程序
Java反射机制能够知道类的基本结构,这种对java类结构的探知能力叫做”自省”。如eclipse的java代码自动提示功能。
通过java反射可以实现以下功能:
在运行时判断任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判断任意一个类所具有的方法和属性;
在运行时调用任意一个对象的方法。
2、java反射常用API
使用java反射技术常用的类如下:
Class类:反射的核心类,反射的所有操作都是围绕该类来生成的。通过Class类,可以获得类的属性、方法等内容信息;
Field类:表示类的属性,可以获取和设置类中属性的值;
Method类:表示类的方法,可以用来获取类中方法的信息,或者执行方法;
Constructor类:表示类的构造方法。
在java程序中使用反射的基本步骤如下:
1)导入java.lang.reflect.*;
2)获得需要操作的类的java.lang.Class对象;
3)调用Class的方法获取Field、Method等对象;
4)使用反射API进行操作。
三、反射的应用
1、获取类的信息
通过反射获取类的信息分两步:首先获取Class对象,然后通过Class对象获取信息
1)获取Class对象
每个类被加载后,系统就会为该类生成一个Class对象,通过该Class对象就可以访问到Java虚拟机中的这个类。
Java程序中获得Class对象通常有以下3种方式:
① 调用对象的getClass()
getClass()方法是Object中的一个方法,所有java对象都可调用该方法,该方法会返回该对象所属类对应的Class对象
Student stu = new Student();
Class class = stu.getClass();
② 调用类的class属性
调用某个类的class属性可获取该类对应的Class属性,这种方式需在编译器就知道类名。
Class class = Student.
③ 使用Class类的forName()静态方法
使用Class类的forName()静态方法也可获取该类对应的Class属性。该方式需传入字符串参数,该参数值是某个类的全名(完整报名+类名),否则会抛ClassNotFoundException
Class class = Class.forName(“com.vnb.test.Student”);
后两种方式都是根据类来获取该类对应的Class属性,而通过调用某个类的class属性来获取对应的Class对象这种方式更有优势,原因如以下两点:
代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在;
无需调用方法,程序性能更高。
2)从Class对象获取信息
① 访问Class对应的类所包含的构造方法
getConstructors()/getDeclaredConstructors()
② 访问Class对应的类所包含的方法
getMethods()/getDeclaredMethods()
③ 访问Class对应的类所包含的属性
getFields()/getDeclaredFields()
④ 访问Class对应的类所包含的注释
getAnnotations()/getDeclaredAnnotations()
⑤ 访问Class对应的类的其他信息
内部类、外部类、全部接口、所有修饰符、此类的包、类名、类的简称、该类超类的Class对象
Method、Contructor、Field这3个类都定义在java.lang.reflect包下,并实现了java.lang.reflect.Member接口,程序可以通过Method来执行对应的方法,通过Constructor来调用对应的构造方法创建对象,通过Field对象直接访问并修改对象的属性值。
2、创建对象
通过反射来创建对象有如下两种方式:
(1)使用Class对象的newInstance()方法创建对象
要求该Class对象对应的类有默认的构造方法,而执行newInstance()实际上是利用默认的构造方法来创建该类的实例
Class class = Date.
Date date = (Date)class.newInstance();
(2)使用Constructor对象创建对象
要先使用Class对象,获取指定的Constructor对象,再用Constructor对象的newInstance()来创建对象对应类的实例。通过这种方式可以选择使用某个类的指定构造方法来创建实例
Class class = Date.
Constructor con = class.getConstructor(long.class);
Date date = (Date con .newInstance(1987);
3、访问类的属性
使用Field对象获取对象的属性。并通过Field对象可以对属性进行取值或赋值操作
如:class Student{
Pubic class Test(){
Student p = new Student();
Class cla = Student.
//使用getDeclaredField()可获取各种访问修饰符级别的属性
Field nameField = cla.getDeclaredField(“name”);
//设置通过反射访问该Field时取消权限检查
nameField.setAccessible(true);
//通过set()为Student对象设置Field值
nameField.set(p,”Jack”);
4、访问类的方法
使用Method对象可以调用对象的方法。在Method类中包含一个invoke()方法,定义如下:
Object invoke(Object obj,Object args);
Obj是执行该方法的对象;args是执行该方法传的参数
class Student{
Public void setName(String name){this.name = name}
Pubic class Test(){
Class cla = Student.
Student p = new Student();
//得到setName()方法
Method met = cla.getMethod(“setName”,String.class);
//调用setName()方法,为name赋值
Met.invoke(p,”Jack”);
若Student类中setName()方法设置访问修饰符为私有,则会抛NoSuchMethodE这是由于Method的invoke()方法要求必须拥有对应的方法访问权限,若没有可使用setAccessible()设置,true表取消权限检查,false表使用权限检查
5、使用Array类动态创建和访问数组
Java.lang.reflect包下有一个Array类,该类对象可以代表所有的数组。程序可以通过使用Array类来动态的创建数组、操作数组元素等。
//创建数组,类型为String,长度10
Object arr = Array.newInstance(String.class,10);
Array.set(arr,5,”Jack”);
Array.get(arr,5);
使用反射机制开发代码灵活性强,但不能乱用,因为通过反射机制创建对象性能稍低。
实际上,反射机制只有在程序需要动态创建某个类的对象时才会考虑使用
通常在开发通用性比较广、基础平台时才会大量使用。因为很多java框架中都需要根据配置文件来创建java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据字符串类名来创建java对象就必须使用反射机制
实际开发中没必要使用反射来访问已知的类的属性和方法,只有在程序需要动态创建某个类的对象时才会考虑使用。例如,从配置文件中读取以字符串形式表示的类时,就需要用反射来获取它的方法和属性
5、请介绍一下Junit4.4 Test Class 的生命周期
6、Junit中@Rule注解是什么意思?如何用@Test测试异常?如果一个Junit测试方法的返回类型为String,那么将会发生什么?
1、你们这个项目中,安全性是怎么解决的
cookies 安全设置
HttpOnly 主要是为了限制web页面程序的浏览器端script程序读取cookie
在web.xml 中配置
&session-config&
&cookie-config&
&http-only&true&/http-only&
&/cookie-config&
&session-timeout&1200&/session-timeout&
&/session-config&
在tomcat 中的server.xml 中配置
&Context path="" docBase=""
useHttpOnly="true" /&
http方法访问方式设置
WebDAV (Web-based Distributed Authoring and Versioning)是基于 HTTP 1.1 的一个通信协议。它为 HTTP 1.1 添加了一些扩展(就是在 GET、POST、HEAD 等几个 HTTP 标准方法以外添加了一些新的方法)
,使得应用程序可以直接将文件写到 Web Server 上,并且在写文件时候可以对文件加锁,写完后对文件解锁,还可以支持对文件所做的版本控制。这个协议的出现极大地增加了 Web 作为一种创作媒体对于我们的价值。基于 WebDAV 可以实现一个功能强大的内容管理系统或者配置管理系统。
弱口令漏洞
解决方案:最好使用至少6位的数字、字母及特殊字符组合作为密码。数据库不要存储明文密码,应存储MD5加密后的密文,由于目前普通的MD5加密已经可以被破解,最好可以多重MD5加密,或者多种加密方式叠加组合
未使用用户名及密码登录后台可直接输入后台URL登录系统。
解决方案:通过配置filter来过滤掉无效用户的连接请求
JSP页面抛出的异常可能暴露程序信息。有经验的入侵者,可以从JSP程序的异常中获取很多信息,比如程序的部分架构、程序的物理路径、SQL注入爆出来的信息等。
解决方案:自定义一个Exception,将异常信息包装起来不要抛到页面上
合法用户“注销”后,在未关闭浏览器的情况下,点击浏览器“后退”按钮,可从本地页面缓存中读取数据,绕过了服务端filter过滤。
解决方案:配置filter对存放敏感信息的页面限制页面缓存
SQL注入漏洞
文件上传漏洞。前台仅使用JS对文件后缀做了过滤,这只能针对普通的用户,而恶意攻击者完全可以修改表单去掉JS校验。
解决方案:前台JS过滤加服务器端程序过滤。具体过滤掉哪些文件类型视具体情况而定
Java WEB容器默认配置漏洞。如TOMCAT后台管理漏洞,默认用户名及密码登录后可直接上传war文件获取webshell。
解决方案:最好删除,如需要使用它来管理维护,可更改其默认路径,口令及密码。
日志,建议增加服务的访问日志,记录来访者的 IP ,传递参数,对后台操作用户建立日志,记录其操作内容。完善的日志记录可以帮助你发现潜在的危险,找到已经发生的问题。
2、前端以什么方式发送请求的
3、请问你们安徽高速项目的场景就是什么
4、在你们的无跳转支付(token版)中,token的使用场景和作用是什么
5、Tomcat 和 Weblogic 的区别是什么
Tomcat和Weblogic的相同点:
Tomcat是Apache基金会提供的Servlet容器,它支持JSP, Servlet和JDBC等J2EE关键技术,所以用户可以用Tomcat开发基于数据库,Servlet和JSP页面的Web应用。
Tomcat和Weblogic的不同点:
Tomcat不是EJB容器;也就是说,Tomcat不支持J2EE的重要技术之一,EJB。那么,使用EJB组件开发的Web应用程序就无法在Tomcat下面运行。众所周知,EJB是分布式应用程序的核心技术,所以说凡是需要使用EJB来开发的应用(例如,银行、电信等大型的分布式应用系统)就不能用Tomcat了。webLogic可以进行EJB发布、jndi数据源的配置;可以进行设置实现日志管理,内存管理,资源配置管理;通过有限的信息查找故障,排除故障,但Tomcat却不能;
用WebLogic运行标准的java可能并不是最好的方式,WebLogic里支持他自己的一些东西,这些东西虽然是在纯java基础上开发的,但其他工具里都没有。WebLogic Server凭借其出色的群集技术,拥有处理关键Web应用系统问题所需的性能、可扩展性和高可用性。WebLogic
Server既实现了网页群集,也实现了EJB组件群集,而且不需要任何专门的硬件或操作系统支持。网页群集可以实现透明的复制、负载平衡以及表示内容容错 。
无论是网页群集,还是组件群集,对于电子商务解决方案所要求的可扩展性和可用性都是至关重要的。共享的客户机/服务器和数据库连接以及数据缓存和EJB都增强了性能表现。这是其它Web应用系统所不具备的。所以,在扩展性方面WebLogic是远远超越了Tomcat。
Tomcat开源免费,WebLogic不开源不免费。
6、weblogic的热部署是怎么实现的(你们怎么在weblogic启动的情况下去修改文件的)
Weblogic的控制台中有如下三个配置的页面:
servlet-reload-check-secs:是否执行 servlet检查,-1永不检查(生产环境默认),0总是检查,1每秒检查(开发环境中的默认值)
resource-reload-check-secs: Web应用程序范围内资源路径中发现的缓存资源执行元数据缓存:-1 表示元数据进行缓存,但从不对磁盘进行检查;0 表示不执行元数据缓存。持续更改文件的客户必须将该参数设置为大于或等于
0的一个值;1 表示每秒重新加载一次。该值为开发环境中的默认值
page-check-seconds:查看 JSP文件是否已更改:-1 表示永不检查页面。该值为生产环境中的默认值。 值 0 表示总是检查页面。 值 1 表示每秒检查一次页面。该值为开发环境中的默认值。
实现步骤:1:修改weblogic的启动参数
使用domain中的startWebLogic.cmd来启动weblogic的话,则在domain/bin下的setDomainEnv.cmd中将该参数配置上,在该文件最后添加设置参数命令:set JAVA_OPTIONS=%JAVA_OPTIONS% -Dorg.apache.tapestry.disable-caching=true -Dorg.apache.tapestry.enable-reset-service=true
2、检查weblogic.xml配置文件,其中如果有:
&container-descriptor&
&servlet-reload-check-secs&0&/servlet-reload-check-secs&
&resource-reload-check-secs&0&/resource-reload-check-secs&
&/container-descriptor&
&jsp-descriptor&
&jsp-param&
&param-name&pageCheckSeconds&/param-name&
&param-value&0&/param-value&
&/jsp-param&
&/jsp-descriptor&
请将上述数值都修改为0
3、配置Eclipse中的工程自动编译
4、检查ie选项中是否设置了每次访问本页时检查最新
7、微信公众号后台系统怎么去调用腾讯的接口的
配置微信公众号平台后台地址;验签,加解密,加签,封装参数;调用接口
其他人面试题目:
1、springBoot知识:为什么要使用这个技术,有哪些缺点,怎么去避免
使用 Spring 项目引导页面可以在几秒构建一个项目方便对外输出各种形式的服务,如 REST API、WebSocket、Web、Streaming、Tasks非常简洁的安全策略集成支持关系数据库和非关系数据库支持运行期内嵌容器,如 Tomcat、Jetty强大的开发包,支持热启动自动管理依赖自带应用监控支持各种 IED,如 IntelliJ IDEA 、NetBeans缺点是集成度较高,使用过程中不太容易了解底层。
感觉Spring Boot 比较适合做微服务,不适合做比较大型的项目
2、平时使用了哪些测试工具,怎么进行测试,怎么覆盖全方位的测试案例
Loaderrunner,jmeter
3、linux操作系统
4、mybatis动态sql语句的形成、mybatis缓存、mybatis怎么做分页
mybatis动态sql语句的形成:sqlBuilder、各种xml配置标签 if where select等
MyBatis 提供了查询缓存来缓存数据,以提高查询的性能。MyBatis 的缓存分为一级缓存和二级缓存。
一级缓存是 SqlSession 级别的缓存
二级缓存是 mapper 级别的缓存,多个 SqlSession 共享
一级缓存是 SqlSession 级别的缓存,是基于 HashMap 的本地缓存。不同的 SqlSession 之间的缓存数据区域互不影响。
一级缓存的作用域是 SqlSession 范围,当同一个 SqlSession 执行两次相同的 sql 语句时,第一次执行完后会将数据库中查询的数据写到缓存,第二次查询时直接从缓存获取不用去数据库查询。当 SqlSession 执行 insert、update、delete 操做并提交到数据库时,会清空缓存,保证缓存中的信息是最新的。
MyBatis 默认开启一级缓存。
二级缓存是 mapper 级别的缓存,同样是基于 HashMap 进行存储,多个 SqlSession 可以共用二级缓存,其作用域是 mapper 的同一个 namespace。不同的 SqlSession 两次执行相同的 namespace 下的 sql
语句,会执行相同的 sql,第二次查询只会查询第一次查询时读取数据库后写到缓存的数据,不会再去数据库查询。
MyBatis 默认没有开启二级缓存,开启只需在配置文件中写入如下代码:
&settings&
&setting name="cacheEnabled" value="true"/&
&/settings&
5、线程并发问题
6、sql优化
7、JVM的了解
8、用过哪些前端框架:jquery,easyUI,jqGrid
9、项目用了什么协议,与前台是怎么进行交互,然后怎么保证安全,遇到出错怎么一步一步解决
10、MVC模式的理解,细分为MVC三层模式中每一层的含义,假设在没有MVC模式的情况下,自己要如何去开发
11、在项目中对junit单元测试的使用情况;怎么进行测试的(比如说等值测试、边界值测试、正例和反例测试等等)
12、获取线程中的计数器的值(多线程的通信问题):http://blog.chinaunix.net/uid--id-3016929.html
13、针对自己的功能模块进行性能测试,如何测试自己写的各个功能模块的性能、执行时间效率
14、tomcat发布到window和linux的区别有什么不同
配置文件路径不同;linux下jdk为opensdk,tomcat下为javasdk
15、你所了解的spring
16、版本发布和管理的软件:svn,cvs,github,TortoiseSVN
17、spring的bean的注入方式有什么不同:get/setter方法注入,构造器注入,注解方式注入
18、某个线程正在执行时,文件被修改怎么办
arraylist和linklist区别
Collection接口:最基本集合接口,可存储一组不唯一、无序的对象。
List接口:继承自Collection,用户能使用索引访问List接口中的元素,类似数组,允许存放重复元素,可存储一组不唯一、有序的对象。常用实现类:ArrayList、LinkedList
Arraylist:有序,长度可变的数组,亦称动态数组,且与数组采取相同存储方式,在内存中分配连续的空间;与数组不同的是,Arraylist可添加任何类型的数据,并且将添加的数据都转换成Object类型,而数组只能添加统一数据类型的数据
Arraylist可使用索引直接获取元素,所以优点是遍历元素和随机访问元素的效率比较高;但是由于Arraylist使用的是和数组相同的存储方式,在内存中分配连续空间,所以在添加和删除非尾部元素时会导致后面所有元素的移动,这就造成了Arraylist在插入、删除等操作频繁时性能低下,所以数据操作频繁时最好用LinkedList。
List list = new ArrayList();将接口list的引用指向了实现类ArrayList对象(多态、低耦合)
LinkedList:无序,是List接口的链接列表实现。支持实现所有List接口可选的列表的操作,并且允许元素是任何数据,包括null。采用链表存储方式,优点在于插入、删除元素时效率比较高,但是查找效率很低,除ArrayList所包含的方法外,还提供在LinkedList的首部或尾部进行插入、删除操作
LinkedList list = new LinkedList();
除了LinkedList中特有的操作首尾部的方法外,其他大部分一样,主要是由于它们都是List接口的实现类。由于ArrayList采用和数组一样的连续的顺序存储方式,当对数据频繁检索时效率高,而LinkedList采用链表存储方式,当对数据添加、删除或修改比较多时,建议选择LinkedList存储数据。
Set接口:集合中的对象并不按特定的方式排序,并且不能保存重复的对象,可存储一组唯一、无序的对象。常用实现类:HashSet
若需要在很多数据中查找某个数据,LinkedList的数据结构决定其效率低下,ArrayList在不知道数据的索引且需全部遍历的情况下,效率也很低。于是有了HashSet。
HashSet: 查找效率高。集合内的元素无序排列;非线程安全;允许集合元素为null;HashSet不存在get()方法,所以Set接口不能使用普通for循环遍历,只能使用Iterator接口
Iterator : 凡是由Collection派生而来的接口或者类,都实现了iterator()方法,返回一个Iterator对象
方法:hasNext():判断是否存在下一个可访问的元素,若有则可进行迭代,返回true
next():返回要访问的下一个元素
List list = new ArrayList();
List.add(“张三”);
List.add(“李四”);
Iterator it = list.iterator();
While(it.hasNext()){
String name = (String)it.next();
Map:存储一组key-value键值对,提供key到value的映射,通过key检索value。Map接口中key不要求有序,不允许重复;value同样不要求有序,但允许重复。常用的实现类HashMap、HashTable
HashMap:查询指定元素效率高。数据添加到HashMap集合后,所有数据类型将转换为Object,所以从其中获取数据时需要强转;HashMap不保证映射的顺序,特别是不保证顺序很久不变;遍历HashMap集合时可以遍历键集(keySet())和值集(values())。
HashTable:类实现一个,该哈希表将键映射到相应的值。任何非
null对象都可以用作键或值。为了成功地在哈希表中存储和获取对象,用作键的对象必须实现hashCode方法和equals方法。
(1)Hashtable是一个散列表,它存储的内容是键值对(key-value)映射。
(2)Hashtable继承于Dictionary(声明了操作"键值对"函数接口的抽象类),实现了Map、Cloneable、java.io.Serializable接口。
(3)Hashtable的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。
成员变量:
(1)table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。
(2)count是Hashtable的大小,它是Hashtable保存的键值对的数量。
(3)threshold是Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值="容量*加载因子"。
(4)loadFactor就是加载因子。
(5)modCount是用来实现fail-fast机制的
当我们想要想Hashtable中添加元素的时候,首先计算key的hash值,然
后通过hash值确定在table数组中的索引位置,最后将value值替换或者插入新的元素,如果容器的数量达到阈值,就会进行扩充。
Hashtable和HashMap到底有哪些不同呢
(1)基类不同:HashTable基于Dictionary类,而HashMap是基于AbstractMap。Dictionary是什么?它是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作。
(2)null不同:HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。
(3)线程安全:HashMap时单线程安全的,Hashtable是多线程安全的。ConcurrentHashMap也是多线程安全的
遍历不同:HashMap仅支持Iterator的遍历方式,Hashtable支持Iterator和Enumeration两种遍历方式。
Collections类操作集合:包含大量静态方法,用于实现对集合元素的排序、查找和替换等操作。
Collections和Collection不同,前者是集合操作类,后者是集合接口。
排序是对集合的常见需求,要排序就得比较大小。Java中,若要实现一个类对象之间比较大小,那么这个类就必须实现Comparable接口。此接口强行对实现它的每个类的对象进行整体排序,称作自然排序。而类的compareTo()方法称为自然比较方法,用于比较此对象与指定对象的顺序,返回负整数、零或正整数。实现此接口的对象列表和数组可以通过Collections.sort()方(和Arrays.sort())进行自动排序。
List要排序也必须实现Comparable接口,Map接口本身是无序的不能进行排序。
使用Collections.binarySearch(list,student3)对集合进行查找,返回索引位置
使用Collections.fill()实现替换集合元素(将list中所有集合元素替换为相同元素)
泛型集合:List中add()方法的参数类型是Object,List及其子类实现类和接口无论放入什么对象都会被转换成Object,而通过get()取出元素时必须进行强转;Map接口使用Iterator的next()方法获取元素时存在同样问题。
因此使用泛型集合在创建集合对象时就指定集合中元素类型,从集合中取出元素时也无需进行强转,并且把非指定类型对象放入集合时会出现编译错误。因此泛型从根本上改进了集合的使用和安全问题。
深入理解泛型:集合中使用泛型只是泛型的其中一种应用,在接口、类、方法等方面也有广泛应用。泛型的本质就是参数化类型。参数化类型的重要性在于创建一些类、接口、方法,其所操作的数据类型被指定为参数。
两个重要概念:
参数化类型:参数化类型包含一个类或接口,以及实际的类型参数列表。
类型变量:是一种非限定性标识符,用来指定类、接口或者方法的类型。如&E&、&T&
泛型类:访问修饰符 class className&typeList&
typeList表示类型参数列表,每个类型变量间用逗号分隔
eg:public class GenericClass&T&{.....}
创建泛型类型实例的语法:new className&TypeList&(argList)
TypeList表定义的类型参数列表,每个类型变量间用逗号分隔
argList表实际传递的类型参数列表,每个类型变量间用逗号分隔
泛型接口:泛型接口就是拥有一个或多个类型变量的接口
定义:访问修饰符 interface interfaceName&TypeList&
eg:public interface TestInterface&T&{
public T print(T t);
泛型类实现泛型接口的语法:
Class className&TypeList& implements interfaceName&Typelist&
注:实例化类时也可interfaceName&具体类型& className = new className&具体类型&();
泛型方法:不管是泛型类还是非泛型类,都可定义泛型方法
访问修饰符 &参数类型&返回值 方法名(类型参数列表)
Eg:public &String& void showName(String s){}
注:泛型方法中,类型变量是放置在访问修饰符与返回值之间
多个参数的泛型类,如HashMap&K,V&
泛型类派生子类(向上泛型和向下泛型)
泛型类也可以继承,但继承了泛型类的子类也必须是泛型
Eg: class 子类&T& extends父类&T&
? 占位符 ||通配符
格式:Collection&?& ... ...
当集合不确定将要接收什么类型的对象时,使用“?”作为接收未来传递来的对象的通配符。
缺点:不能使用对象的特有方法,这一点就是面向接口编程的缺点,可以通过强制转换来使用具体对象的特有方法。
A. 向上限定
只能存储E,或E的子类。
添加:以List的add方法为例。
B. 向下限定
只能存储E,或E的父类。
比较:以TreeSet的比较器为例。
package com.lxh.
import java.util.*;
public class AdvancedGeneric {
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add(new SuperMan("张三",23));
al.add(new SuperMan("张四",24));
al.add(new SuperMan("张五",25));
al.add(new SuperMan("张三",23));
AdvancedGeneric.printIterator(al);
TreeSet ts = new TreeSet();
ts.add(new SuperMan("张三",23));
ts.add(new SuperMan("张四",24));
ts.add(new SuperMan("张五",25));
ts.add(new SuperMan("张三",23));
AdvancedGeneric.printIterator(ts);
* 通配符方式。
* 缺点:不能使用对象中元素的特有方法了。
public static void printIterator(Collection&? extends Man& al) {
Iterator&? extends Man& it = al.iterator();
while(it.hasNext()) {
Man m = it.next();
System.out.println(m.getName());
抽象类和接口的区别
A.一个类可以实现多个接口 ,但却只能继承最多一个抽象类。
B.抽象类可以包含具体的方法 , 接口的所有方法都是抽象的。
C.抽象类可以声明和使用字段 ,接口则不能,但接口可以创建静态的final常量。
D.接口的方法都是public的,抽象类的方法可以是public,protected,private或者默认的package;
E.抽象类可以定义构造函数,接口却不能
冒泡排序的原理和实现
A、原理:每一次循环都两两比较,直到最后,如下图就是两次循环的比较方式
B、代码实现
public class BubbleSortTest {
// 冒泡排序法
public static void bubbleSort(int[] a) {
for (int i = 0; i & a.length - 1; i++)// 每循环一次就少一个数
for (int j = 0; j & a.length - i - 1; j++) {
if (a[j] & a[j + 1]) {
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] =
System.out.println("第" + (i + 1) + "趟排序");
for (int k = 0; k & a. k++) {
System.out.print(a[k] + " ");
System.out.println();
public static void main(String[] args) {
int[] array = { 4, 7, 8, 9, 3, 2 };
bubbleSort(array);
内部类方式实现单例模式
public class SingletonTest(){
//屏蔽外部的new实例
private SingletonTest(){
//静态内部类,用于持有唯一SingletonTest实例
private static class StaticInnerClass{
private static SingletonTest INSTANCE = new SingletonTest();
//公开的唯一访问点
public static SingletonTest getInstance(){
return StaticInnerClass.INSTANCE;
二分查找填写代码
A:原理:二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
B:代码实现
转自:http://blog.csdn.net/lvwei1990/article/details/
* 二分查找算法
* @param srcArray 有序数组
* @param key 查找元素
* @return key的数组下标,没找到返回-1
public class HalfSearchTest {
public static void main(String[] args){
int[] srcArray = {3,5,11,17,21,23,28,30,32,50,64,78,81,95,101};
System.out.println("递归实现:"+binSearch(srcArray,0,srcArray.length-1,94));
System.out.println("while循环实现:"+binSearch(srcArray,65));
// 二分查找普通循环实现
private static int binSearch(int[] srcArray, int key) {
int mid=srcArray.length/2;
if(key==srcArray[mid]){
int start=0;
int end=srcArray.length-1;
while(start&=end){
mid=(start+end)/2;
if(key&srcArray[mid]){
end=mid-1;
}else if(key&srcArray[mid]){
start=mid+1;
return -1;
// 二分查找递归实现
private static int binSearch(int[] srcArray, int start, int end, int key) {
int mid=(start+end)/2;
if(key==srcArray[mid]){
if (start&=end) {
return -1;
} else if(key&srcArray[mid]){
return binSearch(srcArray, mid+1,end,key);
}else if(key&srcArray[mid]){
return binSearch(srcArray, start,mid-1,key);
return -1;
6. String 、StringBuffer、Stringbuilder区别
字符串的比较:
== :比较两个字符串对象在内存中的地址,即判断是否是同一个字符串对象
equals():比较两个字符串对象的值
equals()对字符的大小写也在检查范围之内,因此有equalsIgnoreCase()比较字符串对象的值时忽略字符的大小写
字符串的连接:字符串1.concat(字符串2)
String str = new String(“你好”);
String name = new String(“张三”);
String sentence = str.contact(name);
输出“你好张三”
Indexof()/lastIndexOf():得到结果为index,从0开始-1结束,
”index!=-1&&index!=0”表示存在且不在首位
字符串拆分:字符串.split(separator,limit)
Separator:可选,拆分字符串时使用一个或多个字符,不选则返回包含该字符串所有单个字符的元素数组
Limit:限制返回数组中的元素个数
StringBuffer:String的增强类,比String类更高效地存储字符串的一种引用数据类型,特别是对字符串进行连接操作时,可大大提高效率
toString():将StringBuffer转为String类
append():追加任意类型的值到字符串后;但String下的contact()只能追加字符串到字符串后
Insert():字符串.insert(位置,参数)
将参数插入到指定位置后并返回,参数可以是包括String的任何类型
StringBuilder:可变的字符序列,被设计用作StringBuffer的简易替换,大多数实现中比StringBuffer快,方法基本一样
String类、StringBuffer类、StringBuilder类比较:
A、String类:字符串常量。String是不可变对象,在每次对String类型进行改变时等同于生成一个新的String对象,然后指向新的String对象,所以经常改变内容的字符串最好不要用String类型,因为每次生成对象都会都系统性能造成影响。
B、StringBuffer类:字符串变量。StringBuffer是可变的字符串,在每次对StringBuffer对象进行改变时,会对StringBuffer本身进行操作而不是产生新的对象,再改变引用。所以在字符串对象经常改变情况下推荐使用。字符串连接操作中,StringBuffer类的效率要比String类高。
String str = new String(“welcome to”);
Str+=”here”;
此句实际上是通过建立一个StringBuffer,让其调用append()后再转化成String,这样String的连接操作比StringBuffer多一些附加操作,所以效率就低。并且String对象的不可变性也会影响性能。
C、StringBuilder类:字符串变量。StringBuffer是线程安全的,可提供同步方法,StringBuilder是单线程的、非线程安全的,不提供同步,理论上效率更高
7. 1%2抛异常加循环写输出
8. 不用第三方变量交换a和b的值
转自:https://www.cnblogs.com/hz0356/p/3947240.html
可以用三种算法来实现:1)算术运算;2)指针地址操作;3)位运算。
1) 算术运算
简单来说,就是通过普通的+和-运算来实现。代码如下:
a=10;b=12;
a=b-a; //a=2;b=12
b=b-a; //a=2;b=10
a=b+a; //a=12;b=10
通过以上运算,a和b中的值就进行了交换。表面上看起来很简单,但是不容易想到,尤其是在习惯标准算法之后。
它的原理是:把a、b看做数轴上的点,围绕两点间的距离来进行计算。
具体过程:第一句“a=b-a”求出ab两点的距离,并且将其保存在a中;第二句“b=b-a”求出a到原点的距离(b到原点的距离与ab两点距离之差),并且将其保存在b中;第三句“a=b+a”求出b到原点的距离(a到原点距离与ab两点距离之和),并且将其保存在a中。完成交换。
此算法与标准算法相比,多了三个计算的过程,但是没有借助临时变量。(以下称为算术算法)
2) 指针地址操作
因为对地址的操作实际上进行的是整数运算,比如:两个地址相减得到一个整数,表示两个变量在内存中的储存位置隔了多少个字节;地址和一个整数相加即“a+10”表示以a为基地址的在a后10个a类数据单元的地址。所以理论上可以通过和算术算法类似的运算来完成地址的交换,从而达到交换变量的目的。即:
int *a,*b; //假设
*a=new int(10);
*b=new int(20); //&a=0xh,&b=0xh
a=(int*)(b-a); //&a=0xh,&b=0xh
b=(int*)(b-a); //&a=0xh,&b=0xh
a=(int*)(b+int(a)); //&a=0xh,&b=0xh
通过以上运算a、b的地址真的已经完成了交换,且a指向了原先b指向的值,b指向原先a指向的值了吗?上面的代码可以通过编译,但是执行结果却令人匪夷所思!原因何在?
首先必须了解,操作系统把内存分为几个区域:系统代码/数据区、应用程序代码/数据区、堆栈区、全局数据区等等。在编译源程序时,常量、全局变量等都放入全局数据区,局部变量、动态变量则放入堆栈区。这样当算法执行到“a=(int*)(b-a)”时,a的值并不是0xh,而是要加上变量a所在内存区的基地址,实际的结果是:0x008f0200h,其中0x008f即为基地址,0200即为a在该内存区的位移。它是由编译器自动添加的。因此导致以后的地址计算均不正确,使得a,b指向所在区的其他内存单元。再次,地址运算不能出现负数,即当a的地址大于b的地址时,b-a&0,系统自动采用补码的形式表示负的位移,由此会产生错误,导致与前面同样的结果。
有办法解决吗?当然!以下是改进的算法:
a=(int*)(b-a);
b=(int*)(b-(int(a)&0x0000ffff));
a=(int*)(b+(int(a)&0x0000ffff));
b=(int*)(a-b);
a=(int*)(a-(int(b)&0x0000ffff));
b=(int*)(a+(int(b)&0x0000ffff));
算法做的最大改进就是采用位运算中的与运算“int(a)&0x0000ffff”,因为地址中高16位为段地址,后16位为位移地址,将它和0x0000ffff进行与运算后,段地址被屏蔽,只保留位移地址。这样就原始算法吻合,从而得到正确的结果。
此算法同样没有使用第三变量就完成了值的交换,与算术算法比较它显得不好理解,但是它有它的优点即在交换很大的数据类型时,它的执行速度比算术算法快。因为它交换的时地址,而变量值在内存中是没有移动过的。(以下称为地址算法)
3)** 位运算
通过异或运算也能实现变量的交换,这也许是最为神奇的,请看以下代码:
int a=10,b=12; //a=1010^b=1100;
a=a^b; //a=0110^b=1100;
b=a^b; //a=0110^b=1010;
a=a^b; //a=1100=12;b=1010;
此算法能够实现是由异或运算的特点决定的,通过异或运算能够使数据中的某些位翻转,其他位不变。这就意味着任意一个数与任意一个给定的值连续异或两次,值不变。
即:a^b^b=a。将a=a^b代入b=a^b则得b=a^b^b=a;同理可以得到a=b^a^a=b;轻松完成交换。
以上三个算法均实现了不借助其他变量来完成两个变量值的交换,相比较而言算术算法和位算法计算量相当,地址算法中计算较复杂,却可以很轻松的实现大类型(比如自定义的类或结构)的交换,而前两种只能进行整形数据的交换(理论上重载“^”运算符,也可以实现任意结构的交换)。
9. 选择题类型:线程,字符串,判断程序的输出
10. 线程定义
一、线程概述
计算机操作系统多采用多任务和分时设计,多任务是指在一个操作系统中可以同时运行多个程序。例如可以在使用QQ聊天的同时听音乐,即多个独立运行的任务,每个任务对应一个进程,每个进程又可产生多个线程。
程序是对数据描述与操作的代码的集合,如office的Word、暴风影音等应用程序。
进程是程序的一次动态执行过程,它对应了代码从加载、执行至执行完毕的一个完整过程,这个过程也就是进程本身从产生、发展至消亡的过程。操作系统同时管理一个计算机系统中的多个进程,让计算机系统中的多个进程轮流使用CPU资源,或者共享操作系统的其他资源。
进程的特点:
(1)进程是系统运行程序的基本单位
(2)每个进程都有自己独立的一块内存空间、一组系统资源
(3)每一个进程的内部数据和状态都是完全独立的
当一个应用程序运行的时候会产生一个进程
线程是进程中执行运算的最小单位,一个进程在其执行过程中可以产生多个线程,而线程必须在某个进程内执行。
线程是进程内部的执行单元,可完成一个独立的任务控制流程,如果一个进程中,同时运用多个线程来完成不同的任务,就叫多线程。
线程按级别分为核心级线程和用户级线程
核心级线程:和系统任务相关的线程,负责处理不同进程之间的多个线程。可按照同一相对优先调度方法对线程进行调度,使它们有条不紊的工作,充分利用计算机的软、硬件资源。
用户级线程:在开发程序时,由于程序需要而编写的线程。这些线程的创建、执行和消亡都是在编写应用程序时进行控制的。
线程和进程的联系与区别:
(1)一个进程至少有一个线程
(2)资源分配个进程,同一进程的所有线程共享该进程的所有资源
(3)处理机分配给线程,即真正在处理机上运行的是线程
3、多线程的优势
(1)带来更好的用户体验,避免因程序过慢而导致的计算机死机或者白屏的情况
(2)很大限度的提高计算机系统的利用效率
(3)充分利用网络和系统资源
二、在java中实现多线程
每个线程至少自动拥有一个线程,称主线程。当程序加载到内存时,启动主线程。Java程序的main()方法是主线程的入口,运行java程序时,会先执行这个方法。
线程的使用过程:
(1)定义线程并指定该线程需执行的代码,即完成的功能
(2)创建线程对象
(3)启动线程
(4)终止线程
定义(创建)线程的2中方式:
1、使用Thread类创建线程
java.lang.Thread支持多线程编程,该类提供大量方法来控制和操作线程
Run():执行任务操作的方法
Start():使该线程开始执行
Sleep(long millis):让当前正在执行的线程休眠指定的毫秒数(暂停执行)
getName():获取线程名称
Int getPriority():返回线程的优先级
Void setPriority():更改线程优先级
Thread.State getState():返回该线程的状态
isAlive():测试该线程是否处于活动状态
Join():等待该线程终止
Interrupt():中断线程
Yield():暂停执行当前线程,并执行其他线程
创建线程时继承Thread类并重写Thread类的run()方法。Thread类的run()方法是线程要执行操作任务的方法,所以线程的执行操作的代码都写在run()方法中,并通过start()方法来启动并开始执行线程。缺点:如果定义的类已经继承了其他的类就不能继承Thread类了
Public class MyThread extends Thread{
Private int count = 0;
Public void run(){
While(count&100){
Public class Test{
MyThread myThread = new MyThread();
myThread .start();
2、使用Runnable接口创建线程java.lang.Runnable
Runnable接口中声明了一个run()方法,即public void run()。一个类可以通过实现Runnable接口并实现其run()方法完成线程的所有活动,已实现的run()方法称为该对象的线程体。任何实现了Runnable接口的对象都可以作为一个线程的目标对象。
Public class MyThread implements Runnable{
Private int count = 0;
Public void run(){
While(count&100){
Public class Test{
Thread thread = new Thread(new MyThread());
thread .start();
两种创建方式区别:
(1)直接继承Thread类的方式,编写简单,可以直接操作线程,适用于单重继承的情况
(2)实现Runnable接口的方式,当一个线程继承另一个类时,只能用此方式创建线程,而且这种方式可以使多个线程之间使用同一个Runnable对象。
(3)通过线程池,实现Callable接口
三、多线程的状态
1、新生状态
创建线程对象后,尚未调用其start()方法之前,此线程就有了生命,此线程只是一个空对象,系统没有分配资源。此时只能启动和终止线程,任何其他操作都会报异常。
2、可运行状态
调用start()启动线程后,系统为该线程分配除CPU外的所需资源,这个线程就有了生命,线程处于可运行状态,在此状态下线程可能正在运行,也可能尚未运行。对于只有一个CPU的机器,任何时刻只能有一个处于可运行状态的线程占用处理机,获得CPU资源,此时系统真正运行线程的run()方法。
3、阻塞状态
“不可运行状态”,在得到一个特定时间之后会转回可运行状态。
线程被阻塞原因:
(1)调用了Thread类的sleep()方法
(2)一个线程执行到一个I/O操作时,如果I/O操作尚未完成,则线程将被阻塞
(3)如果一个线程执行需要得到一个对象的锁,而这个对象锁正在被其它线程占用
(4)Suspend()方法被调用导致线程被挂起(但suspend()方法容易导致死锁,已被JDK列为过期方法,基本不再使用)
4、死亡状态
Run()方法执行完毕、stop()被调用、运行过程中出现未捕获异常时都会导致线程进入死亡状态。
四、线程调度
当同一时刻有多个线程处于可运行状态,他们需要排队等待CPU资源,每个线程会自动获得一个线程的优先级,优先级的高低反应线程的重要或紧急程度。可运行状态的线程按优先级排队,线程调度依据优先级基础上的“先到先服务”原则。
线程调度管理器负责线程排队和CPU在线程间的分配,并按线程调度算法进行调度。当线程调度器选中某个线程时,该线程获得CPU资源进入可运行状态。
线程调度是抢占式调度,即如果在当前线程执行过程中一个更高优先级的线程进入可运行状态,则这个更高优先级的线程立即被调用。
1、线程优先级
1~10级,1最高,默认5。每个优先级对应一个Thread类的公用静态变量
Public static final int NORM_PRIORITY = 5;
Public static final int MIN_PRIORITY = 1;
Public static final int MAX_PRIORITY =10;
优先级可通过setPriority(int grade)进行更改。Eg.myThread.setPriority(3);
2、实现线程调度的方法
(1)Join()方法
当前线程暂停执行,等待调用该方法的线程结束后再继续执行本线程
MyThread tempjt = new MyThread(“MyThread”);
tempjt.start();
tempjt.join();
把该线程通过join()方法插入到主线程前面,等该线程执行完后才会继续执行主线程
Thread.currentThread().getName()获取当前线程名称
使用join()实现两个线程间数据传递
Public class Test{
Public static void main(String[] args) throws InterrupterException{
MyThread thread = new MyThread();
thread.start();
System.out.println(“values1:”+thread.value1);
System.out.println(“values2:”+thread.value2);
Public class Mythread extends Thread{
Public String value1;
Public String value2;
Public void run(){
value1 = “value1已赋值”;
value2 = “value2已赋值”;
输出结果value1:value2=null
出现上述结果原因:main()方法为主线程,在thread.start()后启动新线程执行run()方法,但是主线程在run()方法还没有赋值之前就已经进行了输出导致
解决:thread.start()方法后,加入thread.join(),就会在等待新启动线程执行完成后再继续执行主线程。
(2)sleep()方法
Public static void sleep(long millis)
Sleep()方法会让当前线程睡眠,即停止执行,线程由运行中的状态进入不可运行状态,当睡眠时间过后线程再进入可运行状态 Thread.sleep(5000)
(3)yield()方法
public static void yield()
暂停执行当前线程,允许其他线程执行,该线程仍处于可运行状态,不转为阻塞状态。此时系统选择其他相同或更高级别的线程,如无则执行当前线程。
调用yield()方法后,线程并不是处于阻塞状态,它可以和其他等待执行的线程竞争CPU资源,如果又抢占到了CPU资源,就会出现连续运行几次的情况。
sleep()和yield()区别:
① sleep()使当前线程处于被阻塞状态;yield()将当前线程转入暂停执行状态
② Sleep()即使没有其他线程等待运行,当前线程也会等待指定时间;yield()如果没有其他线程等待执行,则当前线程马上恢复执行
③ Sleep()其他等待执行的线程的机会是均等的;yield()会将优先级相同或更高的线程先执行后,再执行当前线程
五、线程同步的必要性
六、线程同步的实现
当两个或多个线程需要访问同一资源时,需要以某种顺序来确保该资源某一时刻只能被一个线程使用的方式就称为线程同步
同步的两种方式:同步方法、同步代码块;都采用关键字synchronized
1、同步方法
访问修饰符 synchronized 返回类型 方法名{...}
synchronized 访问修饰符 返回类型 方法名{...}
使用synchronized修饰的方法控制对类成员变量的访问。每个类对应一把锁,方法一旦执行,就独占该锁,知道该方法执行完后才释放锁,此后被阻塞的线程才能获得锁,重新进入可执行状态。这种机制确保同一时刻对应每一个实例,该实例声明为synchronized的所有方法只能有一个处于可执行状态,从而避免了类成员变量的访问冲突(可能会有多个方法同时声明为synchronized ,且同时对某个类成员变量进行访问操作)。
同步方法的缺陷:若将一个运行时间较长的方法声明成synchronized将会影响效率。例如,将线程中run()方法声明成synchronized,那么线程的整个生命周期都在run()里,只有等run()方法里所有都执行完成后才会执行其他线程。
2、同步代码块
synchronized(syncObject){
//需要访问控制的代码
Synchronized块中的代码必须要获得syncObject对象的锁才能执行,具体机制同同步方法。由于可针对任意代码块,且可任意指定上锁对象,故灵活性高。
Eg:synchronized(acct){
If(acct.getBalance()&=amt){
使用同步机制,容易导致死锁,即多个线程都处于等待状态而无法唤醒。此时等于处于等待状态的多个线程被占用系统资源,但无法运行,因此不会释放自身资源
如何避免死锁:
(1)线程以某个条件未满足而受阻,不能让其继续占有资源
(2)如果多个对象需要互斥访问,应确定线程获得锁的顺序,并保证整个程序以相反的顺序释放锁
七、线程间通信的必要性
很多情况下,需要不仅同步访问同一共享的资源,而且线程间还彼此牵制,相互通信。
八、在java中实现线程间通信
实现线程之间通信的3个方法:
wait():调用后,挂起当前线程,并释放共享资源锁;
notify():调用任意对象的notify()方法会在因调用该对象的wait()方法而阻塞的线程中随机选择一个解除阻塞,但要等到获得锁后才可真正执行;
notifyAll():调用了notifyAll()方法会将因调用该对象的wait()方法而阻塞的所有新城一次性全部解除阻塞。
这三个方法都是Object类中的final方法,被所有的类继承且不允许重写;
这三个方法只能在同步方法或同步块中使用,否则会抛出异常。
public class MyThread implements Runnable{
public static void main(String [] args){
MyThread myThread = new MyThread();
Thread thread ta= new Thread(myThread ,”线程ta”);
Thread thr

我要回帖

更多关于 javasql异常 的文章

 

随机推荐