java面试题

原文链接:http://www.cnblogs.com/wupeixuan/p/8908524.html
(1)java的优势

平台无关性,垃圾回收

(2)java特性

封装,继承,多态(重写与重载)

(3)abstract与interface

相同点:
都不能被实例化
区别

  1. 抽象类中可以有构造方法,可以有抽象方法和具体方法;接口完全抽象,不能有构造方法和具体方法,且方法都是抽象的。
  2. 抽象类可以继承一个类或实现多个接口,其子类只能继承一个抽象类;接口只能继承接口,子类可以实现多个接口。
  3. 抽象类修饰符可以使用public,default,protected等;而interface默认为public abstract,且不可更改。
  4. 抽象类中可以包含静态方法;而接口中不行。
  5. 抽象类中可以包含任意类型的静态成员变量;而接口中必须是public static final类型的成员变量

注意:静态方法和构造方法不能具有抽象属性,即(不能有抽象构造方法或抽象静态方法)
含有抽象方法的类一定是抽象类,而抽象方法不一定含有抽象方法,也可能只有非抽象的普通方法。

(4)java反射机制

什么是反射及详细解析
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.

(5)super()与this()不能同时使用

不能同时使用,this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。

(6)hashcode,equals,Object的这两个方法默认返回什么?描述了一下为什么重写equals方法必须重写hashcode方法

默认的hashCode方法会利用对象的地址来计算hashcode值,不同对象的hashcode值是不一样的。

public boolean equals(Object obj) {
        return (this == obj);
    }

可以看出Object类中的equals方法与“==”是等价的,也就是说判断对象的地址是否相等。Object类中的equals方法进行的是基于内存地址的比较。
一般对于存放到Set集合或者Map中键值对的元素,需要按需要重写hashCode与equals方法,以保证唯一性。

(7)final

final表面意思是不可更改的,恒量的。根据修饰的位置不同作用也不同,针对三种情况

1、final修饰变量:被final修饰的变量必须要初始化,切初始化赋值后不能再重新赋值(既不能再改变)

final变量初始化有三种方式:(1)定义时初始化 (2)在构造方法中赋值 (3)在非静态块中初始化

2、final修饰方法:被final修饰的方法不能被重写
3、final修饰类:被final修饰的类不能被继承,(一个点,final和abstrack不能同时出现)

其余的点:
1、在匿名类中所有的变量都必须是final变量——》why
2、接口中声明的所有变量本身就是final的
3、final方法在编译阶段绑定,称为静态绑定
4、将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
5、按照Java代码惯例,final变量就是常量,而且通常常量名要大写。

(8)String,StringBuffer,StringBuilder区别

1、String内容不可变,StringBuffer和StringBuilder内容可变;
2、StringBuilder非线程安全(单线程使用),String与StringBuffer线程安全(多线程使用,补充:因为我认为String是一个不可变的类,不能被修改,所以说是线程安全;而StringBuffer类中的方法被Synchronized修饰);
3、如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer

(9)String为什么不可变

String 的底层实现是依靠 char[] 数组,既然依靠的是基础类型变量,那么他一定是可变的, String 之所以不可变,是因为 Java 的开发者通过技术实现,隔绝了使用者对 String 的底层数据的操作。
导致各种逻辑错误:比如String str1 = “abc”; String str2 = “abc”;
其实str1和str2指向同一个String对象,如String能改变,那改变其中一个就会导致所有的指向这个String对象的字符串发生改变。

(10)String,是否可以继承,“+”怎样实现

String并不能被继承,因为String是final修饰的,而final修饰的类是不能被修饰的。

// 程序编译期即加载完成对象s1为"ab"
String s1 = "a" + "b";  
// 这种方式,JVM会先创建一个StringBuilder,然后通过其append方法完成累加操作
String s1 = "a";
String s2 = "b"; 
String s3 = s1 + s2; // 等效于 String s3 = (new StringBuilder(s1)).append(s2).toString();

(11)关于字符串常量池

详情解析
注意几点:
1、String str = new String(“abc”);像这样new出来的字符串对象,只会在堆中生成一个String对象,而不会在字符串常量池中存储字符串。
2、String str = “abc”:像这样的赋值创建字符串,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
3、intern()方法:jdk1.7前后不一样,JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,**如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。**简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池1.7后则是将在堆上的地址引用复制到常量池。

(12)map、list、set

Map集合继承树
Collection集合继承树
总结解析
List:

  1. 可以允许重复的对象。
  2. 可以插入多个null元素。
  3. 是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
  4. 常用的实现类有 ArrayList、LinkedList 和 Vector(线程安全的)。ArrayList 最为流行,它提供了使用索引的随意访问,而LinkedList 则对于经常需要从 List中添加或删除元素的场合更为合适。

Set:

  1. 不允许重复对象
  2. 无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。
  3. 只允许一个 null 元素
  4. Set 接口最流行的几个实现类是 HashSet、LinkedHashSet(有序的,它继承于 HashSet、又基于 LinkedHashMap 来实现的) 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。

Map:

  1. Map不是collection的子接口或者实现类。Map是一个接口。
  2. Map 的 每个 Entry 都持有两个对象,也就是一个键(Key)一个值(Value),Map 可能会持有相同的值对象但键对象(Key)必须是唯一的。
  3. TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
  4. Map 里你可以有多个 null 值(Value)但最多只能有一个 null 键(Key)。
  5. Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable(线程安全) 和 TreeMap。

(13)Set如何保证元素不重复

HashSet中add()中调用了HashMap的put(),将一个key-value对放入HashMap中时,首先根据key的hashCode()返回值决定该Entry的存储位置,如果两个key的hash值相同,那么它们的存储位置相同。如果这个两个key的equals比较返回true。那么新添加的Entry的value会覆盖原来的Entry的value,key不会覆盖。因此,如果向HashSet中添加一个已经存在的元素,新添加的集合元素不会覆盖原来已有的集合元素。

(14)说一说对Java io的理解

链接

(15)NIO与BIO的了解以及说一下区别

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO(NIO.2):异步非阻塞式IO服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,

BIO、NIO、AIO适用场景分析:

  1. BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
  2. NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
  3. AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

(16)java并发

Java是一种多线程编程语言,我们可以使用Java来开发多线程程序。 多线程程序包含两个或多个可同时运行的部分,每个部分可以同时处理不同的任务,从而能更好地利用可用资源,特别是当您的计算机有多个CPU时。多线程使您能够写入多个活动,可以在同一程序中同时进行操作处理。

(17)死锁

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源被阻塞时,对已获得的资源保持不放
  3. 不可抢夺条件:进程已获得的资源,在未使用完之前,不可强行抢夺
  4. 循环等待条件:若干线程形成一种头尾相接的循环等待资源关系

避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

(18)wait()与sleep()

  1. wait和notify方法定义在Object类中,因此会被所有的类所继承。 这些方法都是final的,即它们都是不能被重写的,不能通过子类覆写去改变它们的行为。而sleep方法是在Thread类中是由native修饰的,本地方法
  2. 当线程调用了wait()方法时,它会释放掉对象的锁。另一个会导致线程暂停的方法:Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。
  3. 因为wait方法会释放锁,所以调用该方法时,当前的线程必须拥有当前对象的monitor,也即lock,就是锁。要确保调用wait()方法的时候拥有锁,即wait()方法的调用必须放在synchronized方法或synchronized块中

(19)ArrayList和LinkedList有什么区别?

  1. ArrayList是实现了基于动态数组的数据结构,LinkedList基于双向链表的数据结构。
  2. 对于随机访问get和set(查询与修改)ArrayList优于LinkedList,因为LinkedList要移动指针。
  3. 对于增加和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

(20)关于HashMap

详解1
详解2
HashMap的扩容
定位哈希桶数组的索引位置:

  1. 拿到key的hashcode值——》hashCode()
  2. 将hashcode值的高16为参与运算,计算hash值(JDK1.8中,将hashCode的高16位与hashCode进行异或运算(不同为1是异或运算))——》final int hash(Object k)
  3. 将计算出来的hash值与(table.length-1)进行&运算(即indexFor()方法)——》static int indexFor(int h, int length)

get方法:

  1. 先对table进行校验,校验是否为空,length是否大于0
  2. 使用table.length - 1和hash值进行位与运算,得出在table上的索引位置,将该索引位置的节点赋值给first节点,校验该索引位置是否为空
  3. 检查first节点的hash值和key是否和入参的一样,如果一样则first即为目标节点,直接返回first节点
  4. 如果first的next节点不为空则继续遍历
  5. 如果first节点为TreeNode,则调用getTreeNode方法查找节点(红黑树)
  6. 如果first节点不为TreeNode,则调用普通的遍历链表方法查找目标节点(单链表)
  7. 如果查找不到目标节点则返回空

put方法:

  1. 校验table是否为空或者length等于0,如果是则调用resize方法(见下文resize方法)进行初始化

  2. 通过hash值计算索引位置,将该索引位置的头节点赋值给p节点,如果该索引位置节点为空则使用传入的参数新增一个节点并放在该索引位置

  3. 判断p节点的key和hash值是否跟传入的相等,如果相等, 则p节点即为要查找的目标节点,将p节点赋值给e节点

  4. 如果p节点不是目标节点,则判断p节点是否为TreeNode,如果是则调用红黑树的putTreeVal方法(见下文代码块4)查找目标节点

  5. 走到这代表p节点为普通链表节点,则调用普通的链表方法进行查找,并定义变量binCount来统计该链表的节点数

  6. 如果p的next节点为空时,则代表找不到目标节点,则新增一个节点并插入链表尾部,并校验节点数是否超过8个,如果超过则调用treeifyBin方法(见下文代码块6)将链表节点转为红黑树节点

  7. 如果遍历的e节点存在hash值和key值都与传入的相同,则e节点即为目标节点,跳出循环

  8. 如果e节点不为空,则代表目标节点存在,使用传入的value覆盖该节点的value,并返回oldValue

  9. 如果插入节点后节点数超过阈值,则调用resize方法(见下文resize方法)进行扩容

解决哈希冲突的方法:详解1 详情2

  1. 链地址法(默认):将新产生冲突的Entry放在表头
  2. 开放定址法:这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi
  3. 再哈希法:这种方法是同时构造多个不同的哈希函数:
    Hi=RH1(key) i=1,2,…,k,当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。

(21)HashTable为什么是线程安全的?

使用synchronized锁住了。

(22)HashMap、Hashtable、concurrenthashmap

对Map的综述解析(重点)
HashMap与Hashtable:
  (1). HashMap和Hashtable的实现模板不同:虽然二者都实现了Map接口,但HashTable继承于Dictionary类,而HashMap是继承于AbstractMap。Dictionary是是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作。
  
  (2). HashMap和Hashtable对键值的限制不同:HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。

(3). HashMap和Hashtable的线程安全性不同:Hashtable的方法是同步的,实现线程安全的Map;而HashMap的方法不是同步的,是Map的非线程安全实现。

(4). HashMap和Hashtable的地位不同:在并发环境下,Hashtable虽然是线程安全的,但是我们一般不推荐使用它,因为有比它更高效、更好的选择ConcurrentHashMap;而单线程环境下HashMap拥有比Hashtable更高的效率(Hashtable的操作都是同步的,导致效率低下),所以更没必要选择它了。

彻头彻尾理解ConcurrentHashMap

(23)多线程实现方法

  1. 继承Thread类创建线程类,重写run方法,run方法就是代表线程需要完成的任务,调用线程对象的start()来启动该线程,线程类已经继承了Thread类,所以不能再继承其他父类。
  2. 实现Runnable接口创建线程类,定义Runnable实现类,重写run方法
  3. 实现Callable接口,重写call()方法,call()作为线程的执行体,具有返回值
  4. 线程池,使用线程池产生线程对象java.util.concurrent.ExecutorService、java.util.concurrent.Executors;

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时
候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。

假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。

如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。 一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。
详情

runnable与callable的区别

Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在
JDK1.5增加的。它们的主要区别是Callable的call()方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。点击这里详情

(24)synchronized 和 ReentrantLock

技术点:
1、锁的类型:

1、可重入锁:在执行对象中所有同步方法不用再次获得锁(详解)
2、可中断锁:在等待获取锁过程中可中断
3、公平锁:按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利
4、读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写

详解

类别synchronizedLock
存在层次Java的关键字,在jvm层面上是一个类
锁的释放1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁在finally中必须释放锁,不然容易造成线程死锁
锁的获取假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态无法判断可以判断
锁类型可重入 不可中断 非公平 悲观锁可重入 可判断 可公平(两者皆可)乐观锁
性能少量同步大量同步

悲观锁与乐观锁 详解
读操作频繁用乐观锁,写操作频繁用悲观锁。

(25)AQS

AQS是JDK1.5提供的一个基于FIFO等待队列实现的一个用于实现同步器的基础框架
AQS定义了一套多线程访问共享资源的同步器框架

ReentrantLock中的AQS

它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,具体volatile的语义,在此不述。state的访问方式有三种:

getState()
setState()
compareAndSetState()

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
  不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

(26)CAS

CAS是什么?

CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。是一种实现并发算法时常用到的技术,CAS需要有3个操作数:当前内存值V,期望值A,新值B。
CAS指令执行时,当且仅当要更新的值V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

CAS的缺点

  1. 循环时间长开销很大:
    我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
  2. 只能保证一个共享变量的原子操作
    当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
  3. ABA问题
    如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?
    如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。 Java并发包为了解决这个问题,提供了一个**带有标记的原子引用类“AtomicStampedReference”,**它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

(27)可重入锁的设计思路

很透彻的理解ReenTrantLock可重入锁(和synchronized的区别)总结
可重入性:
在获取锁的时候,如果当前线程之前已经获取到了锁,就会把state加1,在释放锁的时候会先减1,这样就保证了同一个锁可以被同一个线程获取多次,而不会出现死锁的情况。这就是ReentrantLock的可重入性。

(28)juc(java.util.concurrent)包内有哪些类

java并发编程之CountDownLatch与CyclicBarrier
CountDownLatch与CyclicBarrier的区别:
CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;
CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行。

对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。

CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。

(29)BlockingQueue

详解
BlockingQueue的核心方法:
  1.放入数据
    (1)offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程);      
    (2)offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
    (3)put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
  2. 获取数据
    (1)poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
    (2)poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
    (3)take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
    (4)drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
    
实现了BlockingQueue的几个类:

  1. ArrayBlockingQueue
      基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
      ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
  2. LinkedBlockingQueue
      基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回**;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。**而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
      作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。
  3. DelayQueue
      DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
      使用场景:
      DelayQueue使用场景较少,但都相当巧妙,常见的例子比如使用一个DelayQueue来管理一个超时未响应的连接队列。
  4. PriorityBlockingQueue
       基于优先级的(优先级的判断通过构造函数传入的Compator对象来决定),需注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。

(29)线程池

详细解析

线程池分类及介绍:
1.FixedThreadPool

1.1 通过Exector的newFixedThreadPool静态方法来创建
1.2 线程数量固定的线程池
1.3 只有核心线程切并且不会被回收
1.4 当所有线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来

2.CachedThreadPool

2.1 通过Exector的newCachedThreadPool静态静态方法来创建
2.2 线程数量不定的线程池
2.3 只有非核心线程,最大线程数量为Integer.MAX_VALUE,可视为任意大
2.4 有超时机制,时长为60s,即超过60s的空闲线程就会被回收
2.5 当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。因此任何任务都会被立即执行
2.6 该线程池比较适合执行大量耗时较少的任务

3.ScheduledThreadPool

3.1 通过Exector的newScheduledThreadPool静态方法来创建
3.2 核心线程数量是固定的,而非核心线程数不固定的,并且非核心线程有超时机制,只要处于闲置状态就会被立即回收
3.3 该线程池主要用于执行定时任务和具有固定周期的重复任务

4.SingleThreadPool

4.1 通过Exector的newSingleThreadPool静态方法来创建
4.2 只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。因此在这些任务之间不需要处理线程同步的问题

线程池的排队策略与拒绝策略:

corePoolSize参数的意义:

核心线程数
1、核心线程会一直存活,即使没有任务需要执行
2、当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
3、设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭

(30)一般线程和守护线程的区别

Java中的线程可以分为守护线程(Daemon)和用户线程(User)
任何线程都可以设置成守护线程和用户线程,Thread.setDaemon(boolean on);True就是把该线程设置成守护线程,反之则是用户线程。必须在Thread.start()之前设置,否则会抛出异常。

(31)java中的异常

Throwable是java中各种异常的顶层类。
异常图
|—Error:错误,一般表示的是jvm本身的错误,如:系统崩溃,虚拟机错误,内存溢出等,这类错误不能通过代码处理,建议将程序终止
|—Exception:表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。包含RuntimeException(运行时异常)和 Checked Exception(受检异常)

  1. RuntimeException(运行时异常): 是一种Unchecked Exception,javac(编译器)在编译时不会提示和发现这种异常,也不要求在程序中处理这种异常。我们可以处理也可以不处理。一般来说,RuntimeException发生的时候,一般是代码有问题! 常见的RuntimeException有NullPointException、ClassCastException(类型转换异常)、IllegalArgumentException(非法参数异常)、IndexOutOfBoundException等。
  2. Checked Exception(检查异常): 不是RuntimeException的异常都是Checked Exception,在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。。如SQLException , IOException,ClassNotFoundException 等。

(32)servlet流程

servlet运行流程
Servlet生命周期
1、实例化:通过反射机制创建Servlet对象

  • 容器收到请求时创建servlet对象(默认)
  • 容器启动后,立即创建servlet对象,等待请求的到来(<load-on-startup>1</load-on-startup>来设置)

2、初始化

  • 容器调用servlet对象的init()方法,该方法只会执行一次。(初始化servlet信息,给servlet分配资源)

3、响应

  • 容器中调用servlet中的service方法来处理请求。
  • service方法是如何实现的: 依据请求类型(get/post)调用相应的doXXX方法

4、销毁

  • 长时间没有被调用或服务器关闭时,会调用对象的destroy方法销毁servlet对象,该方法只会执行一次

(33)forward(转发)与redirect(重定向)

这是servlet中的两种主要的跳转方式

1、forward(转发)

(1)什么是转发?

  • 一个web组件将未完成的处理转交给另外一个web组件来继续做,比如一个servlet将处理结果转交给一个jsp来展现。

(2)如何转发?

  • step1、绑定数据到request上,request.setAttribute(String name , Object obj);
  • step2、获得转发器:RequestDispatcher rd = request.getRequestDispatcher(String uri);
  • step3、进行转发:rd.forward(request,response);

2、redirect(重定向)

(1)什么是重定向?

  • 重定向是服务器通知浏览器向一个新的地址发送请求

(2)如何重定向

  • response.sendRedirect(String url);重定向时,容器会先清空response对象中保存的数据。

3、转发与重定向的区别

转发:同一次请求,在服务器端完成跳转,速度快,地址栏没有变化,同一台服务器,资源共享
重定向:两次不同请求,客户端完成跳转,速度慢,地址栏发生变化,可以在不同服务器,资源不共享

(34)JVM内存划分

1、堆: 几乎所有的对象实例都在这里分配内存,垃圾回收的主要区域。所有线程共享。

2、方法区: 存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。运行时常量池在方法区内。所有线程共享。

3、虚拟机栈: Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表、操作数栈、常量池引用等。每个线程都会有自己的java栈,互不干扰。

4、本地方法栈: 与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
5、程序计数器: 记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)

(35)JVM的垃圾回收

详情

(36)java的类加载过程

  1. 加载: 简单来说,加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。
  2. 链接: 链接分为三个小部分
    • 验证: 主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
    • 准备: 主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值。
    • 解析: 将常量池内的符号引用替换为直接引用的过程。
  3. 初始化: 这个阶段主要是对类变量初始化,是执行类构造器的过程。换句话说,只对static修饰的变量或语句进行初始化。

(37)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值