java回忆篇

内部类

(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也就没有工作可做了,也就没有继续运行程序的必要了。

 

你可能感兴趣的:(java回忆篇)