1. static和final的区别和用途
static:
修饰变量:静态变量随着类的加载时就自动完成初始化,内存中只有一个,且虚拟机只会为它分配一次内存,所有类共享静态变量。
修饰方法:静态方法不依赖于于任何实例对象,被类的所有实例共享。静态方法可以直接通过类名调用,静态方法必须实现,不能用abstract修饰。
修饰代码块:在类加载完之后会自动执行的代码块。
代码执行顺序:父类静态代码块--->子类静态代码块--->父类非静态代码块--->父类构造方法--->子类非静态代码块--->子类构造方法
final:
修饰变量:
编译期常量:编译期间就将常量值带入到如何计算式汇总,只能是基本类型。
运行时常量:可以是基本类型或引用类型,引用不可以改变,引用指向的对象内容可以改变。
修饰方法:不能被子类继承,且不能被子类修改。
修饰类:不能被继承。
修饰形参:形参不能改变。
2. HashMap和Hashtable的区别,HashMap中的key可以是如何对象或数据类型吗?Hashtable是线程安全的么?
HashMap和Hashtable区别:
Hashtable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap,这如同Vector和ArrayList一样。
Hashtable不允许null值(key和value都不可以),HashMap允许null值(key和value都可以)。
两者的遍历方式大同小异,Hashtable仅仅比HashMap多一个elements方法,两者都能通过values()方法返回一个Collection,然后进行遍历,两者也可以通过entrySet()返回一个Set,然后进行遍历处理。
Hashtable使用Enumeration, HashMap使用Iterator。
哈希值的使用不同,Hashtable直接使用对象的hashCode,而HashMap重新计算hash值,而且用于代替求模。
Hashtable的hash数组默认大小是11,增加的方式是old * 2 + 1;HashMap的hash数组默认大小是16,而且一定是2的指数。
Hashtable基于Dictionary类,而HashMap基于AbstractMap类。
HashMap中的key可以是任何对象或数据类型吗:
可以为null,但不能是可变对象,如果是可变对象的话,对象中的属性改变时,则相应的对象hashCode也进行相应改变,导致下次无法查找到已存在Map中的数据。
如果可变对象在HashMap中被用作key时,那就要小心在改变对象状态的时候,不要改变它的哈希值。我们只需要保证成员变量的改变能保证对象的哈希值不变即可。
Hashtable是线程安全的吗:
Hashtable是线程安全的,其内部实现就是在对应的方法上添加了synchronized关键字进行修饰,由于在执行此方法的时候需要获得对象锁,因此执行起来比较慢。所以现在如果为了保证线程安全的话,使用效率高的ConcurrentHashMap。
3. HashMap和ConcurrentHashMap的区别是什么,ConcurrentHashMap是线程安全吗?ConcurrentHashMap是如何保证线程安全的?
HashMap和ConcurrentHashMap的区别:
HashMap是非线程安全的,ConcurrentHashMap是线程安全的。
ConcurrentHashMap将整个Hash桶进行了分段segment,也就是将这个大数组分成了小的片段segment,而且每个小的片段segment上面都有各自的锁存在,那么在插入元素的时候就需要先找到应该插入到哪一个片段的segment,然后再在这个片段上面进行插入,而且这里只需要获取该segment的锁。
ConcurrentHashMap是线程安全吗,ConcurrentHashMap是如何保证线程安全:
Hashtable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问Hashtable的线程都必须竞争同一把锁,那么假如容器里有多把锁,每一把锁用于锁定容器其中的一部分数据,则当多线程访问容器里不同数据段的数据时,线程间就不会存在锁的竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
get操作的高效操作之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读;get方法里将要使用的共享变量都定义成volatile,如用于统计当前segement大小的count字段和用于存储值得HashEntry的value。定义成volatile的变量能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写(有一种情况可以被多线程写,那就是写入的值不依赖于原值),在get操作里只需要读不需要写共享变量count和value,所以可以不用加速。
put方法首先定位到segment,然后在segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对segment里的HashEntry数据进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。
4. 因为别人知道源码是怎么实现的,故意构造相同的hash的字符串进行攻击,怎么处理?那jdk7怎么办?
怎么处理构造相同hash的字符串进行攻击:
当客户端提交一个请求并附带参数的时候,web应用服务器会把我们的参数转换成一个HashMap进行存储,这个HashMap存储结构如:key1--->value1;
单数物理存储结构是不同的,key值会被转换成hashCode,这个hashCode就会被转成数组下标:0--->value1;
不同的string可能会产生相同hashcode而导致碰撞,碰撞后的物理存储结构就变为:0--->value1--->value2(拉链法);
限制post和get参数的个数,越少越好;限制post数据的大小;使用WAF过滤。
Jdk7如何处理hashCode字符串攻击:
HashMap会动态的使用一个专门的TreeMap实现来替换掉它。
5. String、StringBuffer、StringBuilder以及对String不变性的理解
String、StringBuffer、StringBuilder:
都是final类,都不允许被继承;
String长度是不可变的,StringBuffer、StringBuilder长度是可变的;
StringBuffer是线程安全的, StringBuilder不是线程安全的,但它们两个中的所有方法都是相同的,StringBuffer在StringBuilder的方法上添加了synchronized修饰,保证线程安全;
StringBuilder比StringBuffer拥有更好的性能;
如果是一个String类型的字符串,在编译时就可以确定是一个字符串常量,则编译完成之后,字符串会自动拼接成一个常量。此时String的速度比StringBuffer和StringBuilder的性能好的多。
String不变性的理解:
String类是被final进行修饰的,不能被继承;
在用+号连接字符串的时候会创建新的字符串。
String s = new String("Hello world");可能会创建两个对象也可能只创建一个对象。如果静态区中有"Hello world"字符串常量的话,则仅仅在堆中创建一个对象,如果静态区中没有"Hello world"对象,则堆上和静态区中都需要创建对象;
在Java中,通过使用"+"符号来串联字符串的时候,实际上底层会转换成通过StringBuilder实例的append()方法来实现。
6. String有重写Object的hashCode和toString方法吗?如果重写equals不重写hashCode会出现什么问题?
String有重写Object的hashCode和toString方法吗:
String重写了Object类的hashCode和toString方法。
当equals方法被重写是,通常有必要重写hashCode方法,以维护hashCode方法的常规协定,改协定声明相对等的两个对象必须有相同的hashCode。
object1.equals(object2)时为true,则object1.hashCode() == object2.hashCode()为true
obejct1.hashCode() == object2.hashCode()为false时,则object1.equals(object2)必定为false
object1.hashCode() == object2.hashCode()为true时,则object1.equals(object2)可能为true,也可能为false
重写equals补充些hashCode会出现什么问题:
在存储散列集合时(如Set类),如果原对象.equlas(新对象),但没有对hashCode重写,即两个对象拥有不同的hashCode,则在集合中将会村吃两个值相同的对象,从而导致混淆。因此在重写equals方法时,必须重写hashCode方法。
7. 什么是线程池?如果让你设计一个动态大小的线程池,如何设计,应该有哪些方法?
什么是线程池:
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
设计一个动态大小的线程池,如何设计,应该有哪些方法:
一个线程池应包括以下四个基本组成部分:
线程管理器(ThreadPool):用于创建并管理线程池,包括创建线程池,销毁线程池,添加任务;
工作线程(PoolWorker):线程池中线程,在没有任务处于等待状态,可以循环的执行任务;
任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完成后的收尾工作,任务的执行状态等;
任务队列(TaskQueue):用于存放没有处理的任务,提供一种缓冲机制;
所包含的方法:
private ThreadPool() 创建线程池
public static ThreadPool getThreadPool() 获得一个默认线程个数的线程池
public void execute(Runnable task) 执行任务,其实只是把任务加入任务队列,什么时候执行由线程池管理器决定
public void execute(Runnable[] task) 批量执行任务,其实只是把任务加入任务队列,什么时候执行由线程池管理器决定
public void destroy() 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁
public int getWorkThreadNumber() 返回工作线程的个数
public int getFinishedTaskNumber() 返回已完成任务的个数,这里的已完成指的是出了任务队列的任务个数,可能该任务并没有实际执行完成
public void addThread() 在保证线程池中所有线程正在执行,并且要执行线程的个数大于某一值时,添加线程池中线程的个数
public void reduceThread在保证线程池中有很大一部分线程处于空闲状态,并且空闲状态的线程在小于某一值时,减少线程池中线程的个数
8. Java是否有内存泄漏和内存溢出
静态集合类,使用Set、Vector、HashMap等集合类的时候需要特别注意。当这些类被定义成静态的时候,由于它们的生命周期更应用程序一样长,这时候就有可能发生内存泄漏,例如:
class StaticTest
{
private static Vector v = new Vector(10);
public void init(){
for(int i = 1; i < 100; i++){
Object object = new Object();
v.add(object);
object = null;
}
}
}
在上面的代码中,循环申请了Object对象,并添加到Vector中,然后设置为null,可是这些对象被vector引用着,因此不能被GC回收,因此造成内存泄漏。因此要释放这些对象,还需要将它们从vector删除,最简单的方法就是将vector设置为null
监听器:在Java编程中,我们都需要和监听器打交道,通常一个应用中会用到很多监听器,我们会调用一个控件,诸如addXXXListener()等方法来增加监听器,但往往在释放的时候却没有去删除这些监听器,从而增加了内存泄漏的机会。
物理连接:一些物理连接,比如数据库连接和网络连接,除非显式的关闭了连接,否则是不会自动被GC回收的。Java数据库连接一般用DataSource.getConnection()来创建,当不再使用时必须用Close()方法来释放,因为这些连接是独立于JVM的。对于Resultset和Statement对象可以不进行显式回收,但Connection对象一定要显式回收,因为Connection在任何时候都无法自动回收,而Connection一旦回收,Resultset和Statement对象就会立即为null。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset和Statement对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statememnt对象无法释放,从而引起内存泄漏。一般情况下,在try代码块里创建连接,在finally里释放连接,这样就能避免此类内存泄漏。
内部类和外部模块等引用:内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。在调用外部模块的时候,也应该注意注意防止内存泄漏,如果模块A调用了外部模块B的一个方法,如:
public void register(Object o), 这个方法有可能就使得A模块持有传入对象的引用,这时候需要查看B模块是否提供了除去引用的方法,这种情况容易忽略,而且发生内存泄漏的话,还比较难察觉。单例模式:因为单例对象初始化后将在JVM的整个什么周期内存在,如果它还持有一个外部对象的(生命周期比较短)引用,那么这个外部对象就不能被会手续,从而导致内存泄漏。如果这个外部对象还持有其他对象的引用,那么内存泄漏更严重。
9. volatile 关键字是如何保证内存可见性
volatile关键字的作用
保证内存的可见性
防止指令重排
注意:volatile并不能保证操作的原子性
内存可见性
volatile保证可见性的原理是在每次访问变量时都会进行一次刷新,因此每次访问都是主内存中最新的版本,所以volatile关键字的作用之一就是保证变量修改的实时可见性。
当且仅当满足以下所有条件时,才应该使用volatile变量
对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
该变量没有包含在具有其他变量的不变式中。
volatile使用建议
在两个或者更多的线程需要访问的成员变量上使用volatile。当前访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile。
由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。
10. synchronized与lock的区别,使用场景是什么,看过synchronized的源码没?
synchronized与lock的区别
synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
lock(显示锁):需要显示指定起始位置和终止位置。一般使用ReentrantLock类作为锁,多个线程中必须要使用一个ReentrantLock类作为对象才能保证锁的生效,且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。在Java1.5中synchronize是性能低效的。因为 这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。但是到了Java1.6发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。
synchronized采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其 他线程只能依靠阻塞来等待线程释放锁。Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就 是CAS操作(Compare and Swap)。
3004

被折叠的 条评论
为什么被折叠?



