1、bean的生命周期
2、redis 的用法
3、ngnix的负载均衡的算法有哪些?各有什么差异?
4、什么时候用乐观锁,什么时候用悲观锁?
5、mysql 或 oracle中,要想查到数据库中正在执行的SQL语句,有哪些思路或者方法?
6、什么是单点登录,介绍一下单点登录系统中的登录和注销流程是什么?
7、Linux环境下查看一个java进程的端口号有哪些方法?
8、请解释一下 volatile 关键字?
volatile 是java虚拟机提供的最轻量级的同步机制;被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象。
volatile关键字的作用:保证了变量的可见性(visibility)。被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。
为什么会出现脏读?
Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都是必须在工作内存中进行的,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。变量的值何时从线程的工作内存写回主存,无法确定。
volatile实现原理
在生成汇编代码时会在volatile修饰的共享变量 进行写操作的时候会多出Lock前缀的指令;主要有两个方面的影响:
1、将当前处理器缓存行的数据写会系统内存;
2、这个写会内存的操作会使得其他CPU里缓存了该内存地址的数据无效。
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会相处理器发送一条Lock前缀的指令,将这个变量所在的缓存行的数据写会到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,在执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。因此,经过分析我们可以得出如下结论:
1、Lock前缀的指令会引起处理器缓存写会内存;
2、一个处理器的缓存会写到内存会导致其他处理器的缓存失效;
3、当处理器发现本地缓存失效后,就会从内存中重读该变量数,即可以获取当前 最新值。
这样针对volatile变量通过这样的机制就使得每个线程都能获取到该变量的最新值。
注意:volatile 只能保证变量的可见性,不能保证原子性。
9、请说明一下 HashMap JDK1.7 和 JDK1.8 的实现数据结构有什么区别?HashMap什么时候会扩容,如何扩容?
jdk8 相较于jdk7在底层实现方面的不同
1、jdk1.7在new HashMap 时创建一个长度为16的数组
2、jdk1.8在首次调用put()方法时,底层创建长度为16的数组
3、jdk1.8 底层的数组是:Node[]而不是Entry[]
4、jdk1.7底层结构:数组+链表;jdk1.8底层结构:数组+链表+红黑树。
- 4.1、形成链表时,七上八下(jdk1.7:新的元素指向旧的元素;jdk1.8:旧的元素指向新的元素)
- 4.2、当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64 时,此时此索引位置上的所有数据改为使用红黑树存储。
- DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
- DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
- threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
- TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
- MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
10、请实现一种单例模式(懒汉和饿汉模式,其他)?
1.懒汉模式
public class LanHan(){
private static LanHan instance;// 懒汉式
private LanHan(){} // 私有构造方法
public static LanHangetInstance(){
if(instance == null){
instance = new LanHan();
}
return instance;
}
}
懒汉式,顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是synchronized关键字。
懒汉的双重锁机制
public class Singleton {
/*
* 利用静态变量来记录Singleton的唯一实例
* volatile 关键字确保:当uniqueInstance变量被初始化成Singleton实例时,
* 多个线程正确地处理uniqueInstance变量
*/
private volatile static Singleton uniqueInstance;
/**
* 构造器初始化,只有Singleton类内才可以调用构造器
*/
private Singleton(){}
public static Singleton getInstance(){
if(uniqueInstance == null){
synchronized (Singleton.class){
if (uniqueInstance == null){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
2.饿汉模式
public class EHan(){
private static EHan instance = new EHan; // 饿汉模式
private EHan(){} // 私有构造方法
public static EHan getInstance(){
return instance;
}
}
饿汉式,从名字上也很好理解,就是“比较勤”,实例在初始化的时候就已经建好了,不管你有没有用到,都先建好了再说。好处是没有线程安全的问题,坏处是浪费内存空间。
3.双检锁
public class DoubleCheck{
private static DoubleChece instance;
private DoubleChece(){}
public static DoubleCheck getInstance(){
if(instance == null){
sychronized (DoubleCheck.class){
if(instance == null){
instance = new DoubleCheck();
}
}
}
return instance;
}
}
双检锁,又叫双重校验锁,综合了懒汉式和饿汉式两者的优缺点整合而成。看上面代码实现中,特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
4.静态内部类
public class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton gerInstance(){
return SingletonHolder.INSTANCE ;
}
}
静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
5.枚举
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
枚举的方式是比较少见的一种实现方式,但是看上面的代码实现,却更简洁清晰。并且她还自动支持序列化机制,绝对防止多次实例化。
完整的枚举单例
public class User {
//私有化构造函数
private User(){ }
//定义一个静态枚举类
static enum SingletonEnum{
//创建一个枚举对象,该对象天生为单例
INSTANCE;
private User user;
//私有化枚举的构造函数
private SingletonEnum(){
user=new User();
}
public User getInstnce(){
return user;
}
}
//对外暴露一个获取User对象的静态方法
public static User getInstance(){
return SingletonEnum.INSTANCE.getInstnce();
}
}
public class Test {
public static void main(String [] args){
System.out.println(User.getInstance());
System.out.println(User.getInstance());
System.out.println(User.getInstance()==User.getInstance());
}
}
结果为true
为什么使用单例
1.私有化构造器并不保险
《effective java》中只简单的提了几句话:“享有特权的客户端可以借助
AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
2.序列化问题
任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实
例,这个新建的实例不同于该类初始化时创建的实例。”当然,这个问题也是可以
解决的,想详细了解的同学可以翻看《effective java》第77条:对于实例控制,枚
举类型优于readResolve
一般情况下,懒汉式(包含线程安全和线程不安全梁总方式)都比较少用;饿汉式和双检锁都可以使用,可根据具体情况自主选择;在要明确实现 lazy loading 效果时,可以考虑静态内部类的实现方式;若涉及到反序列化创建对象时,大家也可以尝试使用枚举方式。