小厂实习面试记录-003

1.单例模式创建方式

1.饿汉式(静态常量)【可用】

1
2
3
4
5
6
7
8
9
10
public class Singleton{
private final static Singleton INSTANCE=new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

2.饿汉式(静态代码块)【可用】

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton{
private static Singleton instance;
static{
instance=new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,
也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

3.懒汉式(线程不安全)【不可用】

1
2
3
4
5
6
7
8
9
10
11
12
13
public static Singleton{
private static Singletons singleton;
private Singleton(){
public static Singleton getInstance(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
这种写法起到了LazyLoading的效果,但是只能在单线程下使用。如果在多线程下,
一个线程进入了if (singleton ==null)判断语句块,还未来得及往下执行,
另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

4.懒汉式(线程安全,同步方法)【不推荐用】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton{
private static Singleton singleton;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。
而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。
方法进行同步效率太低要改进。

5.懒汉式(线程安全,同步代码块)【不可用】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton{
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class){
singleton=new Singleton();
}
}
return singleton;
}
}
由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。
但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程
进入了if (singleton ==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,
这时便会产生多个实例。

6.双重检查【推荐使用】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton{
private static volatile Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton ==null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if(singleton == null),直接return实例化对象。
优点:线程安全;延迟加载;效率较高。

7.静态内部类【推荐使用】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton{
private Singleton(){}
private static class SingletonInstance{
private static final Singleton INSTANCE=new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

优点:避免了线程不安全,延迟加载,效率高。

8.枚举【推荐使用】

1
2
3
4
5
6
7
public enum Singleton{
INSTANCE;
private static Singleton getInstance(){
return INSTANCE;
}
}
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

2.集合概述

Java集合类主要由两个接口CollectionMap派生出来的,Collection有三个子接口:List、Set、Queue。

Java集合框架图如下:

List代表了有序可重复集合,可直接根据元素的索引来访问;

Set代表无序不可重复集合,只能根据元素本身来访问;

Queue是队列集合;

Map代表的是存储key-value对的集合,可根据元素的key来访问value。

集合体系中常用的实现类有ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等实现类。

3.线程创建方式

  • 通过扩展Thread类来创建多线程
  • 通过实现Runnable接口来创建多线程
  • 实现Callable接口,通过FutureTask接口创建线程。
  • 使用Executor框架来创建线程池。

4.异常概述

常见的Exception有哪些?


常见的RuntimeException:

  1. ClassCastException //类型转换异常
  2. IndexOutOfBoundsException //数组越界异常
  3. NullPointerException //空指针
  4. ArrayStoreException //数组存储异常
  5. NumberFormatException //数字格式化异常
  6. ArithmeticException //数学运算异常

unchecked Exception:

  1. NoSuchFieldException //反射异常,没有对应的字段
  2. ClassNotFoundException //类没有找到异常
  3. IllegalAccessException //安全权限异常,可能是反射时调用了private方法

Error和Exception的区别?


Error:JVM 无法解决的严重问题,如栈溢出StackOverflowError、内存溢出OOM等。程序无法处理的错误。

Exception:其它因编程错误或偶然的外在因素导致的一般性问题。可以在代码中进行处理。如:空指针异常、数组下标越界等。

运行时异常和非运行时异常(checked)的区别?


unchecked exception包括RuntimeExceptionError类,其他所有异常称为检查(checked)异常。

  1. RuntimeException由程序错误导致,应该修正程序避免这类异常发生。
  2. checked Exception由具体的环境(读取的文件不存在或文件为空或sql异常)导致的异常。必须进行处理,不然编译不通过,可以catch或者throws。

5.SpringMVC的RequestMapping参数

  • value 代表实际的url

1
2
3
4
5
6
7
@Controller
public class HelloController{
@RequestMapping (value = "/hello")
public void hello(HttpServletRequest request, HttpServletResponse response) throws IOException {

}
}
  • param 指定某一种参数名类型,当请求数据中包含该名称的请求参数时,才能进行相应,否则拒绝此次请求。

1
@RequestMapping(value = "/findUser", param = "username")
  • method 请求方法,支持GET,POST,PUT,DELETE

1
2
3
4
@RequestMapping (value = "/hello", method = RequestMethod.GET)
public void hello (HttpServletRequest request, HttpServletResponse response) throws IOException {

}
  • consumes 接受的Content-Type类型

1
2
3
4
@RequestMapping (value = "/hello", consumes = "application/json")
public void hello (HttpServletRequest request, HttpServletResponse response) throws IOException {

}
  • produces 返回的Content-Type

1
2
3
4
@RequestMapping (value = "/hello", produces = "application/json")
public void hello (HttpServletRequest request, HttpServletResponse response) throws IOException {

}
  • headers 用于指定某一种请求头类型。

1
2
3
4
@RequestMapping (value = "/hello", headers = "Content-Type:text/html;charset=UTF-8")
public void hello (HttpServletRequest request, HttpServletResponse response) throws IOException {

}

6.Spring事务和隔离级别

Spring 事务实现方式有哪些?


事务就是一系列的操作原子执行。Spring事务机制主要包括声明式事务和编程式事务。

  • 编程式事务:通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护。
  • 声明式事务:将事务管理代码从业务方法中分离出来,通过aop进行封装。Spring声明式事务使得我们无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。使用 @Transactional 注解开启声明式事务。

@Transactional相关属性如下:

属性 类型 描述
value String 可选的限定描述符,指定使用的事务管理器
propagation enum: Propagation 可选的事务传播行为设置
isolation enum: Isolation 可选的事务隔离级别设置
readOnly boolean 读写或只读事务,默认读写
timeout int (in seconds granularity) 事务超时时间设置
rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

有哪些事务传播行为?


在TransactionDefinition接口中定义了七个事务传播行为:

  1. PROPAGATION_REQUIRED如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。如果嵌套调用的两个方法都加了事务注解,并且运行在相同线程中,则这两个方法使用相同的事务中。如果运行在不同线程中,则会开启新的事务。
  2. PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
  3. PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果不存在事务,则抛出异常IllegalTransactionStateException
  4. PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。需要使用JtaTransactionManager作为事务管理器。
  5. PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。需要使用JtaTransactionManager作为事务管理器。
  6. PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常。
  7. PROPAGATION_NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则按PROPAGATION_REQUIRED 属性执行。

PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:

使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务是两个独立的事务。一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。

使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。

事务隔离级别


名称 结果 脏读 不可重复读 幻读
Read UnCommitted(读未提交) 什么都不解决
Read Committed(读已提交) 解决了脏读的问题
Repeatable Read(可重复读) (mysql的默认级别)解决了不可重复读
Serializable(序列化) 解决所有问题 - - -
  • READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读和幻读问题。

  • READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更数据,可避免脏读,仍会出现不可重复读和幻读问题。

  • REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。

  • SERIALIZABLE(序列化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。

    Spring隔离级别


1
2
3
4
5
int ISOLATION_DEFAULT = -1;  // 默认采用数据库的隔离级
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;//读未提交
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; //读已提交
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; //可重复度
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; //序列化

7.强引用、软引用、弱引用、虚引用是什么,有什么区别?

强引用:在程序中普遍存在的引用赋值,类似Object obj = new Object()这种引用关系。只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

软引用:如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。

1
2
//软引用
SoftReference<String> softRef = new SoftReference<String>(str);

弱引用:在进行垃圾回收时,不管当前内存空间足够与否,都会回收只具有弱引用的对象。

1
2
//弱引用
WeakReference<String> weakRef = new WeakReference<String>(str);

虚引用:虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要是为了能在对象被收集器回收时收到一个系统通知

总结

  • 突然打来的电话面试,都不知道什么公司,虽然模模糊糊,也努力回答了,不知结果!
  • 但是学习还是得继续!
  • 加油!

小厂实习面试记录-003
https://zty-f.github.io/2022/05/12/小厂面试记录-003/
作者
ZTY
发布于
2022年5月12日
更新于
2025年1月2日
许可协议