java常见面试题整理

ThreadLocal

Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量。
采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本,每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。
ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。

深入研究java.lang.ThreadLocal类

深入分析 ThreadLocal 内存泄漏问题

Java内存模型:

Java虚拟机规范中将Java运行时数据分为六种。

  1. 程序计数器:是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的,为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器,互不影响,该区域为“线程私有”。
  2. Java虚拟机栈:线程私有的,与线程生命周期相同,用于存储局部变量表,操作栈,方法返回值。局部变量表放着基本数据类型,还有对象的引用。
  3. 本地方法栈:跟虚拟机栈很像,不过它是为虚拟机使用到的Native方法服务。
  4. Java堆:所有线程共享的一块内存区域,对象实例几乎都在这分配内存。
  5. 方法区:各个线程共享的区域,储存虚拟机加载的类信息,常量,静态变量,编译后的代码。
  6. 运行时常量池:代表运行时每个class文件中的常量表。包括几种常量:编译时的数字常量、方法或者域的引用。

    Java中JVM虚拟机详解

java虚拟机常用命令工具

  1. jps:显示系统中所有Hotspot虚拟机进程
  2. .jstat:查看jvm的GC情况
  3. jstack:显示虚拟机的线程栈信息
  4. jinfo:显示虚拟机的配置信息
  5. jmap:用于生成虚拟机的内存快照信息(打印出某个java进程(使用pid)内存内的,所有‘对象’的情况(如:产生那些对象,及其数量))
    使用jmap dump 分析JVM内存状态
    java虚拟机常用命令工具

java GC

java GC是在什么时候,对什么东西,做了什么事情?

在什么时候:
  1. 新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存。在某次GC过程中,如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去。
  2. 大对象以及长期存活的对象直接进入老年区。
  3. 当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full
    GC以尽可能地获得老年区的空间。

对什么东西:从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象。

做什么:

新生代:复制清理;
老年代:标记-清除和标记-整理算法;
永久代:存放Java中的类和加载类的类加载器本身。

Java GC的那些事(上)
Java GC的那些事(下)

Synchronized 与Lock

  1. ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
    线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
    如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
    如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

  2. ReentrantLock获取锁定与三种方式:
    a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
    b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
    c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
    d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断

    总体的结论先摆出来:

    • synchronized:
      在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
    • ReentrantLock:
      ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。

Lock与synchronized 的区别

String、StringBuffer与StringBuilder之间区别

每次操作字符串,String会生成一个新的对象,而StringBuffer不会;StringBuilder是非线程安全的,StringBuffer是线程安全的

对于三者使用的总结:

  1. 如果要操作少量的数据用 = String
  2. 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
  3. 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

    String、StringBuffer与StringBuilder之间区别

fail-fast

机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件

fail-fast机制

Volatile和Synchronized:

Volatile和Synchronized四个不同点:

  1. 粒度不同,前者针对变量 ,后者锁对象和类
  2. syn阻塞,volatile线程不阻塞
  3. syn保证三大特性,volatile不保证原子性
  4. syn编译器优化,volatile不优化

要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

  • 对变量的写操作不依赖于当前值。
  • 该变量没有包含在具有其他变量的不变式中。

Java并发编程之volatile关键字解析

举例解析Java中Volatile的作用

CAS(Compare And Swap) 无锁算法:

CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

非阻塞同步算法与CAS(Compare and Swap)无锁算法

线程池

线程池的作用:
在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。
常用线程池:ExecutorService 是主要的实现类,其中常用的有
Executors.newSingleThreadPool(),newFixedThreadPool(),newcachedTheadPool(),newScheduledThreadPool()。

线程池原理解析

hashcode(),equal()方法深入解析

Java对于eqauls方法和hashCode方法是这样规定的:

1、如果两个对象相同,那么它们的hashCode值一定要相同;

2、如果两个对象的hashCode相同,它们并不一定相同(上面说的对象相同指的是用eqauls方法比较。)
Java语言对equals()的要求如下,这些要求是必须遵循的。

  • 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
  • 反射性:x.equals(x)必须返回是“true”。
  • 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
  • 还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
    •任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

hashcode(),equal()方法深入解析

自动装箱拆箱

//自动装箱
Integer total = 99;

//自定拆箱
int totalprim = total;

简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

详解Java的自动装箱与拆箱

反射机制

谈谈Java反射机制

如何写一个不可变类?

  • 将类声明为final,所以它不能被继承
  • 将所有的成员声明为私有的,这样就不允许直接访问这些成员
  • 对变量不要提供setter方法
  • 将所有可变的成员声明为final,这样只能对它们赋值一次
  • 通过构造器初始化所有成员,进行深拷贝(deep copy)
  • 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝

如何写一个不可变类?

java nio

  1. Channel,Buffer 和 Selector(多路复用器) 构成了核心的API。其它组件,如Pipe和FileLock
  2. Channel:FileChannel,DatagramChannel,SocketChannel,ServerSocketChanne
  3. Buffer:ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer
  4. 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中
  5. Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,

传统的IO和NIO的区别其本质就是阻塞和非阻塞的区别

阻塞概念:应用程序在获取网络数据的时候,如果网络传输很慢,那么程序就会一直处于等待状态,直到数据传输结束为止。
非阻塞概念:应用程序直接可以获取已经准备好的网络数据,无需等待。
io为同步阻塞的形式nio为同步非阻塞的形式。NIO1.0并没有实现异步的概念,在jdk1.7之后的NIO2.0才真正的实现了异步非阻塞的概念。
同步时:应用程序会直接参与IO读写操作,并且我们的程序会直接阻塞到某一个方法上,直到数据准备就绪。或者采用轮询的策略实时检查数据的就绪状态,如果就绪则获取数据。
异步时:就是所用IO操作交给操作系统处理,我们编写的应用程序不参与IO的操作,我们的程序不需要关心IO的读写,当操作系统完成了IO的读写操作时,会给我们的应用程序发送通知,我们的应用程序直接拿走数据即可。

AIO编程

AIO的特点:
1. 读完了再通知我
2. 不会加快IO,只是在读完后进行通知
3. 使用回调函数,进行业务处理

在理解了NIO的基础上,看AIO,区别在于AIO是等读写过程完成后再去调用回调函数。
NIO是同步非阻塞的
AIO是异步非阻塞的
由于NIO的读写过程依然在应用线程里完成,所以对于那些读写过程时间长的,NIO就不太适合。
而AIO的读写过程完成后才被通知,所以AIO能够胜任那些重量级,读写过程长的任务。

索引

什么时候使用索引:
1. 经常出现在group by,order by和distinc关键字后面的字段
2. 经常与其他表进行连接的表,在连接字段上应该建立索引
3. 经常出现在Where子句中的字段
4. 经常出现用作查询选择的字段

MyISAM和InnoDB索引实现对比

MyISAM和InnoDB索引实现对比

两种类型最主要的差别就是Innodb 支持事务处理与外键和行级锁。

MyISAM和InnoDB索引实现对比

MySQL存储引擎中的MyISAM和InnoDB区别详解

聚集索引和非聚集索引

聚集索引和非聚集索引

spring IOC

Spring支持三种依赖注入方式,分别是属性(Setter方法)注入,构造注入和接口注入。
在Spring中,那些组成应用的主体及由Spring IOC容器所管理的对象被称之为Bean。
Spring的IOC容器通过反射的机制实例化Bean并建立Bean之间的依赖关系。
简单地讲,Bean就是由Spring IOC容器初始化、装配及被管理的对象。
获取Bean对象的过程,首先通过Resource加载配置文件并启动IOC容器,然后通过getBean方法获取bean对象,就可以调用他的方法。
 Spring 3中为Bean定义了5中作用域,分别为singleton(单例)、prototype(原型)、request、session和global session
Spring框架IOC容器和AOP解析

关于Spring的69个面试问答——终极列表

Bean的作用域、生命周期

AOP

Spring AOP两种实现机制是什么?

  1. 如果是有接口声明的类进行AOP 时,spring调用的是java.lang.reflection.Proxy 类来做处理
  2. 如果是没有接口声明的类时, spring通过cglib包和内部类来实现

在AOP,权限控制,事务管理等方面都有动态代理的实现。JDK本身有实现动态代理技术,但是略有限制,即被代理的类必须实现某个接口,否则无法使用
JDK自带的动态代理,因此,如果不满足条件,就只能使用另一种更加灵活,功能更加强大的动态代理技术—— CGLIB。Spring里会自动在JDK的代理和CGLIB之间切换,同时我们也可以强制Spring使用CGLIB。

Spring AOP应用场景:性能检测,访问控制,日志管理,事务等。
默认的策略是如果目标类实现接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理

<aop:aspectj-autoproxy proxy-target-class="true" />配置了这句话的话就会强制使用cglib代理。 默认就是false。

Spring AOP 简介以及简单用法

spring mvc 原理

SpringMVC运行原理
1. 客户端请求提交到DispatcherServlet
2. 由DispatcherServlet控制器查询HandlerMapping,找到并分发到指定的Controller中。
4. Controller调用业务逻辑处理后,返回ModelAndView
5. DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
6. 视图负责将结果显示到客户端
Spring:基于注解的Spring MVC(上)

Spring:基于注解的Spring MVC(下)

一个Http请求

DNS域名解析 –> 发起TCP的三次握手 –> 建立TCP连接后发起http请求 –> 服务器响应http请求,浏览器得到html代码 –> 浏览器解析html代码,并请求html代码中的资源(如javascript、css、图片等) –> 浏览器对页面进行渲染呈现给用户

HTTP 请求方式: GET和POST的比较

GET - 从指定的服务器中获取数据
POST - 提交数据给指定的服务器处理

GET方法:
使用GET方法时,查询字符串(键值对)被附加在URL地址后面一起发送到服务器:
/test/demo_form.jsp?name1=value1&name2=value2
特点:
GET请求能够被缓存
GET请求会保存在浏览器的浏览记录中
以GET请求的URL能够保存为浏览器书签
GET请求有长度限制
GET请求主要用以获取数据

POST方法:
使用POST方法时,查询字符串在POST信息中单独存在,和HTTP请求一起发送到服务器:
POST /test/demo_form.jsp HTTP/1.1
Host: w3schools.com
name1=value1&name2=value2
特点:
POST请求不能被缓存下来
POST请求不会保存在浏览器浏览记录中
以POST请求的URL无法保存为浏览器书签
POST请求没有长度限制

HTTP 请求方式: GET和POST的比较

GET请求中URL的最大长度限制总结

https

https和http的不同

Session与Cookie

  1. session保存在服务器,客户端不知道其中的信息;cookie保存在客户端,服务器能够知道其中的信息。
  2. session中保存的是对象,cookie中保存的是字符串。
  3. session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到。而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的。
  4. Cookie的不可跨域名性

理解Cookie和Session机制

事务

事务的特性:⑴ 原子性(Atomicity),⑵ 一致性(Consistency),⑶ 隔离性(Isolation),⑷ 持久性(Durability)
如果不考虑事务的隔离性,会发生的几种问题:

  1. 脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
  2. 不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
  3. 幻读是事务非独立执行时发生的一种现象。

数据库事务的四大特性以及事务的隔离级别

四种隔离级别:
 
1. Serializable (串行化):可避免脏读、不可重复读、幻读的发生
2. Repeatable read (可重复读):可避免脏读、不可重复读的发生。
3. Read committed (读已提交):可避免脏读的发生。
4. Read uncommitted (读未提交):最低级别,任何情况都无法保证。

spring事务

  1. 切点信息,用于定位实施事物切面的业务类方法
  2. 控制事务行为的事务属性,这些属性包括事物隔离级别,事务传播行为,超时时间,回滚规则。
    Spring通过aop/tx Schema 命名空间和@Transaction注解技术来进行声明式事物配置。

spring事务

Mybatis面试题

每一个Mybatis的应用程序都以一个SqlSessionFactory对象的实例为核心。首先用字节流通过Resource将配置文件读入,然后通过SqlSessionFactoryBuilder().build方法创建SqlSessionFactory,然后再通过SqlSessionFactory.openSession()方法创建一个SqlSession为每一个数据库事务服务。
经历了Mybatis初始化 –>创建SqlSession –>运行SQL语句,返回结果三个过程

mybatis#{}和${}的区别是什么?

1,mybatis${}是Properties文件中的变量占位符,它可以用于标签属性值和sql内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc.Driver。
2,#{}是sql的参数占位符,Mybatis会将sql中的#{}替换为?号,在sql执行前会使用PreparedStatement的参数设置方法,按序给sql的?号占位符设置参数值,比如ps.setInt(0,parameterValue),#{item.name}的取值方式为使用反射从参数对象中获取item对象的name属性值,相当于param.getItem().getName()。

  1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by “111”, 如果传入的值是id,则解析成的sql为order by “id”.
  2. s q l o r d e r b y user_id$,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为order by id.
  3. #方式能够很大程度防止sql注入。
  4. $方式无法防止Sql注入。
  5. $方式一般用于传入数据库对象,例如传入表名
  6. 一般能用#的就别用$.

Mybatis常见面试题

servlet/filter/listener/interceptor区别与联系

  1. servlet:servlet是一种运行服务器端的java应用程序,具有独立于平台和协议的特性,并且可以动态的生成web页面,它工作在客户端请求与服务器响应的中间层
  2. filter:filter是一个可以复用的代码片段,可以用来转换HTTP请求、响应和头信息.Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
    Filter有如下几个用处。

    • 在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest。
    • 根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。
    • 在HttpServletResponse到达客户端之前,拦截HttpServletResponse。
    • 根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。
      Filter有如下几个种类。

    用户授权的Filter:Filter负责检查用户请求,根据请求过滤用户非法请求。
    日志Filter:详细记录某些特殊的用户请求。
    负责解码的Filter:包括对非标准编码的请求解码。
    能改变XML内容的XSLT Filter等。
    Filter可负责拦截多个请求或响应;一个请求或响应也可被多个请求拦截。

创建一个Filter只需两个步骤:
- 建Filter处理类;
- web.xml文件中配置Filter。
3. listener:监听器,从字面上可以看出listener主要用来监听只用。通过listener可以监听web服务器中某一个执行动作,并根据其要求作出相应的响应。通俗的语言说就是在application,session,request三个对象创建消亡或者往其中添加修改删除属性时自动执行代码的功能组件。比如spring 的总监听器 会在服务器启动的时候实例化我们配置的bean对象 、 hibernate 的 session 的监听器会监听session的活动和生命周期,负责创建,关闭session等活动。

Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。主要作用是: 做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等等。
4. interceptor:是在面向切面编程的,就是在你的service或者一个方法,前调用一个方法,或者在方法后调用一个方法,是基于JAVA的反射机制
5. 加载次序
servlet、filter、listener是配置到web.xml中(web.xml 的加载顺序是:context-param -> listener -> filter -> servlet ),interceptor不配置到web.xml中,struts的拦截器配置到struts.xml中。spring的拦截器配置到spring.xml中
6. 几个区别:
1,servlet 流程是短的,url传来之后,就对其进行处理,之后返回或转向到某一自己指定的页面。它主要用来在 业务处理之前进行控制.
2,filter 流程是线性的, url传来之后,检查之后,可保持原来的流程继续向下执行,被下一个filter, servlet接收等,而servlet 处理之后,不会继续向下传递。filter功能可用来保持流程继续按照原来的方式进行下去,或者主导流程,而servlet的功能主要用来主导流程。filter可用来进行字符编码的过滤,检测用户是否登陆的过滤,禁止页面缓存等
3, servlet,filter都是针对url之类的,而listener是针对对象的操作的,如session的创建,session.setAttribute的发生,在这样的事件发生时做一些事情。可用来进行:Spring整合Struts,为Struts的action注入属性,web应用定时任务的实现,在线人数的统计等
4,interceptor 拦截器,类似于filter,不过在struts.xml中配置,不是在web.xml,并且不是针对URL的,而是针对action,当页面提交action时,进行过滤操作,相当于struts1.x提供的plug-in机制,可以看作,前者是struts1.x自带的filter,而interceptor 是struts2 提供的filter.
与filter不同点:(1)不在web.xml中配置,而是在struts.xml中完成配置,与action在一起
( 2 ) 可由action自己指定用哪个interceptor 来在接收之前做事

servlet/filter/listener/interceptor区别与联系

HashMap与HashTable的区别。

  1. HashMap是非线程安全的,HashTable是线程安全的。
  2. HashMap的键和值都允许有null值存在,而HashTable则不行。
  3. 因为线程安全的问题,HashMap效率比HashTable的要高。

HashMap与TreeMap的应用与区别

HashMap与TreeMap的应用与区别

TreeMap原理

  1. TreeMap是红黑树结构,红黑树是有序的,所以TreeMap也是有序的
  2. TreeMap的键不能为null
  3. TreeMap效率不如HashMap,Map需要有序的场合才使用TreeMap

    TreeMap

2种办法让HashMap线程安全

  1. 通过Collections.synchronizedMap()返回一个新的Map,这个新的map就是线程安全的. 这个要求大家习惯基于接口编程,因为返回的并不是HashMap,而是一个Map的实现.
  2. 重改写了HashMap,具体的可以查看java.util.concurrent.ConcurrentHashMap. 这个方法比方法一有了很大的改进.

    http://flyfoxs.iteye.com/blog/2100120

类加载器工作机制:

  1. 装载:将Java二进制代码导入jvm中,生成Class文件。
  2. 连接:a)校验:检查载入Class文件数据的正确性 b)准备:给类的静态变量分配存储空间 c)解析:将符号引用转成直接引用

双亲委派模型

用户自定义加载器(Application ClassLoader)->应用程序加载器->扩展类加载器(Extension ClassLoader)->启动类加载器(Bootstrap ClassLoader)。
双亲委派模型的式作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完全这个加载请求时,子加载器才会尝试自己去加载。
JVM(1):Java 类的加载机制

ConcurrentHashMap

  1. ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全,而同步的HashMap和HashMap的是锁住整个容器,而加锁之后ConcurrentHashMap不需要锁住整个容器,只需要锁住对应的Segment就好了,所以可以保证高并发同步访问,提升了效率。
  2. 可以多线程写。
    ConcurrentHashMap把HashMap分成若干个Segmenet
    1.get时,不加锁,先定位到segment然后在找到头结点进行读取操作。而value是volatile变量,所以可以保证在竞争条件时保证读取最新的值,如果读到的value是null,则可能正在修改,那么就调用ReadValueUnderLock函数,加锁保证读到的数据是正确的。
    2.Put时会加锁,一律添加到hash链的头部。
    3.Remove时也会加锁,由于next是final类型不可改变,所以必须把删除的节点之前的节点都复制一遍。
    4.ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对Hash表的不同Segment进行的修改。

ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全,而同步的HashMap和HashTable的是锁住整个容器,而加锁之后ConcurrentHashMap不需要锁住整个容器,只需要锁住对应的segment就好了,所以可以保证高并发同步访问,提升了效率。

ConcurrentHashMap能够保证每一次调用都是原子操作,但是并不保证多次调用之间也是原子操作。

  1. 在jdk1.8中主要做了2方面的改进

改进一:取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。
改进二:将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。

ConcurrentHashMap在jdk1.8中的改进
ConcurrentHashMap原理分析
JDK8的ConcurrentHashMap源码分析

HashMap,ConcurrentHashMap与LinkedHashMap的区别

  1. ConcurrentHashMap是使用了锁分段技术技术来保证线程安全的,锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问
  2. ConcurrentHashMap 是在每个段(segment)中线程安全的
  3. LinkedHashMap维护一个双链表,可以将里面的数据按写入的顺序读出

Vector和ArrayList的区别

  1. ArrayList在内存不够时默认是扩展50% + 1个,Vector是默认扩展1倍。
  2. Vector提供indexOf(obj, start)接口,ArrayList没有。
  3. Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销。
    Java中Vector和ArrayList的区别

LinkedHashMap

1)Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。

(2)注意,如果在映射中重新插入 键,则插入顺序不受影响。(如果在调用 m.put(k, v) 前 m.containsKey(k) 返回了 true,则调用时会将键 k 重新插入到映射 m 中。)

(3)此实现可以让客户避免未指定的、由 HashMap(及 Hashtable)所提供的通常为杂乱无章的排序工作,同时无需增加与 TreeMap 相关的成本。使用它可以生成一个与原来顺序相同的映射副本,而与原映射的实现无关:

(4) 提供特殊的构造方法来创建链接哈希映射,该哈希映射的迭代顺序就是最后访问其条目的顺序,从近期访问最少到近期访问最多的顺序(访问顺序)。这种映射很适合构建 LRU 缓存。调用 put 或 get 方法将会访问相应的条目(假定调用完成后它还存在)。putAll 方法以指定映射的条目集迭代器提供的键-值映射关系的顺序,为指定映射的每个映射关系生成一个条目访问。任何其他方法均不生成条目访问。特别是,collection 视图上的操作不 影响底层映射的迭代顺序。 removeEldestEntry(Map.Entry)
LinkedHashMap超详细分析

ThreadPoolExecutor

构造方法参数说明

corePoolSize:核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。
maximumPoolSize:线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
keepAliveTime:非核心线程的闲置超时时间,超过这个时间就会被回收。
unit:指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。
workQueue:线程池中的任务队列.
常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。

threadFactory:线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法

原理

  1. 如果当前池大小 poolSize 小于 corePoolSize ,则创建新线程执行任务。
  2. 如果当前池大小 poolSize 大于 corePoolSize ,且等待队列未满,则进入等待队列
  3. 如果当前池大小 poolSize 大于 corePoolSize 且小于 maximumPoolSize ,且等待队列已满,则创建新线程执行任务。
  4. 如果当前池大小 poolSize 大于 corePoolSize 且大于 maximumPoolSize ,且等待队列已满,则调用拒绝策略来处理该任务。
  5. 线程池里的每个线程执行完任务后不会立刻退出,而是会去检查下等待队列里是否还有线程任务需要执行,如果在 keepAliveTime 里等不到新的任务了,那么线程就会退出。

Executor拒绝策略

  1. AbortPolicy:为java线程池默认的阻塞策略,不执行此任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute需要try
    catch,否则程序会直接退出。
  2. DiscardPolicy:直接抛弃,任务不执行,空方法
  3. DiscardOldestPolicy:从队列里面抛弃head的一个任务,并再次execute 此task。
  4. CallerRunsPolicy:在调用execute的线程里面执行此command,会阻塞入口
  5. 用户自定义拒绝策略:实现RejectedExecutionHandler,并自己定义策略模式
    线程池之拒绝策略

CopyOnWriteArrayList

CopyOnWriteArrayList : 写时加锁,当添加一个元素的时候,将原来的容器进行copy,复制出一个新的容器,然后在新的容器里面写,写完之后再将原容器的引用指向新的容器,而读的时候是读旧容器的数据,所以可以进行并发的读,但这是一种弱一致性的策略。
使用场景:CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。

CachedThreadPool vs FixedThreadPool

  1. CachedThreadPool:是通过 java.util.concurrent.Executors 创建的 ThreadPoolExecutor 实例。这个实例会根据需要,在线程可用时,重用之前构造好的池中线程。这个线程池在执行 大量短生命周期的异步任务时(many short-lived asynchronous task),可以显著提高程序性能。调用 execute 时,可以重用之前已构造的可用线程,如果不存在可用线程,那么会重新创建一个新的线程并将其加入到线程池中。如果线程超过 60 秒还未被使用,就会被中止并从缓存中移除。因此,线程池在长时间空闲后不会消耗任何资源。
  2. FixedThreadPool 是通过 java.util.concurrent.Executors 创建的 ThreadPoolExecutor 实例。这个实例会复用 固定数量的线程 处理一个 共享的无边界队列 。任何时间点,最多有 nThreads 个线程会处于活动状态执行任务。如果当所有线程都是活动时,有多的任务被提交过来,那么它会一致在队列中等待直到有线程可用。如果任何线程在执行过程中因为错误而中止,新的线程会替代它的位置来执行后续的任务。所有线程都会一致存于线程池中,直到显式的执行 ExecutorService.shutdown() 关闭。
  3. SingleThreadPool 是通过 java.util.concurrent.Executors 创建的 ThreadPoolExecutor 实例。这个实例只会使用单个工作线程来执行一个无边界的队列。(注意,如果单个线程在执行过程中因为某些错误中止,新的线程会替代它执行后续线程)。它可以保证认为是按顺序执行的,任何时候都不会有多于一个的任务处于活动状态。和 newFixedThreadPool(1) 的区别在于,如果线程遇到错误中止,它是无法使用替代线程的。
    线程池 FixedThreadPool vs CachedThreadPool

死锁

死锁产生的四个必要条件

  1. 互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
  2. 不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
  3. 请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
  4. 循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。

解决死锁

一是死锁预防,就是不让上面的四个条件同时成立。
二是,合理分配资源。
三是使用银行家算法,如果该进程请求的资源操作系统剩余量可以满足,那么就分配。

进程间的通信方式

  1. 管道( pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  2. 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  3. 信号量( semophore ) :
    信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  4. 消息队列( message queue ) :
    消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  5. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  6. 共享内存( shared memory )
    :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC
    方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  7. 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

进程和线程的关系

  1. 进程是表示资源分配的基本单位,又是调度运行的基本单位。线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位
  2. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
  3. 源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
  4. 处理机分给线程,即真正在处理机上运行的是线程。
  5. 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
    进程与线程的区别和联系

进程调度算法

先来先服务,短作业优先,最短剩余时间优先,高响应比优先,优先级,时间片轮转
操作系统的进程调度算法

JAVA 中堆和栈的区别,说下java 的内存机制

a.基本数据类型比变量和对象的引用都是在分配的
b.堆内存用来存放由new创建的对象和数组
c.类变量(static修饰的变量),程序在一加载的时候就在堆中为类变量分配内存,堆中的内存地址存放在栈中
d.实例变量:当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量,是根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的”物理位置”,实例变量的生命周期–当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存
e.局部变量: 由声明在某方法,或某代码段里(比如for循环),执行到它的时候在中开辟内存,当局部变量一但脱离作用域,内存立即释放
JVM在新版本的改进更新以及相关知识
一.JVM在新版本的改进更新以及相关知识
Java 8: 从永久代(PermGen)到元空间(Metaspace)
Minor GC、Major GC和Full GC之间的区别

多态

多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
多态的概念:同一操作作用于不同对象,可以有不同的解释,有不同的执行结果,这就是多态,简单来说就是:父类的引用指向子类对象
三个必要条件:继承、重写、向上转型。
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

理解java的三大特性之多态

wait()和sleep()的区别

sleep来自Thread类,和wait来自Object类
调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁
sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU
sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒

Object有哪些公用方法?

a.方法equals测试的是两个对象是否相等
b.方法clone进行对象拷贝
c.方法getClass返回和当前对象相关的Class对象
d.方法notify,notifyall,wait都是用来对给定对象进行线程同步的

Override和Overload的含义以及区别

a.Overload顾名思义是重新加载,它可以表现类的多态性,可以是函数里面可以有相同的函数名但是参数名、返回值、类型不能相同;或者说可以改变参数、类型、返回值但是函数名字依然不变。
b.就是ride(重写)的意思,在子类继承父类的时候子类中可以定义某方法与其父类有相同的名称和参数,当子类在调用这一函数时自动调用子类的方法,而父类相当于被覆盖(重写)了。

Java的四种引用,强弱软虚,以及用到的场景

  1. 强引用:强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。
  2. .软引用(SoftReference):如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
  3. 弱引用(WeakReference):弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。
  4. 虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
    软引用及对象重获方法实现Java对象的高速缓存
    Java的四种引用,强弱软虚,用到的场景

AQS

  1. AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
  1. 2种同步方式:独占式,共享式。独占式如ReentrantLock,共享式如Semaphore,CountDownLatch,组合式的如ReentrantReadWriteLock
  2. 节点的状态
    CANCELLED,值为1,表示当前的线程被取消;
    SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
    CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
    PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
    值为0,表示当前节点在sync队列中,等待着获取锁。
  3. 模板方法模式
     protected boolean tryAcquire(int arg) : 独占式获取同步状态,试着获取,成功返回true,反之为false
     protected boolean tryRelease(int arg) :独占式释放同步状态,等待中的其他线程此时将有机会获取到同步状态;
     protected int tryAcquireShared(int arg) :共享式获取同步状态,返回值大于等于0,代表获取成功;反之获取失败;
     protected boolean tryReleaseShared(int arg) :共享式释放同步状态,成功为true,失败为false
    AQS维护一个共享资源state,通过内置的FIFO来完成获取资源线程的排队工作。该队列由一个一个的Node结点组成,每个Node结点维护一个prev引用和next引用,分别指向自己的前驱和后继结点。双端双向链表。
    1. 独占式:乐观的并发策略
      acquire
       a.首先tryAcquire获取同步状态,成功则直接返回;否则,进入下一环节;
      b.线程获取同步状态失败,就构造一个结点,加入同步队列中,这个过程要保证线程安全;
       c.加入队列中的结点线程进入自旋状态,若是老二结点(即前驱结点为头结点),才有机会尝试去获取同步状态;否则,当其前驱结点的状态为SIGNAL,线程便可安心休息,进入阻塞状态,直到被中断或者被前驱结点唤醒。
      release
      release的同步状态相对简单,需要找到头结点的后继结点进行唤醒,若后继结点为空或处于CANCEL状态,从后向前遍历找寻一个正常的结点,唤醒其对应线程。
  4. 共享式:
    共享式地获取同步状态.同步状态的方法tryAcquireShared返回值为int。
    a.当返回值大于0时,表示获取同步状态成功,同时还有剩余同步状态可供其他线程获取;
     b.当返回值等于0时,表示获取同步状态成功,但没有可用同步状态了;
     c.当返回值小于0时,表示获取同步状态失败。
  5. AQS实现公平锁和非公平锁
    非公平锁中,那些尝试获取锁且尚未进入等待队列的线程会和等待队列head结点的线程发生竞争。公平锁中,在获取锁时,增加了isFirst(current)判断,当且仅当,等待队列为空或当前线程是等待队列的头结点时,才可尝试获取锁。
     Java并发包基石-AQS详解

Java里的阻塞队列

7个阻塞队列。分别是

ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

添加元素

Java中的阻塞队列接口BlockingQueue继承自Queue接口。BlockingQueue接口提供了3个添加元素方法。
add:添加元素到队列里,添加成功返回true,由于容量满了添加失败会抛出IllegalStateException异常
offer:添加元素到队列里,添加成功返回true,添加失败返回false
put:添加元素到队列里,如果容量满了会阻塞直到容量不满

删除方法

3个删除方法
poll:删除队列头部元素,如果队列为空,返回null。否则返回元素。
remove:基于对象找到对应的元素,并删除。删除成功返回true,否则返回false
take:删除队列头部元素,如果队列为空,一直阻塞到队列有元素并删除

字符流和字节流

  1. Java中的字节流处理的最基本单位为单个字节,它通常用来处理二进制数据。两个字节流类是InputStream和OutputStream,它们分别代表了组基本的输入字节流和输出字节流。
  2. 字符流:Java中的字符流处理的最基本的单元是Unicode码元(大小2字节),它通常用来处理文本数据。
  3. 字符流与字节流的区别
    a,字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。
    b,字节流默认不使用缓冲区;字符流使用缓冲区。
    c,字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。

java nio

RabbitMQ实现迟队列

  1. Time To Live(TTL)
    A: 通过队列属性设置,队列中所有消息都有相同的过期时间。
    B: 对消息进行单独设置,每条消息TTL可以不同。
  2. Dead Letter Exchanges(DLX)
    RabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。
    x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange
    x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送

Dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,发布者和订阅者之间还能通信么?

可以通信的,启动dubbo时,消费者会从zk拉取注册的生产者的地址接口等数据,缓存在本地。每次调用时,按照本地存储的地址进行调用;

注册中心对等集群,任意一台宕机后,将会切换到另一台;注册中心全部宕机后,服务的提供者和消费者仍能通过本地缓存通讯。服务提供者无状态,任一台 宕机后,不影响使用;服务提供者全部宕机,服务消费者会无法使用,并无限次重连等待服务者恢复;

挂掉是不要紧的,但前提是你没有增加新的服务,如果你要调用新的服务,则是不能办到的。

dubbo负载均衡策略

  1. Random LoadBalance
    随机,按权重设置随机概率。在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。(权重可以在dubbo管控台配置)
  2. RoundRobin LoadBalance
    轮循,按公约后的权重设置轮循比率。存在慢的提供者累积请求问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
  3. LeastActive LoadBalance
    最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
  4. ConsistentHash LoadBalance
    一致性Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。缺省只对第一个参数Hash,如果要修改,请配置

dubbo服务集群配置

  1. Failover Cluster(默认): 失败自动切换,当出现失败,重试其它服务器。(缺省)通常用于读操作,但重试会带来更长延迟。可通过retries=”2”来设置重试次数(不含第一次)。
  2. Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
  3. Failsafe Cluster: 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作
  4. Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
  5. Forking Cluster: 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks=”2”来设置最大并行数。

dubbo协议

Dubbo缺省协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。
连接个数:单连接
连接方式:长连接
传输协议:TCP
传输方式:NIO异步传输
序列化:Hessian二进制序列化
适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用dubbo协议传输大文件或超大字符串。
适用场景:常规远程服务方法调用
dubbo面试题
dubbo通信协议之对比

mongo面试题

1.数据库的整体结构
键值对–》文档–》集合–》数据库
mongodb 面试题总结
28个MongoDB经典面试题

Elasticsearch面试题

面试小结之 Elasticsearch 篇

倒排索引

倒排索引是关键词到文档的映射: 关键词——>文档
倒排索引

排序算法

八大经典排序算法

java thread状态

NEW 状态是指线程刚创建, 尚未启动

RUNNABLE 状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等

BLOCKED 这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区
WAITING 这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束

TIMED_WAITING 这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态

TERMINATED 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)
2.wait()和sleep()的区别

sleep来自Thread类,和wait来自Object类
调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁
sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU
sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒

java 线程的几种状态
yeild(),sleep(),wait()区别详解

java 异常体系

Throwable
|-Error
|-Exception
|-RuntimeException

Thorwable类(表示可抛出)是所有异常和错误的超类,两个直接子类为Error和Exception,分别表示错误和异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常, 这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。下面将详细讲述这些异常之间的区别与联系:

1、Error与Exception
Error是程序无法处理的错误,它是由JVM产生和抛出的,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。
2、运行时异常和非运行时异常
运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
3.异常链化:以一个异常对象为参数构造新的异常对象。新的异对象将包含先前异常的信息。这项技术主要是异常类的一个带Throwable参数的函数来实现的。这个当做参数的异常,我们叫他根源异常(cause)

public class Throwable implements Serializable {
    private Throwable cause = this;

    public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }
     public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }

    //........
}

Java 中的异常和处理详解

Java中关键字throw和throws的区别

1、throws出现在方法函数头;而throw出现在函数体。
2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
Java中关键字throw和throws的区别

JVM—内存溢出、OutOfMemoryError、StackOverflowError

JVM—内存溢出、OutOfMemoryError、StackOverflowError

泛型:super,extends

<? super T>表示包括T在内的任何T的父类,
<? extends T>表示包括T在内的任何T的子类
泛型中? super T和? extends T的区别

tcp 三次握手 四次握手

TCP三次握手与四次挥手过程

一致性hash算法

http://afghl.github.io/2016/07/04/consistent-hashing.html

守护线程(Daemon Thread)

在Java中有两类线程:用户线程 (User Thread)、守护线程 (Daemon Thread)。

所谓守护 线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:

(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。

(2) 在Daemon线程中产生的新线程也是Daemon的。

(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

java interrupte

  1. interrupte():方法并不会立即执行中断操作;具体而言,这个方法只会给线程设置一个为true的中断标志(中断标志只是一个布尔类型的变量),而设置之后,则根据线程当前的状态进行不同的后续操作。如果,线程的当前状态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么在将中断标志设置为true后,还会有如下三种情况之一的操作:
    a,如果是wait、sleep以及jion三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个InterruptedException;
    b,如果是java.nio.channels.InterruptibleChannel进行的io操作引起的阻塞,则会对线程抛出一个ClosedByInterruptedException;(待验证)
    c,如果是轮询(java.nio.channels.Selectors)引起的线程阻塞,则立即返回,不会抛出异常。(待验证)
    一个线程在运行状态中,其中断标志被设置为true,则此后,一旦线程调用了wait、jion、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志被清除,重新设置为false。
  2. interrupted和isInterrupted
 public static boolean interrupted() {
    return currentThread().isInterrupted(true);
  }
 public boolean isInterrupted() {
   return isInterrupted(false);
 }
 private native boolean isInterrupted(boolean ClearInterrupted);
  1. Interrupt是指对该线程设置了终止状态, 并没有终止该线程。
  2. Interrupted是指判断当前线程是否终止了, 并且会由于interrupt设置的线程终止状态。
  3. IsInterruped是指判断线程Thread对象是否已经是终止状态,但并不清除状态。

LinkedHashMap 实现LRU

public class LRULinkedHashMap <K,V> extends LinkedHashMap<K,V> {
    //定义缓存的容量
    private int capacity;
    private static final long serialVersionUID = 1L;
    //带参数的构造器
    LRULinkedHashMap(int capacity){
        //调用LinkedHashMap的构造器,传入以下参数
        super(16,0.75f,true);
        //传入指定的缓存最大容量
        this.capacity=capacity;
    }
    //实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
    @Override
    public boolean removeEldestEntry(Map.Entry<K, V> eldest){
        System.out.println(eldest.getKey() + "=" + eldest.getValue());
        return size()>capacity;
    }   
}

condition

对Condition的源码理解,主要就是理解等待队列,等待队列可以类比同步队列,而且等待队列比同步队列要简单,因为等待队列是单向队列,同步队列是双向队列。

java condition使用及分析

condition

Java中Unsafe类详解

  1. 通过Unsafe类可以分配内存,可以释放内存;类中提供的3个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存,与C语言中的3个方法对应。
  2. 可以定位对象某字段的内存位置,也可以修改对象的字段值,即使它是私有的;
  3. 挂起与恢复:将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。
    Java中Unsafe类详解

DelayQueue

队列中每个元素都有个过期时间,并且队列是个优先级队列,当从队列获取元素时候,只有过期元素才会出队列。

并发队列-无界阻塞延迟队列delayqueue原理探究

Fork/Join框架详解

  Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。Fork/Join框架要完成两件事情:

  1.任务分割:首先Fork/Join框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割

  2.执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列里,启动一个线程从队列里取数据,然后合并这些数据。

  在Java的Fork/Join框架中,使用两个类完成上述操作

  1.ForkJoinTask:我们要使用Fork/Join框架,首先需要创建一个ForkJoin任务。该类提供了在任务中执行fork和join的机制。通常情况下我们不需要直接集成ForkJoinTask类,只需要继承它的子类,Fork/Join框架提供了两个子类:

    a.RecursiveAction:用于没有返回结果的任务

    b.RecursiveTask:用于有返回结果的任务

  2.ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行

  任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务(工作窃取算法)。
Fork/Join框架的实现原理
  ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,ForkJoinTask数组负责将存放程序提交给ForkJoinPool,而ForkJoinWorkerThread负责执行这些任务。

原子操作类

在java.util.concurrent.atomic包下,可以分为四种类型的原子更新类:原子更新基本类型、原子更新数组类型、原子更新引用和原子更新属性。

  1. 原子更新基本类型
    使用原子方式更新基本类型,共包括3个类:
    AtomicBoolean:原子更新布尔变量
    AtomicInteger:原子更新整型变量
    AtomicLong:原子更新长整型变量
  2. 原子更新数组
    通过原子更新数组里的某个元素,共有3个类:
    AtomicIntegerArray:原子更新整型数组的某个元素
    AtomicLongArray:原子更新长整型数组的某个元素
    AtomicReferenceArray:原子更新引用类型数组的某个元素
    AtomicIntegerArray常用的方法有:
    int addAndSet(int i, int delta):以原子方式将输入值与数组中索引为i的元素相加
    boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式更新数组中索引为i的值为update值
  3. 原子更新引用类型
    AtomicReference:原子更新引用类型
    AtomicReferenceFieldUpdater:原子更新引用类型里的字段
    AtomicMarkableReference:原子更新带有标记位的引用类型。
  4. 原子更新字段类
    如果需要原子更新某个类的某个字段,就需要用到原子更新字段类,可以使用以下几个类:
    AtomicIntegerFieldUpdater:原子更新整型字段
    AtomicLongFieldUpdater:原子更新长整型字段
    AtomicStampedReference:原子更新带有版本号的引用类型。
    要想原子更新字段,需要两个步骤:
    每次必须使用newUpdater创建一个更新器,并且需要设置想要更新的类的字段
    更新类的字段(属性)必须为public volatile

原子操作类

同步屏障CyclicBarrier

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier和CountDownLatch的区别

CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。比如以下代码执行完之后会返回true。

Semaphore

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源
控制并发线程数的Semaphore
同步屏障CyclicBarrier

参照

参照

猜你喜欢

转载自blog.csdn.net/u012998254/article/details/79246335