1.String、StringBuffer与StringBuilder的区别
解析:(1)String是字符串常量,StringBuffer是字符串变量,线程安全的,StringBuilder也是字符串变量,非线程安全的。(2)String和StringBufferd的主要性能区别在于String是不可变的对象,因此每次对String类型的值进行变更的时候就等同于生成了一个新的String对象,并将引用重新指向新的对象,因为每次生成对象都对系统性能有影响,特别是JVM的GC还要回收内存中无引用的对象。StringBuffer则不会产生新的对象,所以字符串的值经常改变的情况下推荐使用StringBuffer。
2.Java是如何实现跨平台的
解析:通过JVM实现,jVM也是一个软件,不同的平台有不同的版本。Java程序从源文件创建到程序运行要经过两大步骤:1、源文件由编译器编译成字节码;2、字节码由JVM解释运行。不同平台下编译生成的字节码是一样的,但是由JVM翻译成的机器码却不一样。
3.Java线程有哪些状态,这些状态之间是如何转化的
解析:(1)新建(NEW):新创建了一个线程对象。(2)可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。(3)运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。(4)阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
(一)等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二)同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
(5)死亡(DEAD):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
4.List接口、Set接口和Map接口的区别
解析:(1)List和Set接口继承自Collection接口,而Map不是继承的Collection接口;(2)List元素有放入顺序,可重复,接口有三个实现类:LinkedList:基于链表实现,增删快查找慢,ArrayList线程不安全,效率高,Vector线程安全,ArrayList和Vector适合查询。(3)Set接口元素无放入顺序,不可重复,有两个实现接口:HashSet,LinkedHashSet;(3)Map接口以键值对的方式出现,有三个实现类:HashMap,HashTable,LinkedHashMap。HashMap非线程安全,高效,支持null,如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力;HashTable线程安全,键值不支持null;LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。
5.Cookie和Session的区别
解析:(1)session 在服务器端,cookie 在客户端(浏览器)。
(2)session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie 同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)。(3)session 可以放在文件、数据库、或内存中都可以。(4)用户验证这种场合一般会用session 。因此,维持一个会话的核心就是客户端的唯一标识,即 sessionid。
6.HashMap与ConcurrentHashMap的区别
解析:并发编程实践中,ConcurrentHashMap是一个经常被使用的数据结构,相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在线程安全的基础上提供了更好的写并发能力,但同时降低了对读一致性的要求。ConcurrentHashMap的设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响,无论对于Java并发编程的学习还是Java内存模型的理解,ConcurrentHashMap的设计以及源码都值得非常仔细的阅读与揣摩。 ConcurrentHashMap具体是怎么实现线程安全的呢,肯定不可能是每个方法加synchronized,那样就变成了HashTable。 JDK6与JDK7中的实现它引入了一个“分段锁”的概念,在ConcurrentHashMap中,就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中。JDK8中的实现它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。它沿用了与它同时期的HashMap版本的思想,底层依然由“数组”+链表+红黑树的方式思想(JDK7与JDK8中HashMap的实现),但是为了做到并发,又增加了很多辅助的类,例如TreeBin,Traverser等对象内部类 。
equals()
方法很明显是对两个对象的地址值进行的比较(即比较引用是否相同)。String 、Math、Integer、Double等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法,进行的内容比较,而已经不再是地址的比较。当然,基本类型是进行值的比较。
hashCode()
方法给对象返回一个hash code值,如果两个对象根据equals(Object)
方法是相等的,那么调用二者各自的hashCode()
方法必须产生同一个integer结果。
(1)相等(相同)的对象必须具有相等的哈希码(或者散列码)。
(2)如果两个对象的hashCode相同,它们并不一定是相同的对象。乐观锁避免了悲观锁独占对象的现象,同时也提高了并发性能,但它也有缺点:① 乐观锁只能保证一个共享变量的原子操作。如上例子,自旋过程中只能保证value变量的原子性,这时如果多一个或几个变量,乐观锁将变得力不从心,但互斥锁能轻易解决,不管对象数量多少及对象颗粒度大小。② 长时间自旋可能导致开销大。假如CAS长时间不成功而一直自旋,会给CPU带来很大的开销。③ ABA问题。CAS的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过,但这个判断逻辑不严谨,假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大。解决的思路是引入版本号,每次变量更新都把版本号加一。乐观锁是对悲观锁的改进,虽然它也有缺点,但它确实已经成为提高并发性能的主要手段,而且jdk中的并发包也大量使用基于CAS的乐观锁。
详见:乐观的并发策略——基于CAS的自旋
9.comparable与comparator的区别
解析:Comparable和Comparator都是用来实现集合中元素的比较、排序的。
Comparable是在集合内部定义的方法实现的排序,位于java.lang下。
Comparator是在集合外部实现的排序,位于java.util下。
Comparable是一个对象本身就已经支持自比较所需要实现的接口,如String、Integer自己就实现了Comparable接口,可完成比较大小操作。自定义类要在加入list容器中后能够排序,也可以实现Comparable接口,即若一个类实现了Comparable 接口,实现 Comparable 接口的类的对象的 List 列表 ( 或数组)可以通过 Collections.sort(或 Arrays.sort)进行排序:Collections.sort(list),在用Collections类的sort方法排序时若不指定Comparator,那就以自然顺序排序。所谓自然顺序就是实现Comparable接口设定的排序方式。
Comparator是一个专用的比较器,如果我们的这个类无法修改,我们又要对其进行排序或者自比较函数不能满足要求时,可写一个比较器来完成两个对象之间大小的比较。Comparator体现了一种策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。
例子:Comparable :
package collections;
public class Person1 implements Comparable
{
private int age;
private String name;
public Person1(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person1 o)
{
return this.age-o.age;
}
@Override
public String toString()
{
return name+":"+age;
}
}
测试代码:
Person1 person1 = new Person1("zzh",18);
Person1 person2 = new Person1("jj",17);
Person1 person3 = new Person1("qq",19);
List list = new ArrayList<>();
list.add(person1);
list.add(person2);
list.add(person3);
System.out.println(list);
Collections.sort(list);
System.out.println(list)
输出结果:
[zzh:18, jj:17, qq:19]
[jj:17, zzh:18, qq:19]
Comparetor:
Person2 p1 = new Person2("zzh",18);
Person2 p2 = new Person2("jj",17);
Person2 p3 = new Person2("qq",19);
List list2 = new ArrayList();
list2.add(p1);
list2.add(p2);
list2.add(p3);
System.out.println(list2);
Collections.sort(list2,new Comparator(){
@Override
public int compare(Person2 o1, Person2 o2)
{
if(o1 == null || o2 == null)
return 0;
return o1.getAge()-o2.getAge();
}
});
System.out.println(list2);
10.单例模式(线程安全)
解析:
解法一:加同步锁时,前后两次判断实例是否存在(可行)
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
解法二:饿汉式(推荐使用):
public class Singleton {
private static Singleton instance=new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
注解:初试化静态的instance创建一次。如果我们在Singleton类里面写一个静态的方法不需要创建实例,它仍然会早早的创建一次实例。而降低内存的使用率。缺点:没有lazyloading的效果,从而降低内存的使用率。
解法三:静态内部内(建议使用):public class Singleton {
private Singleton(){
}
private static class SingletonHolder{
private final static Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
注解:定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。而类型为SingletonHolder的类,只有在Singleton.getInstance()中调用,由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。
解析:①同步。这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信。 ②while轮询的方式 。③wait/notify机制。要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中,线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法)。Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。notify()方法会唤醒一个等待当前对象的锁的线程。被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁。notify()方法应该是被拥有对象的锁的线程所调用。换句话说,和wait()方法一样,notify方法调用必须放在synchronized方法或synchronized块中。
package utils;
public class WaitNotifyTest {
private Object object=new Object();
class WaitThread extends Thread{
@Override
public void run() {
synchronized (object){
System.out.println(Thread.currentThread().getName()+"线程等待开始");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程等待结束");
}
}
}
class NotifyThread extends Thread{
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object){
System.out.println(Thread.currentThread().getName()+"唤醒线程");
object.notify();
}
}
}
public static void main(String [] args){
WaitNotifyTest waitNotifyTest=new WaitNotifyTest();
WaitThread waitThread=waitNotifyTest.new WaitThread();
waitThread.start();
NotifyThread notifyThread=waitNotifyTest.new NotifyThread();
notifyThread.start();
}
}
详见:Java 多线程(七) 线程间的通信——wait及notify方法
12.spring中ApplicationContextAware接口有什么用途
解析:当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean。换句话说,就是这个类可以直接获取spring配置文件中,所有有引用到的bean对象。
详见:通过ApplicationContextAware加载Spring上下文环境
13.zookeeper怎么通知客户端
解析:
客户端与服务器通信采用tcp长连接,客户端和服务器通过心跳来保持seesion的连接。当session失效时临时节点会被删除。
Clients会连接到某一个ZooKeeper Server上。Client和Server保持一个TCP长连接,通过该TCP长连接,Client可以发送请求,得到response,得到watch event,还有发送心跳(客户端和服务端通过心跳来保持连接,即session)。如果和Server的TCP长连接断了,那么Client就会连接到另外一个Server上。
在ZooKeeper中,引入了Watcher机制来实现这种分布式的通知功能。ZooKeeper允许客户端向服务端注册一个Watcher监听,当服务端的一些事件触发了这个Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。
详见: Zookeeper(十)Watcher——数据变更的通知
14.dubbo支持哪些协议
解析:Dubbo支持dubbo、rmi、hessian、http、webservice、thrift、redis等多种协议,但是Dubbo官网是推荐我们使用Dubbo协议的。dubbo协议:(1)dubbo默认采用dubbo协议,dubbo协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况 ;(2)他不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。rmi协议:Java标准的远程调用协议。采用JDK标准的java.rmi.*实现,采用阻塞式短连接和JDK标准序列化方式 。
详见:Dubbo支持的协议的详解
15.nginx负载均衡算法有哪些
解析:nginx的负载均衡策略可以划分为两大类:内置策略和扩展策略。内置策略包含加权轮询和ip hash,在默认情况下这两种策略会编译进nginx内核,只需在nginx配置中指明参数即可。扩展策略有很多,如fair、url_hash、consistent hash等,默认不编译进nginx内核。(1)轮询(默认):每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。(2)指定权重:指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。(3)ip_hash:每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
16.volatile型变量的特殊规则
解析:关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制,但是它并不容易完全被正确、完整的理解,以至于许多程序员都不习惯去使用它,遇到需要处理多线程的问题的时候一律使用synchronized来进行同步。了解volatile变量的语义对后面了解多线程操作的其他特性很有意义。Java内存模型对volatile专门定义了一些特殊的访问规则,当一个变量被定义成volatile之后,他将具备两种特性:
main是主线程,在main中创建了thread线程,在main中调用了thread.join(),那么等thread结束后再执行main代码。在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
18.java的多态
解析:多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。
19.Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?
解析:把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。这两个过程结合起来,可以轻松地存储和传输数据。
序列化的目的:(1)以某种存储形式使自定义对象持久化;(2)将对象通过网络进行传输。
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2) 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2) 通过对象输入流的readObject()方法读取对象。
20.Spring事务的传播行为
解析:@Transactional(propagation=Propagation.REQUIRED)
如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY)
必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER)
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务。
21.Spring AOP 实现原理
解析:AOP技术利用一种称为“横切”的技术,解剖封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,这样就能减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。AOP使用代理模式,动态代理实现主要是实现InvocationHandler,并且将目标对象注入到代理对象中,利用反射机制来执行目标对象的方法。
动态代理主要有两种方式:JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler
接口和Proxy
类。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final
,那么它是无法使用CGLIB做动态代理的。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
测试:
生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。
详见:Spring aop的实现原理
22.Java NIO和IO的区别
解析:Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
阻塞与非阻塞IO:Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
23.java反射机制
解析:1.反射机制概念
在Java中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。
2.反射的应用场合
在Java程序中许多对象在运行是都会出现两种类型:编译时类型和运行时类型。
编译时的类型由声明对象时实用的类型来决定,运行时的类型由实际赋值给对象的类型决定 。
除此之外,程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为Object,但是程序有需要调用该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射了。24.如何预防Mysql注入
解析:所谓SQL注入,就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
防止SQL注入,我们需要注意以下几个要点:
1.永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和双"-"进行转换等。
2.永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
4.不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装
6.sql注入的检测方法一般采取辅助软件或网站平台来检测。解析:foward是服务器端控制页面转向,在客户端的浏览器地址中不会显示转向后的地址;sendRedirect则是完全的跳转,浏览器中会显示跳转的地址并重新发送请求链接。
原理:forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后再将这些内容返回给浏览器,浏览器根本不知道服务器发送的这些内容是从哪来的,所以地址栏还是原来的地址。redirect是服务器端根据逻辑,发送一个状态码,告诉浏览器重新去请求的那个地址,浏览器会用刚才的所有参数重新发送新的请求。
解析:JVM性能调优有很多设置,这个参考JVM参数即可。主要调优的目的:
控制GC的行为。GC是一个后台处理,但是它也是会消耗系统性能的,因此经常会根据系统运行的程序的特性来更改GC行为。
控制JVM堆栈大小。一般来说,JVM在内存分配上不需要你修改,但是当你的程序新生代对象在某个时间段产生的比较多的时候,就需要控制新生代的堆大小。同时,还要需要控制总的JVM大小避免内存溢出。
控制JVM线程的内存分配。如果是多线程程序,产生线程和线程运行所消耗的内存也是可以控制的,需要通过一定时间的观测后,配置最优结果。
代理模式:
详见:java设计模式之代理模式
30.hashmap原理
解析:hashmap里面存放key-value对的时候,都会为它们实例化一个Entry对象,这个Entry对象就会存储在前面提到的Entry数组table中,Entry对象将会存放在具体哪个位置(在table中的精确位置)由key的hashcode()方法计算出来的hash值来决定。hash值用来计算key在Entry数组的索引。如果两个元素有相同的hashcode,即两个key有相同的hash值(也叫冲突),它们会被放在同一个索引上。问题出现了,该怎么放呢?原来它是以链表(LinkedList)的形式来存储的。
详见:Hashmap的工作原理
31.线程池原理
解析:最简单的服务器工作模型:服务器每当接收到一个客户端请求时就创建一个线程为其服务。这种模式理论上可以工作的很好,但实际上会存在一些缺陷,服务器应用程序中经常出现的情况是单个客户端请求处理的任务很简单但客户端的数目却是巨大的,因此服务器在创建和销毁线程所花费的时间和系统资源可能比处理客户端请求处理的任务花费的时间和资源更多。
线程池技术就是为了解决上述问题而出现的。合理的使用线程池便可重复利用已创建的线程,以减少在创建线程和销毁线程上花费的时间和资源。除此之外,线程池在某些情况下还能动态的调整工作线程的数量,以平衡资源消耗和工作效率。同时线程池还提供了对池中工作线程进行统一的管理的相关方法。
线程池的工作模型主要两部分组成,一部分是运行Runnable的Thread对象(工作线程),另一部分就是阻塞队列(执行任务的线程队列)。
由线程池创建的Thread对象其内部的run方法会通过阻塞队列的take方法获取一个Runnable对象,然后执行这个Runnable对象的run方法(即,在Thread的run方法中调用Runnable对象的run方法)。当Runnable对象的run方法执行完毕以后,Thread中的run方法又循环的从阻塞队列中获取下一个Runnable对象继续执行。这样就实现了Thread对象的重复利用,也就减少了创建线程和销毁线程所消耗的资源。
当需要向线程池提交任务时会调用阻塞队列的offer方法向队列的尾部添加任务。提交的任务实际上就是Runnable对象。
详见:线程池原理