旁白:通常的面试都是从最简单基本的问题开始。html
面试官:请在黑板上写出一个线程安全的单例模式的例子。java
面试者:面试
其实线程安全的实现有不少种,根据业务场景能够new一个实例做为私有静态成员变量,这样程序一启动,实例就生成,私有化构造函数,利用公用的静态函数getInstance返回实例。这种预加载的是能保证线程安全的可是若是不是肯定会被使用,会形成内存的浪费,因此能够将实例放到私有静态类中做为成员变量。下面只写一种利用锁机制来保证的懒加载方法。算法
public
class
Singleton {
private
volatile
static
Singleton singleton;
private
Singleton (){}
public
static
Singleton getSingleton() {
if
(singleton ==
null
) {
synchronized
(Singleton.
class
) {
if
(singleton ==
null
) {
singleton =
new
Singleton();
}
}
}
return
singleton;
}
}
|
旁白:从这个例子上我能想到的知识点主要有三个编程
☆ volatile 关键字,可深刻到Java VM内存相关缓存
☆ synchronized 关键字,可深刻到Java锁机制,高并发相关安全
☆ new 关键字,可深刻到Java VM类加载机制相关数据结构
可是面试官一开始可能要先考察一下面试者是否真的理解本身写的代码多线程
面试官:你写的这个程序是怎么保证线程安全的?并发
面试者:将类的构造方法私有起来,外部调用进行初始化的时候只能经过调用getSingleton这个静态方法来得到实例,静态方法是整个Java虚拟机中只有一个实例。在建立的时候首先进行非空判断,这时候若是实例不存在,对整个类进行加锁同步,为了不过程当中非空状态的改变,同步块内再进行一次判断,若是不存在实例则建立实例返回。使用volatile关键字,下次访问这个方法就能直接看到实例的最新非空状态,直接返回实例。
面试官:volatile 起到了什么做用?
面试者:volatile这个英文单词意思是易变的,用在多线程中来同步变量。Java的对象都是在内存堆中分配空间。可是Java有主内存和线程本身独有的内存拷贝。对于没有volatile修饰的局部变量,线程在运行过程当中访问的是工做内存中的变量值,其修改对于主内存不是当即可见。而volatile修饰的值在线程独有的工做内存中无副本,线程直接和主内存交互,修改对主内存当即可见。
面试官:synchronized起到了什么做用?
面试者:锁定对象,限制当前对象只能被一个线程访问。
面试官:synchronized里你传Singleton.class这个参数,起到什么做用,换成别的行不行?
面试者:对当前类加锁,使得这个代码块一次只能被一个线程访问。这里Singleton.class能够换成一个常量字符串或者本身定义一个内部静态Object。
面试官:那传Singleton.class,常量字符串,本身定义一个内部静态Object有区别吗?
面试者:由于这是一个静态方法,至关于一个概念上的类锁,因此在这里起到的效果是同样的。可是若是是原型模式,或者直接每一个类都是new出来的,实例不一样的话,在一个非静态方法里加这三种锁,这时是一个对象锁,由于Singleton.class或者是静态的一个Object或者是JVM只存一份的字符串常量,这些对象线程间是共享的,会对全部的实例的同步块都加同一把锁,每一个实例访问到此对象的同步代码块都会被阻塞。可是若是这时synchronized的参数是this,或者是内部new出来的一个内部非静态Object,则各个实例拥有不一样的锁,访问同一个代码相同同步块也是互不干扰。只有实例内部使用了同一个对象锁才会同步等待。
面试官:那你知道synchronized关键字实现同步的原理吗?
面试者:synchronized在Java虚拟机中使用监视器锁来实现。每一个对象都有一个监视器锁,当监视器锁被占用时就会处于锁定状态。
线程执行一条叫monitorenter的指令来获取监视器锁的全部权。若是此监视器锁的进入数为0,则线程进入并将进入数设置为1,成为线程全部者。若是线程已经拥有该锁,由于是可重入锁,能够从新进入,则进入数加1.若是线程的监视器锁被其余线程占用,则阻塞直到此监视器锁的进入数为0时才能进入该锁。
线程执行一条叫monitorexit的指令来释放全部权。执行monitorexit的必须是线程的全部者。每次执行此指令,线程进入数减1,直到进入数为0。监视器锁被释放。
面试官:你刚才提到的可重入锁是什么概念,有不可重入锁吗?
面试者:我说的可重入锁是广义的可重入锁,固然jdk1.5引入了concurrent包,里面有Lock接口,它有一个实现叫ReentrantLock。广义的可重入锁也叫递归锁,是指同一线程外层函数得到锁以后,内层还能够再次得到此锁。可重入锁的设计是为了不死锁。sun的corba里的mutex互斥锁是一种不可重入锁的实现。自旋锁也是一种不可重入锁,本质上是一种忙等锁,CPU一直循环执行"测试并设置"直到可用并取得该锁,在递归的调用该锁时必然会引发死锁。另外,若是锁占用时间较长,自旋锁会过多的占用CPU资源,这时使用基于睡眠原理来实现的锁更加合适。
面试官:你刚才提到了concurrent包,它里面有哪些锁的实现?
面试者:经常使用的有ReentrantLock,它是一种独占锁。ReadWriteLock接口也是一个锁接口,和Lock接口是一种关联关系,它返回一个只读的Lock和只写的Lock。读写分离,在没有写锁的状况下,读锁是无阻塞的,提升了执行效率,它是一种共享锁。ReadWriteLock的实现类为ReentrantReadWriteLock。ReentrantLock和ReentrantReadWriteLock实现都依赖于AbstractQueuedSynchronizer这种抽象队列同步器。
面试官:锁还有其余维度的分类吗?
面试者:还能够分为公平锁和非公平锁。非公平锁是若是一个线程尝试获取锁时能够获取锁,就直接成功获取。公平锁则在锁被释放后将锁分配给等待队列队首的线程。
面试官:AQS是什么?
面试者:AQS是一个简单的框架,这个框架为同步状态的原子性管理,线程的阻塞和非阻塞以及排队提供了一种通用机制。表现为一个同步器,主要支持获取锁和释放锁。获取锁的时候若是是独占锁就有可能阻塞,若是是共享锁就有可能失败。若是是阻塞,线程就要进入阻塞队列,当状态变成可得到锁就修改状态,已进入阻塞队列的要从阻塞队列中移除。释放锁时修改状态位及唤醒其余被阻塞的线程。
AQS本质是采用CHL模型完成了一个先进先出的队列。对于入队,采用CAS操做,每次比较尾节点是否一致,而后插入到尾节点中。对于出队列,由于每一个节点缓存了一个状态位,不知足条件时自旋等待,直到知足条件时将头节点设置为下一个节点。
面试官:那知道这个队列的数据结构吗?
面试者:这个队列是用一个双向链表实现的。
面试官:你刚才提到AQS是一种通用机制,那它还有哪些应用?
面试者:AQS除了刚才提到的可重入锁ReentrantLock和ReentrantReadWriteLock以外,还用于不可重入锁Mutex的实现。java并发包中的同步器如:Semphore,CountDownLatch,FutureTask,CyclicBarrier都是采用这个机制实现的。
旁白:既然问到了并发工具包中的东西,每一个均可以引出一堆,可是基本原理已经问出来了,其余的问下去没什么意思。转向下一个问题。
面试官:你黑板上写的实例是经过new对象建立出来的,还可不能够采用别的方法来建立对象呢?
面试者:还可使用class类的newInstance方法,Constructor构造器类的newInstance方法,克隆方法和反序列法方法。
面试官:两种newInstance方法有没有区别?
面试者:
☆ Class类位于java的lang包中,而构造器类是java反射机制的一部分。
☆ Class类的newInstance只能触发无参数的构造方法建立对象,而构造器类的newInstance能触发有参数或者任意参数的构造方法来建立对象。
☆ Class类的newInstance须要其构造方法是共有的或者对调用方法可见的,而构造器类的newInstance能够在特定环境下调用私有构造方法来建立对象。
☆ Class类的newInstance抛出类构造函数的异常,而构造器类的newInstance包装了一个InvocationTargetException异常。
Class类本质上调用了反射包构造器类中无参数的newInstance方法,捕获了InvocationTargetException,将构造器自己的异常抛出。
面试官:类加载的时候,本身定义了一个类和java本身的类类名和命名空间都同样,JVM加载的是哪个呢?
面试者:调用的是java自身的,根据双亲委派模型,最委派Bootstrap的ClassLoader来加载,找不到才去使用Extension的ClassLoader,还找不到才去用Application的ClassLoader,这种机制利于保证JVM的安全。
面试官:你刚才提到的java的反射机制是什么概念?
面试者:java的反射机制是在运行状态中,对于任何一个类,都可以知道它全部的属性和方法;对于任何一个对象,都可以调用它的任何一个方法和属性。这种动态的获取信息和动态调用对象的方法的功能就是java的反射机制。它是jdk动态代理的实现方法。
面试官:java还有没有其余的动态代理实现?
面试者:还有cglib动态代理。
面试官:这两种动态代理哪一个比较好呢?
面试者:AOP源码中同时使用了这两种动态代理,由于他们各有优劣。jdk动态代理是利用java内部的反射机制来实现,在生成类的过程当中比较高效,cglib动态代理则是借助asm来实现,能够利用asm将生成的类进行缓存,因此在类生成以后的相关执行过程当中比较高效。可是jdk的动态代理前提是目标类必须基于统一的接口,因此有必定的局限性。
旁白:面试者都已经提到AOP了,那么接下来横向,纵向,怎样都能问出一大堆问题,就不赘述。基于上面问题,读者也能够本身画出一棵知识树,而后就能找到能对答如流的终极方案:就是基本都没超过《深刻理解java虚拟器》《java并发编程实践》这两本书,大学学过的《数据结构与算法》《编译原理》掌握的好也能够在面试中加分哦。
原文地址
https://www.cnblogs.com/xiexj/p/6845029.html?from=singlemessage