内部类
(1) 在方法间定义的非静态内部类:
● 外围类和内部类可互相访问自己的私有成员。
● 内部类中不能定义静态成员变量。
(2)在方法间定义的静态内部类:
● 只能访问外部类的静态成员。
(3)在方法中定义的局部内部类:
● 该内部类没有任何的访问控制权限
● 外围类看不见方法中的局部内部类的,但是局部内部类可以访问外围类的任何成员。
● 方法体中可以访问局部内部类,但是访问语句必须在定义局部内部类之后。
● 局部内部类只能访问方法体中的常量,即用final修饰的成员。
(4)在方法中定义的匿名内部类:
● 没有构造器,取而代之的是将构造器参数传递给超类构造器。
抽象类和接口的异同
1.语法层面上的区别
1)抽象类可以提供成员方法的实现细节,而接口中只能存在publicabstract方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是publicstatic final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2.设计层面上的区别
1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个"是不是"的关系,而 接口 实现则是"有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了pptB和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和pptC进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
java的gc相关 :
gc算法有标记清除算法,标记整理算法和复制算法。老生代采用前两种,新生代采用复制算法。
1) 从JVM内存模型开始说起,在纸上画出大概的组成部分,然后说出每个组成部分的特点
2) 开始说说分代GC,这时就把GC算法引入进来,再结合每个区域的特点 把Minor GC 和Full GC 引入进来
2) 可以跟他说说垃圾回收器,Serial 、 ParNew 、CMS 等等
jvm内存模型
Java虚拟机运行时数据区域包括
程序计数器(当前线程所执行的字节码的行号指示器,线程私有的)、
java虚拟机栈(线程私有的,每个方法在执行的同时都会创建一个栈帧,栈帧存储局部变量表、操作数栈、动态链接、方法出口)、
本地方法栈(为虚拟机使用到的native方法服务)、
java堆(存放对象实例, 所有线程共享 ,所有的对象实例以及数组都要在堆上分配)、
方法区( 各个线程共享 的内存区域,已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据)、
Jvm内存分配与回收策略
1. 对象优先在eden分配,当Eden没有足够空间时,将进行一次minorGC。
2. 大对象直接进入老年代。
3. 长期存活的对象将进入老年代。
4. 动态对象年龄判定
5. 空间分配担保
垃圾回收器
Serial
新生代,单线程实现,在他进行垃圾收集时,必须暂停所有其他的工作线程,知道他收集结束。
ParNew
Serial收集器的多线程实现版本,用于新生代
Parallel Scavenge
目标是达到可控制的吞吐量,吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间到比值, 即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),并行的多线程的新生代垃圾收集器,采用复制算法。
Serial Old收集器
Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记整理算法。
Parallel Old收集器
Parallel Old是ParallelScavenge收集器得老年代版本,多线程和标记整理算法。
CMS收集器
以获取最短回收停顿时间为目标的收集器。基于标记清除算法。
G1
并行,分代收集,标记整理算法,可预测的停顿
hashmap和concurrenthashmap实现
1.Java的hashmap实现 :哈希数组,数组长度一定是奇数,解决冲突用单链表。
HashMap共有四个构造方法。构造方法中提到了两个很重要的参数:初始容量和加载因子。这两个参数是影响HashMap性能的重要参数, 其中容量表示哈希表中槽的数量(即哈希数组的长度),初始容量是创建哈希表时的容量(从构造函数中可以看出,如果不指明,则默认为16),加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 resize 操作(即扩容)。 K ey和value允许为null。
G et方法如何实现?hash方法获取key的hash值,在此hash对应的链表上找到key相同的元素。
K ey为null固定存放在table[0]。
P ut方法
如果key不为null,则同样先求出key的hash值,根据hash值得出在table中的索引,而后遍历对应的单链表,如果单链表中存在与目标key相等的键值对,则将新的value覆盖旧的value,比将旧的value返回, 如果找不到与目标key相等的键值对,或者该单链表为空,则将该键值对插入到改单链表的头结点位置(每次新插入的节点都是放在头结点的位置)
两外注意最后两行代码,每次加入键值对时,都要判断当前已用的槽的数目是否大于等于阀值(容量*加载因子),如果大于等于,则进行扩容,将容量扩为原来容量的2倍。
核心的两个方法是indexOf和hash,
我们重点来分析下求hash值和索引值的方法,这两个方法便是HashMap设计的最为核心的部分,二者结合能保证哈希表中的元素尽可能均匀地散列。
计算哈希值的方法如下:
static int hash( int h) {
h ^= (h >>> 20 ) ^ (h >>> 12 );
return h ^ (h >>>7 ) ^ (h >>> 4 );
}
它只是一个数学公式,IDK这样设计对hash值的计算,自然有它的好处,至于为什么这样设计,我们这里不去追究,只要明白一点,用的位的操作使hash值的计算效率很高。
由hash值找到对应索引的方法如下:
static int indexFor( int h, int length) {
return h & (length-1 );
}
这个我们要重点说下,我们一般对哈希表的散列很自然地会想到用hash值对length取模(即除法散列法),Hashtable中也是这样实现的,这种方法基本能保证元素在哈希表中散列的比较均匀,但取模会用到除法运算,效率很低, HashMap中则通过h&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多,这也是HashMap对Hashtable的一个改进。
接下来,我们分析下为什么哈希表的容量一定要是2的整数次幂。 首先, length为2的整数次幂的话,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率;其次,length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间,因此,length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。
HashMap通过hashcode对其内容进行快速查找,而 TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
HashMap 非线程安全 TreeMap 非线程安全
2.ConcurrentHashMap分段锁
http://www.cnblogs.com/ITtangtang/p/3948786.html
http://goldendoc.iteye.com/blog/1103980
java多线程
1.BlockingQueue
ja va.util.concurrent包里的BlockingQueue接口表示一个线程安放入和提取实例的队列。本小节我将给你演示如何使用这个BlockingQueue。
本节不会讨论如何在Java中实现一个你自己的BlockingQueue。如果你对那个感兴趣,参考《Java并发指南》
BlockingQueue用法
BlockingQueue通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。下图 是对这个原理的阐述:
一个线程往里边放,另外一个线程从里边取的一个BlockingQueue。
一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界点。也就是说,它是有限的。如果该阻塞队列到达了其临界点,负责生产的线程将会在往里边插入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中拿走一个对象。
负责消费的线程将会一直从该阻塞队列中拿出对象。如果消费线程尝试去从一个空的队列中提取对象的话,这个消费线程将会处于阻塞之中,直到一个生产线程把一个对象丢进队列。
BlockingQueue的方法
BlockingQueue具有4组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:
抛异常 |
特定值 |
阻塞 |
超时 |
|
插入 |
add(o) |
offer(o) |
put(o) |
offer(o, timeout, timeunit) |
移除 |
remove(o) |
poll(o) |
take(o) |
poll(timeout, timeunit) |
检查 |
element(o) |
peek(o) |
四组不同的行为方式解释:
1. 抛异常 :如果试图的操作无法立即执行,抛一个异常。
2. 特定值 :如果试图的操作无法立即执行,返回一个特定的值(常常是true / false)。
3. 阻塞 :如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
4. 超时 :如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是true / false)。
无法向一个BlockingQueue中插入null。如果你试图插入null,BlockingQueue将会抛出一个NullPointerException。
可以访问到BlockingQueue中的所有元素,而不仅仅是开始和结束的元素。比如说,你将一个对象放入队列之中以等待处理,但你的应用想要将其取消掉。那么你可以调用诸如remove(o)方法来将队列之中的特定对象进行移除。但是这么干效率并不高(译者注:基于队列的数据结构,获取除开始或结束位置的其他对象的效率不会太高),因此你尽量不要用这一类的方法,除非你确实不得不那么做
2.锁lock
java.util.concurrent.locks.Lock是一个类似于synchronized块的线程同步机制。但是Lock比synchronized块更加灵活、精细。
顺便说一下,在我的《 Java并发指南》中我对如何实现你自己的锁进行了描述。
Java Lock例子
既然Lock是一个接口,在你的程序里需要使用它的实现类之一来使用它。以下是一个简单示例:
Lock lock = newReentrantLock();
lock.lock();
//critical section
lock.unlock();
首先创建了一个Lock对象。之后调用了它的lock()方法。这时候这个lock实例就被锁住啦。任何其他再过来调用lock()方法的线程将会被阻塞住,直到锁定lock实例的线程调用了unlock()方法。最后unlock()被调用了,lock对象解锁了,其他线程可以对它进行锁定了。
Java Lock实现
java.util.concurrent.locks包提供了以下对Lock接口的实现类:
· ReentrantLock
Lock和synchronized代码块的主要不同点
一个Lock对象和一个synchronized代码块之间的主要不同点是:
· synchronized代码块不能够保证进入访问等待的线程的先后顺序。
· 你不能够传递任何参数给一个synchronized代码块的入口。因此,对于synchronized代码块的访问等待设置超时时间是不可能的事情。
· synchronized块必须被完整地包含在单个方法里。而一个Lock对象可以把它的lock()和unlock()方法的调用放在不同的方法里。
Lock的方法
Lock接口具有以下主要方法:
· lock()
· lockInterruptibly()
· tryLock()
· tryLock(long timeout, TimeUnittimeUnit)
· unlock()
lock()将Lock实例锁定。如果该Lock实例已被锁定,调用lock()方法的线程将会阻塞,直到Lock实例解锁。
lockInterruptibly()方法将会被调用线程锁定,除非该线程被打断。此外,如果一个线程在通过这个方法来锁定Lock对象时进入阻塞等待,而它被打断了的话,该线程将会退出这个方法调用。
tryLock()方法试图立即锁定Lock实例。如果锁定成功,它将返回true,如果Lock实例已被锁定该方法返回false。这一方法永不阻塞。
tryLock(long timeout, TimeUnit timeUnit)的工作类似于tryLock()方法,除了它在放弃锁定Lock之前等待一个给定的超时时间之外。
unlock()方法对Lock实例解锁。一个Lock实现将只允许锁定了该对象的线程来调用此方法。其他(没有锁定该Lock对象的线程)线程对unlock()方法的调用将会抛一个未检查异常(RuntimeException)。
R eentrantlock详解
http://www.cnblogs.com/zhimingyang/p/5702752.html
3.ThreadLocal
作用:维持线程封闭性。线程封闭性:一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭。(JDBC的Connection对象)
原理:使线程中的某个值与保存值的对象关联起来。
4.V olatile :可见性,防止指令重排
5.原子类与CAS
java中CAS的实现
CAS就是Compareand Swap的意思,比较并操作。很多的cpu直接支持CAS指令。CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
JDK1.5中引入了底层的支持,在int、long和对象的引用等类型上都公开了CAS的操作,并且JVM把它们编译为底层硬件提供的最有效的方法,在运行CAS的平台上,运行时把它们编译为相应的机器指令。在java.util.concurrent.atomic包下面的所有的原子变量类型中,比如AtomicInteger,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作。
在CAS操作中,会出现ABA问题。就是如果V的值先由A变成B,再由B变成A,那么仍然认为是发生了变化,并需要重新执行算法中的步骤。有简单的解决方案:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后为变为A,版本号也是不同的。AtomicStampedReference和AtomicMarkableReference支持在两个变量上执行原子的条件更新。AtomicStampedReference更新一个“对象-引用”二元组,通过在引用上加上“版本号”,从而避免ABA问题,AtomicMarkableReference将更新一个“对象引用-布尔值”的二元组。
线程的几种状态 : 线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。
sleep和wait有什么区别?
第一种解释:
功能差不多,都用来进行线程控制,他们最大本质的区别是:sleep()不释放同步锁,wait()释放同步缩.
还有用法的上的不同是:sleep(milliseconds)可以用时间指定来使他自动醒过来,如果时间不到你只能调用interreput()来强行打断;wait()可以用notify()直接唤起.
第二种解释:
sleep是Thread类的静态方法。sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行,例如:
try{
System.out.println("I'm going to bed");
Thread.sleep(1000);
System.out.println("I wake up");
}
catch(IntrruptedException e) {
}
wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者,例如:
//Thread 1
try{
obj.wait();//suspend thread until obj.notify() is called
}
catch(InterrputedException e) {
}
第三种解释:
这两者的施加者是有本质区别的.
sleep()是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,也就是说,在线程里面决定.好比如说,我要做的事情是"点火->烧水->煮面",而当我点完火之后我不立即烧水,我要休息一段时间再烧.对于运行的主动权是由我的流程来控制.
而wait(),首先,这是由某个确定的对象来调用的,将这个对象理解成一个传话的人,当这个人在某个线程里面说"暂停!",也是thisOBJ.wait(),这里的暂停是阻塞,还是"点火->烧水->煮饭",thisOBJ就好比一个监督我的人站在我旁边,本来该线 程应该执行1后执行2,再执行3,而在2处被那个对象喊暂停,那么我就会一直等在这里而不执行3,但正个流程并没有结束,我一直想去煮饭,但还没被允许,直到那个对象在某个地方说"通知暂停的线程启动!",也就是thisOBJ.notify()的时候,那么我就可以煮饭了,这个被暂停的线程就会从暂停处 继续执行.
其实两者都可以让线程暂停一段时间,但是本质的区别是一个线程的运行状态控制,一个是线程之间的通讯的问题
在java.lang.Thread类中,提供了sleep(),
而java.lang.Object类中提供了wait(),notify()和notifyAll()方法来操作线程
sleep()可以将一个线程睡眠,参数可以指定一个时间。
而wait()可以将一个线程挂起,直到超时或者该线程被唤醒。
wait有两种形式wait()和wait(milliseconds).
sleep和wait的区别有:
1,这两个方法来自不同的类分别是Thread和Object
2,最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3,wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在
任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
4,sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
http://blog.csdn.net/xusongsong520/article/details/8602177
J ava中的值传递和引用传递
http://www.cnblogs.com/clara/archive/2011/09/17/2179493.html
S tringbuffer和stringbuilder的区别
1. 在执行速度方面的比较:StringBuilder > StringBuffer
2. StringBuffer与StringBuilder,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,不像String一样创建一些对象进行操作,所以速度就快了。
3. StringBuilder:线程非安全的
StringBuffer:线程安全的
当我们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最快,但是可以保证StringBuffer是可以正确操作的。当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,就是速度的原因。
守护线程和一般线程
在 Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。