java整理的相关笔记,对面试很有用的

1,什么是线程安全 
答:如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,
就是线程安全的。

2,都说String是不可变的,为什么我可以这样做呢
   String a = "1";
   a = "2";
答:首先String类是用Fianl关键字修饰的,这说明String是不可以被继承的。但是String类的主力成员变量value是char[]数组,而且是final修饰的,其创建以后是不能被修改的,这只是说明这个引用地址是不可变的,但是value数组里面的元素还是可变的。
上面声明的变量“1”和“2”分别是两个不同的String对象,而a变量只不过是重新把地址指向了“2”对象所在的位置。

3,HashMap的实现原理
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。
当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部。

4,写出三种单例模式,如果能考虑线程安全最好
a、静态内部类方式
public class SingletonDemo4 {
private static class SingletonHolder {
private static final SingletonDemo4 instance = new SingletonDemo4();
}
private SingletonDemo4() {}
public static final SingletonDemo4 getInsatance() {
return SingletonHolder.instance;
}
}
b、双重检测形
public class SingletonDemo5 {
//volatile 关键字修饰,确保线程安全
private volatile static SingletonDemo5 singletonDemo5;
private SingletonDemo5() { }


public static SingletonDemo5 getSingletonDemo5() {
if (singletonDemo5 == null) {
synchronized (SingletonDemo5.class) {
if (singletonDemo5 == null) {
singletonDemo5 = new SingletonDemo5();
}
}
}
return singletonDemo5;
}
}
c、饿汉模式
public class SingletonDemo2 {
private static SingletonDemo2 instance = new SingletonDemo2();
private SingletonDemo2() { }
/**
* 基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,这时候初始化instance显然没有达到lazy loading的效果。
* @return
* @return SingletonDemo2
*/
public static SingletonDemo2 getInstance1(){
return instance;
}
}

5,ArrayList和LinkedList有什么区别
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

6,实现线程的2种方式
a、继承Thread类,b、实现Runnable接口方式实现多线程

7,JVM的内存结构
JVM内存结构主要有三大块:堆内存、方法区和栈。
a、堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;
b、方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);
c、栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。
d、程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。

8,Lock与Synchronized的区别
a. lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;
b. synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。
  (所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。) 
c. lock等待锁过程中可以用interrupt来终端等待,而synchronized只能等待锁的释放,不能响应中断; 
d. lock可以通过trylock来知道有没有获取锁,而synchronized不能; 
e. Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
详细解释以上相关内容:
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
  1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
  2)线程执行发生异常,此时JVM会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,很影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
首先要说明的就是Lock,通过查看Lock的源码可知,Lock是一个接口,代码如下:
public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
接下来详细说明每个方法的使用:
(1)lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,
使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
(2)tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,
也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
(3)tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。
如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
(4)lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。
也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,
那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。由于lockInterruptibly()的声明中抛出了异常,
所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。
(通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。
而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。)
Lock也要很多的实现类,比如ReentrantLock(可重入锁)、WriteLock等。
 java整理的相关笔记,对面试很有用的
9,数据库隔离级别有哪些,各自的含义是什么,MYSQL默认的隔离级别是是什么。
a、数据库事务的隔离级别有4个,由低到高依次为:
Read uncommitted(未授权读取、读未提交):
如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
避免了更新丢失,却可能出现脏读。也就是说事务B读取到了事务A未提交的数据。
Read committed(授权读取、读提交):
读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
该隔离级别避免了脏读,但是却可能出现不可重复读。事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
Repeatable read(可重复读取)、
读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
避免了不可重复读取和脏读,但是有时可能出现幻读。这可以通过“共享读锁”和“排他写锁”实现。
Serializable(序列化),这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
c、大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle。
Mysql的默认隔离级别就是Repeatable read。
10,请解释如下jvm参数的含义:
-server -Xms512m -Xmx512m -Xss1024K 
-XX:PermSize=256m 
-XX:MaxPermSize=512m 
-XX:MaxTenuringThreshold=20 
-XX:CMSInitiatingOccupancyFraction=80 
-XX:+UseCMSInitiatingOccupancyOnly。 
答:-Xms 初始堆大小 物理内存的1/64(<1GB) 默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.
-Xmx 最大堆大小 物理内存的1/4(<1GB)     默认空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制
-Xss 每个线程的堆栈大小
-XX:PermSize 设置持久代(perm gen)初始值 物理内存的1/64  一般内存溢出时候需要调整的值
-XX:MaxPermSize 设置持久代最大值 物理内存的1/4