技术面-Java部分
Java部分-必须掌握与记住的一些问题—By小楠
公众号:Android开发进阶1、Java中Error与Exception的联系与区别
1、Error和Exception都是Throwable的子类
2、Error类一般是指与虚拟机相关的问题 ,例如系统崩溃,虚拟机错误、内存空间不足、方法调用栈溢出等。
建议:导致应用程序中断,仅靠程序本身无法恢复和预防,建议程序终止。
3、Exception类表示程序可以处理的异常,可以捕获并且可能恢复。
建议:应该尽可能处理异常,使得程序恢复运行,而不是随着异常的发生而终止。
都是Throwable的子类,都是可序列化的。
Uncheck Exception和Check Exception
1、Uncheck Exception:程序的瑕疵或逻辑错误,语法上不需要声明抛出,运行时无法恢复,包括Error和RuntimeException及其子类。
2、Check Exception:程序不能直接控制的无效外界情况(如用户输入,数据库问题,网络异常,文件丢失),需要try catch处理或者throws声明抛出。包括Error和RuntimeException及其子类,以及其他Exception。
throw与throws的区别
1、throw用于方法内部,抛出一个Throwable类型的异常。如果是检查异常,必须向外抛出,方法的调用者需要处理。
2、throws的用于方法体外部向外抛出异常。如果是检查异常,调用者必须处理或者向外抛出(无力处理的时候)。
2、虚拟机相关常问知识
JVM基本功能
1、JVM是Java字节码执行的引擎,它还能优化Java字节码,使之转换成效率更高的机器指令。程序员编写的程序最终都要在JVM上执行。JVM屏蔽了与具体操作系统平台的相关的信息,从而实现了java程序只需生成在JVM上运行的字节码文件(class文件),就可以在多种平台上不加修改的地运行,不同平台对应着不同的JVM,在执行字节码时,JVM负责将每一条要执行的字节码送给解释器,解释器再将其翻译成特定平台环境的机器指令并执行。Java语言最重要的特点就是跨平台运行,使用JVM就是为了支持与操作系统无关,实现跨平台运行。
2、JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的。ClassLoader是Java运行时一个重要的系统组件,负责在运行时查找栈和装入类文件的类。
DVM的基本功能
Dalvik主要是完成对象生命周期管理,堆栈管理,进程隔离、线程管理,安全和异常管理,以及垃圾回收等等重要功能。
每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。
Android的DVM和Sun的标准JVM的区别
1、DVM是基于寄存器的,而JVM是基于虚拟栈的。寄存器的存取速度比栈块,DVM能根据android硬件实现最大的优化。
2、DVM执行的是.dex文件,JVM执行的是.class文件。android工程编译出来的.class文件以及aapt工具生成的R.class文件等会被dex工具抽取到一个.dex文件中(最终生成apk)。.dex文件相对于.class文件来说去掉了冗余信息,较少了硬件的I/O操作次数,提高了类的查找速度。(另外,odex文件是dex文件的进一步优化)
3、所有的android线程都对应着Linux线程,所以虚拟机可以更多地依赖操作系统的线程调度和管理。
4、有一个特殊的虚拟机进程Zygote,他是虚拟机实例的孵化器。它在系统启动的时候就会产生,它会完成虚拟机的初始化,库的加载,预制类库和初始化的操作。如果系统需要一个新的虚拟机实例,它会迅速复制自身,以最快的数据提供给系统。对于一些只读的系统库,所有虚拟机实例都和Zygote 共享一块内存区域。
虚拟机的垃圾回收机制
注意:Sun公司只是定义了垃圾回收的规则而不局限于其具体的实现算法,不同厂商生产的虚拟机采用的算法也不尽相同。
常用的垃圾搜索算法:
1、引用计数器算法(JDK1.1之后废弃):通过给每个对象设置一个计数器,当有地方引用到时,计数器+1,引用失效时,计数器-1。当计数器为0的时候,JVM就认为对象不再被使用可以被回收了。缺点:不能解决循环引用的问题,每次操作计数器都会带来额外的开销。
2、根搜索算法:通过一些GC Root对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用莲,当一个对象没有被GC Root的引用链的时候,JVM就认为对象不再被使用可以被回收了。
GC Root对象有:
1、Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots。
2、Thread - 活着的线程
3、Stack Local - Java方法的local变量或参数
4、JNI Local - JNI方法的local变量或参数
5、JNI Global - 全局JNI引用
6、Monitor Used - 用于同步的监控对象
7、Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此就只有留给分析分员去确定哪些是属于"JVM持有"的了。
虚拟机的垃圾回收算法:
1、标记—清除算法(Mark-Sweep)(DVM 使用的算法)
标记—清除算法:包括两个阶段:"标记"和"清除"。在标记阶段,确定所有要回收的对象,并做标记。清除阶段紧随标记阶段,将标记阶段确定不可用的对象清除。标记—清除算法是基础的收集算法,标记和清除阶段的效率不高,而且清除后回产生大量的不连续空间,这样当程序需要分配大内存对象时,可能无法找到足够的连续空间。
2、复制算法:把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,然后把这块内存整个清理掉。复制算法实现简单,运行效率高,但是由于每次只能使用其中的一半,造成内存的利用率不高。JVM用复制算法收集新生代。
3、标记—整理算法:把存活的对象往内存的一端移动,然后直接回收边界以外的内存。提高了内存的利用率,并且它适合在收集对象的存活时间较长的老年代。
4、分代收集:根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点采取不同的回收算法。新生代—复制算法。老年代—标记-整理算法。
什么时候下回触发GC?对什么东西进行回收?可以手动触发吗?
1.系统空闲的时候。
2.系统自身决定,不可预测的时间/调用System.gc()的时候。
3.能说出新生代、老年代结构,能提出minor gc/full gc
4.能说明minor gc/full gc的触发条件、OOM的触发条件,降低GC的调优的策略。
总结:程序员不能具体控制时间,系统在不可预测的时间调用System.gc()函数的时候;当然可以通过调优,用NewRatio控制newObject和oldObject的比例,用MaxTenuringThreshold 控制进入oldObject的次数,使得oldObject 存储空间延迟达到full gc,从而使得计时器引发gc时间延迟OOM的时间延迟,以延长对象生存期。
对什么东西?
1.不使用的对象。2.超出作用域的对象/引用计数为空的对象。
gc到底怎么判断哪些对象在不在作用域的?至于引用计数来判断对象是否可收集的,我可以会补充一个下面这个例子让面试者分析一下obj1、obj2是否会被GC掉?
class C{
public Object x;
}
C obj1,obj2 = new C();
obj1.x = obj2;
obj2.x = obj1;
obj1,obj2 = null;
3.从gc root开始搜索,搜索不到的对象。PS:有面试者在这个问补充强引用、弱引用、软引用、幻影引用区别等,不是我想问的答案,但可以加分。
4.从root搜索不到,而且经过第一次标记、清理后,仍然没有复活的对象。分析:我期待的答案。但是的确很少面试者会回答到这一点,所以在我心中回答道第3点我就给全部分数。
总结:超出了作用域或引用计数为空的对象;从gc root开始搜索找不到的对象,而且经过一次标记、清理,仍然没有复活的对象。
做什么?
1.删除不使用的对象,腾出内存空间。
2.补充一些诸如停止其他线程执行、运行finalize等的说明。
补充一点题外话,面试时我最怕遇到的回答就是"这个问题我说不上来,但是遇到的时候我上网搜一下能做出来"。做程序开发确实不是去锻炼茴香豆的"茴"有几种写法,不死记硬背我同意,我不会纠语法、单词,但是多少你说个思路呀,要直接回答一个上网搜,我完全没办法从中获取可以评价应聘者的信息,也很难从回答中继续发掘话题展开讨论。建议大家尽量回答引向自己熟悉的,可讨论的领域,展现给面试官最擅长的一面。
3.能说出诸如新生代做的是复制清理、from survivor、to survivor是干啥用的、老年代做的是标记清理、标记清理后碎片要不要整理、复制清理和标记清理有有什么优劣势等。
总结:删除不使用的对象,回收内存空间;运行默认的finalize,当然程序员想立刻调用就用dipose调用以释放资源如文件句柄,JVM用from survivor、to survivor对它进行标记清理,对象序列化后也可以使它复活。
Java虚拟机的内存模型
Java虚拟机将其管辖的内存分为三个逻辑部分:方法区,栈,堆。
1、方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。
常数池,源代码中的命名常量、String 常量和static 变量保存在方法区。
2、Java Stack 是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。
最典型的Stack 应用是方法的调用,Java 虚拟机每调用一次方法就创建一个方法帧(frame),退出该
方法则对应的方法帧被弹出(pop)。栈中存储的数据也是运行时确定的。
3、Java 堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。
堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java 对象的内存总是在heap 中分配。
Java 内存分配
1、基础数据类型直接在栈空间分配;
2、方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
3、引用数据类型,需要用new 来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
4、方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
5、局部变量new出来时,在栈空间和堆空间中分配,当局部变量生命周期结束后,栈空间立刻回收,堆空间区域等待GC 回收;
6、方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放;
7、字符串常量在DATA 区域分配,this 在堆空间分配;
8、数组既在栈空间分配数组名称,又在堆空间分配数组实际的大小
3、Java的容器相关问题。
[图片上传失败...(image-c69bc0-1510450462343)]
Set 、List、Map的区别:
Set:实现了Collection接口,集合中的对象不按照特定方式排序,不可以有重复对象。
List:实现了Collection接口,集合中的对象按照索引位置排序,并且可以有重复的对象。
Map:实现了 Map 接口,集合中的每一个元素都是一个键值对,键不可以重复,值可以重复。
HashMap与HashTable-哈希表(数组+链表实现)
- HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
- HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
- 两者使用的迭代方式不一样。
sychronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。
HashMap可以通过下面的语句进行同步:Map m = Collections.synchronizeMap(hashMap);
总结:Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java 5或以上的话,请使用ConcurrentHashMap吧。
ArrayList、Vector的区别
- Vector是线程安全的,也就是说是它的方法之间是线程同步的,而ArrayList是线程序不安全的。
- 数据增长:ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList与Vector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法。
ArrayList底层是什么实现的?如何动态扩容?
1、底层是Object[] 数组实现的,动态扩容是需要扩容的时候,先new 一个更加大的Object[] 数组,把原来的数组拷贝过去。
2、当进行数据插入的时候,先判断当前的array数组长度是否已经等于ArrayList的size,如果相等说明数组已经满了,需要重新创建数组, 如果当前长度小于6,新数组长度增加12,否则新数组长度增加原来的一半。 接着把新元素插入到数组末尾,size+1,
3、当删除数据的时候,先保存被删除的数据(用于返回),判断下标是否越界。删除一个数据,后面的数据前移一格,最后一个数据置为null,否则会引发内存泄漏。更新size-1。
4、清空数据的时候,先判断size是否为0,不为0的时候,把array的所有元素置为null。
描述在java 中有普通集合、同步(线程安全)的集合、并发集合
1、普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性。
2、线程安全集合仅仅是给集合添加了synchronized 同步锁,严重牺牲了性能,而且对并发的效率就更低了
3、并发集合(ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque)则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率。例如利用 锁分段技术 ,避免了每次数据操作都锁住整个数组。
4、强引用、软引用、弱引用的区别?
强引用: 我们写代码天天在用的就是强引用。如果一个对象被被人拥有强引用,那么垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
软引用: 那么如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
弱引用: 如果一个对象只具有弱引用,那该类就是可有可无的对象,因为只要该对象被gc 扫描到了随时都会把它干掉。
虚引用: 形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。
5、String、StringBuilder与StringBuffer的区别
在执行速度方面的比较: StringBuilder > StringBuffer
StringBuffer与StringBuilder,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,不像String一样创建一些对象进行操作,所以速度就快了。
StringBuilder:线程非安全的 StringBuffer:线程安全的
当我们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最快,但是可以保证StringBuffer是可以正确操作的。当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,就是 速度 的原因。
对于三者使用的总结:
1、如果要操作少量的数据用 = String
2、单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
3、多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
String 是基本数据类型吗?可以被继承吗?
String 是引用类型,底层用char 数组实现的。因为String 是final 类,在java 中被final 修饰的类不能被继承。
因此String 当然不可以被继承。
6、Overload(重载)与Override(重写)的区别
重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数。
覆盖(也叫重写)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样。
隐藏是指派生类中的函数把基类中相同名字的函数屏蔽掉了。
方法的重写(Overriding)和重载(Overloading)是Java多态性的不同表现。 重写(Overriding)是父类与子类之间多态性的一种表现,而重载(Overloading)是一个类中多态性的一种表现。
override(重写)
-
方法名、参数、返回值相同。
-
子类方法不能缩小父类方法的访问权限。
-
子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
-
存在于父类和子类之间。
-
方法被定义为final不能被重写。
overload(重载)
-
参数类型、个数、顺序至少有一个不相同。
-
不能重载只有返回值不同的方法名。
-
存在于父类和子类、同类中。
7、hashCode与equals的区别:
1、基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean他们之间的比较,应用双等号(==),比较的是他们的值。
2、复合数据类型(类)复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。
与集合的关系:
将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。
规则:
1、如果两个对象根据equals()方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
2、如果两个对象根据equals()方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生相同的整数结果。 但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。
3、一般来说,两个方法都应该同时被重写。
8、Java中创建对象的四种方法 收藏Java中创建对象的四种方式
1、用new语句创建对象,这是最常见的创建对象的方法。
2、运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
3、对象的clone()。(必须打上Cloneable标记,显示调用clone,默认是浅复制)
4、运用反序列化手段,搜索调用java.io.ObjectInputStream对象的 readObject()方法。
[图片上传失败...(image-4aaaf2-1510450462343)]
9、类加载器与初始化
JVM的类加载器种类
1、Java 的类加载器的种类都有哪些?
1、根类加载器(Bootstrap) --C++写的,看不到源码
2、扩展类加载器(Extension) --加载位置:jre\lib\ext
3、系统(应用)类加载器(System\App) --加载位置:classpath
4、自定义加载器(必须继承ClassLoader),自定义类加载器
可以实现字节码的加密以及解密功能
说说静态变量、静态代码块加载的过程和时机?
回答:当类加载器将类加载到JVM中的时候就会创建静态变量,静态变量加载的时候就会分配内存空间。 静态代码块的代码只会在类第一次初始化,也就是第一次被使用的时候执行一次。
类什么时候被初始化?(只有下面这6 中情况才会导致类的类的初始化)
1)创建类的实例,也就是new 一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName("com.lyj.load"))
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM 启动时标明的启动类,即文件名和类名相同的那个类
类的初始化步骤:
1)如果这个类还没有被加载和链接,那先进行加载和链接
2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
3)加入类中存在初始化语句(如static 变量和static 块),那就依次执行这些初始化语句。
10、多态实现机制,实现方式
实现机制: 靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
实现方式(多态性的实现):
1、重写:父类与子类之间的多态性的实现。
2、重载:一个类的多态性的实现。
3、对象转型
4、反射(例如工厂模式中)等
11、Java IO
字节流和字符流。
1、字节流继承于InputStream 和OutputStream。
2、字符流继承于InputStreamReader 和OutputStreamWriter。
字节流如何转为字符流(装饰者设计模式):
1、字节输入流转字符输入流通过InputStreamReader 实现,该类的构造函数可以传入InputStream 对象。
2、字节输出流转字符输出流通过OutputStreamWriter 实现,该类的构造函数可以传入OutputStream 对象。
12、Java多线程(重点)
创建线程的两种方式,以及不同点?
1、继承java.lang.Thread 类。
2、直接实现Runnable 接口来重写run()方法实现线程。
相同点: java.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable 接口来执行,由于线程类本身就是
调用的Runnable 接口。
不同点: 由于java只支持单继承,因此如果对象还需要继承其他类的话,就使用第二种方法创建线程。
在java 中wait 和sleep 方法的不同?
最大的不同是在等待时wait 会释放锁,而sleep 一直持有锁。wait 通常被用于线程间交互,sleep 通常被用于暂停执行。
synchronized 和volatile 关键字的作用,异同?
volatile 本质是在告诉jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile 修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
synchronized 本质是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
异同:
1.volatile 仅能使用在变量级别;synchronized 则可以使用在变量、方法、和类级别的
2.volatile 仅能实现变量的修改可见性,并不能保证原子性;synchronized 则可以保证变量的修改可见性和原子性
3.volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
4.volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化
什么是线程池,如何使用?
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。
在JDK 的java.util.concurrent.Executors 中提供了生成多种线程池的静态方法。
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
ExecutorService newScheduledThreadPool =Executors.newScheduledThreadPool(4);
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
请叙述一下您对线程池的理解?
(如果问到了这样的问题,可以展开的说一下线程池如何用、线程池的好处、线程池的启动策略)
合理利用线程池能够带来三个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定
性,使用线程池可以进行统一的分配,调优和监控。
线程池的启动策略?
1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也
不会马上执行它们。
2、当调用execute() 方法添加一个任务时,线程池会做如下判断:
a. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
b. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列。
c. 如果这时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建线程运行这
个任务;
d. 如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常,告
诉调用者"我不能再接受任务了"。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize 的大小。
如何控制某个方法允许并发访问线程的个数?
通过初始化相同数量的信号量Semaphore来控制,方法里先申请一个请求semaphore.acquire();,然后执行,执行后释放一个请求semaphore.release();
由于信号量的个数有限,信号量为0的时候,新线程访问改方法的时候将不能获取信号量acquire而等待。
三个线程a、b、c 并发运行,b,c 需要a 线程初始化完毕数据怎么实现?
通过信号量Semaphore来控制:初始化两个信号量,a线程全部获取;a线程初始化数据完毕以后,释放两个信号量使得b、c线程开始执行。
13、反射—功能:实现框架
反射的理解
一句话总结:反射就是把Java类中的各种成分通过java的反射API映射成相应的Java类,得到这些类以后就可以对其进行使用。比如方法,构造方法,成员变量,类型,包等。
Class类: 用来描述Java类的类就是Class这个类。
每个类在java虚拟机中占用一片内存空间,里面的内容就是对应这个类的字节码(Class)。
获取字节码Class的三种方法
1、类名.class 2、对象.getClass 3、Class.forName("类的全名")(动态加载进来,与反射密切相关)
代理与AOP
要为已存在的多个具有相同接口的目标类(已经开发好,或者没有源码)的各个方法增加一些系统功能,例如异常处理、日志、计算方法的运行时间、事务管理等。可以使用代理,代理就有这样的好处。
JVM可以在运行期间动态生成出类的字节码,这种动态生成的类往往用作代理,成为动态代理。JVM生成的类必须实现一个或者多个接口,所以JVM生成的类智能用作具有相同家口的目标类的代理。
静态代理与动态代理的区别
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
动态代理是实现JDK 里的InvocationHandler 接口的invoke 方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过Proxy 里的newProxyInstance 得到代理对象。
还有一种动态代理CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。
AOP 编程就是基于动态代理实现的,比如著名的Spring 框架、Hibernate 框架等等都是动态代理的使用例子。
AOP: 面向切面编程(AOP是Aspect Oriented Program的首字母缩写),在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。技术上,AOP基本是通过代理的方式来实现的。
实现: 通过字节码生成代理类对象需要传入InvocationHandler对象,代理类的方法调用会触发InvocationHandler的分发(invoke方法),InvocationHandler内部会对被代理类的对象的方法进行调用,并且插入一些指定的功能。
14、注解
注解的概念: 注解实际上是一个类,写注解实际上是创建注解的对象。注解相当于为程序打一种标记。javac编译工具、开发工具以及其他程序可以利用反射来获取你的类以及各种元素上有没有何种注解(并且可以获取注解上的值),就去做相应的处理。
标记可以加在包、类型(Type,包括类,枚举,接口)、字段、方法、方法的参数以及局部变量上面。
注解的生命周期
- SOURCE:注解被保留到源文件阶段。当javac把源文件编译成.class文件的时候,就将相应的注解去掉。例如常见的Override、SuppressWarnings都属于SOURCE类型的生命周期,因为一旦代码编译之后该注解就没用了。
- CLASS:java虚拟机通过类加载器向内存中加载字节码的时候,就将相应的注解去掉,因此无法通过反射获取相应的注解。
- RUNTIME:注解保留在内存中的字节码上面了,虚拟机在运行字节码的时候,仍然可以使用的注解。例如Deprecated,类被别人使用的时候,加载到内存,扫描,从二进制代码去看是否过时,而不是检查源代码。
15、泛型
集合等地方都使用到了泛型,免去了强制类型转换的不安全性问题,包括code阶段以及运行阶段。泛型是给编译器看的,让编译器拦截源程序中的非法输入,编译完以后就会去掉类型信息( 因此可以通过反射的方式,获取add方法,手动调用add添加非T类型的数据到集合中 ),保证程序的运行效率。对于参数化的泛型类型,getClass方法的返回值和原始类型完全一样。
16、正则