12个模块150道 java必考面试题

7 阅读39分钟

一、java基础

1.JDK 和 JRE 有什么区别?

分享一份大彬精心整理的大厂面试手册,包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等高频面试题,非常实用,有小伙伴靠着这份手册拿过字节offer~【领取/点击】

2.== 和 equals 的区别是什么?

“==”比较的是两个引用在内存中的地址是否相同,也就是说在内存中的地址是否一样。

equals方法是由Object类提供的,可以由子类来进行重写。默认的实现只有当对象和自身进行比较时才会返回true, 这个时候和 “==”是等价的。

Java中很多类(String类 Date类 File类)等都对equals方法进行了重写,这里拿常见的String类举例。

 1  public class Test {
 2     public static void main(String[] args) {
 3         String str1 = "abc";
 4         String str2 = "abc";
 5         System.out.println(str1==str2);//true
 6 
 7 
 8         String str3 = new String("abc");
 9         String str4 = new String ("abc");
10         System.out.println(str3==str4);//false
11         System.out.println(str3.equals(str4));//true
12     }
13 }

先看第3行代码,先在栈中创建一个对 String类的对象引用变量str1,然后通过引用去字符串常量池 里找有没有"abc",如果没有,则将"abc"存放进字符串常量池。这里常量池中并没有“abc”。即在编译期已经创建好(直接用双引号定义的)“abc”,存储在了常量池中。

第4行代码又创建了对String类的对象引用str2,然后通过引用去字符串常量池 里找有没有"abc",如果没有,则将"abc"存放进字符串常量池 ,并令str2指向”abc”,如果已经有”abc” 则直接令str2指向“abc”。这里我们在第三行代码中已经将“abc”这个字符串存储进了常量池。所以str2和str1指向的是同一个“abc”,返回true。

第8行和第9行代码分别创建了2个对象,str3和str4指向的是不同的对象,即上面所说的内存空间中存储位置不同。故str3 == str4 返回的肯定是false。

第11行代码 str3.equals(str4) 返回true

因为String类重写了equals方法,比较的是内存空间存放的数据是否相同。这里存放的都是字符串“abc” 故返回true。

3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

答案肯定是不一定。同时反过来equals为true,hashCode也不一定相同。类的hashCode方法和equals方法都可以重写,返回的值完全在于自己定义。hashCode()返回该对象的哈希码值;equals()返回两个对象是否相等。

4.final在 java 中有什么作用?

根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。

final类不能被继承,没有子类,final类中的方法默认是final的。

final方法不能被子类的方法覆盖,但可以被继承。

final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

final不能用于修饰构造方法

注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。

  1. java 中的 Math.round(-1.5) 等于多少?

-1Math.round(1.5)的返回值是2,Math.round(-1.5)的返回值是-1。四舍五入的原理是在参数上加0.5然后做向下取整。

6.String 属于基础的数据类型吗?

String不是基本的数据类型,是final修饰的java类,java中的基本类型一共有8个,它们分别为:

1 字符类型:byte,char

2 基本整型:short,int,long

3 浮点型:float,double

4 布尔类型:boolean

  1. java 中操作字符串都有哪些类?它们之间有什么区别?

String : final修饰,String类的方法都是返回new String。即对String对象的任何改变都不影响到原对象,对字符串的修改操作都会生成新的对象。

StringBuffer : 对字符串的操作的方法都加了synchronized,保证线程安全。

StringBuilder : 不保证线程安全,在方法体内需要进行字符串的修改操作,可以new StringBuilder对象,调用StringBuilder对象的append、replace、delete等方法修改字符串。

8.String str="i"与 String str=new String(“i”)一样吗?

内存地址不一样

9.如何将字符串反转?

public class StringReverse {
 
    /**
     * 二分递归地将后面的字符和前面的字符连接起来。
     *
     * @param s
     * @return
     */
    public static String reverse1(String s) {
        int length = s.length();
        if (length <= 1)
            return s;
        String left = s.substring(0, length / 2);
        String right = s.substring(length / 2, length);
        return reverse1(right) + reverse1(left);
    }
     
    /**
     * 取得当前字符并和之前的字符append起来
     * @param s
     * @return
     */
    public static String reverse2(String s) {
        int length = s.length();
        String reverse = "";
        for (int i=0; i<length; i++)
            reverse = s.charAt(i) + reverse;
        return reverse;
    }
     
    /**
     * 将字符从后往前的append起来
     * @param s
     * @return
     */
    public static String reverse3(String s) {
        char[] array = s.toCharArray();
        String reverse = "";
        for (int i = array.length - 1; i >= 0; i--) {
            reverse += array[i];
        }
        return reverse;
    }
     
    /**
     * 和StringBuffer()一样,都用了Java自实现的方法,使用位移来实现
     * @param s
     * @return
     */
    public static String reverse4(String s) {
        return new StringBuilder(s).reverse().toString();
    }
     
    /**
     * 和StringBuilder()一样,都用了Java自实现的方法,使用位移来实现
     * @param s
     * @return
     */
    public static String reverse5(String s) {
        return new StringBuffer(s).reverse().toString();
    }
     
    /**
     * 二分交换,将后面的字符和前面对应的那个字符交换
     * @param s
     * @return
     */
    public static String reverse6(String s) {
        char[] array = s.toCharArray();
        int end = s.length() - 1;
        int halfLength = end / 2;
        for (int i = 0; i <= halfLength; i++) {
            char temp = array[i];
            array[i] = array[end-i];
            array[end-i] = temp;
        }
         
        return new String(array);
    }
     
    /**
     * 原理是使用异或交换字符串
     * a=a^b;
     * b=b^a;
         * a=b^a;
     *
     * @param s
     * @return
     */
    public static String reverse7(String s) {
        char[] array = s.toCharArray();
           
          int begin = 0;
          int end = s.length() - 1;
           
          while (begin < end) {
               array[begin] = (char) (array[begin] ^ array[end]);
               array[end] = (char) (array[end] ^ array[begin]);
               array[begin] = (char) (array[end] ^ array[begin]);
               begin++;
               end--;
          }
           
          return new String(array);
    }
     
    /**
     * 基于栈先进后出的原理
     *
     * @param s
     * @return
     */
    public static String reverse8(String s) {
        char[] array = s.toCharArray();
        Stack<Character> stack = new Stack<Character>();
        for (int i = 0; i < array.length; i++)
            stack.push(array[i]);
 
        String reverse = "";
        for (int i = 0; i < array.length; i++)
            reverse += stack.pop();
           
        return reverse;
    }
     
    public static void main(String[] args) {
        System.out.println(reverse1("Wang Sheng"));
        System.out.println(reverse2("Wang Sheng"));
        System.out.println(reverse3("Wang Sheng"));
        System.out.println(reverse4("Wang Sheng"));
        System.out.println(reverse5("Wang Sheng"));
        System.out.println(reverse6("Wang Sheng"));
        System.out.println(reverse7("Wang Sheng"));
        System.out.println(reverse8("Wang Sheng"));
    }
}

10.String 类的常用方法都有那些?

增-删改查

删:trim, subString

改:valueOf ,toUpperCase, toLowerCase

查:length, indexOf ,charAt ,startsWith, endsWith

11.抽象类必须要有抽象方法吗?

抽象类中不一定包含抽象方法,但是包含抽象方法的类一定要被声明为抽象类。

抽象类本身不具备实际的功能,只能用于派生其子类。

抽象类中可以包含构造方法, 但是构造方法不能被声明为抽象。

抽象类不能用final来修饰,即一个类不能既是最终类又是抽象类。

抽象类可以没有抽象方法,但是如果你的一个类已经声明成了抽象类,即使这个类中没有抽象方法,它也不能再实例化,即不能直接构造一个该类的对象。

12.普通类和抽象类有哪些区别?

1.抽象类不能被实例化。

2.抽象类可以有构造函数,抽象方法不能被声明为静态。

3.抽象方法只需申明,而无需实现,抽象类中可以允许普通方法有主体

4.含有抽象方法的类必须申明为抽象类

5.抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类。

14.接口和抽象类有什么区别?

接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。

(1) 接口不能被实例化

(2) 接口只能包含方法声明

(3) 接口的成员包括方法、属性、索引器、事件

(4) 接口中不能包含常量、字段(域)、构造函数、析构函数、静态成员

  1. java 中 IO 流分为几种?

16. BIONIOAIO 有什么区别?

  • BIO:线程发起请求,不管内核是否准备号IO操作,从请求发起,线程一直阻塞,直到操作完成。
  • NIO:线程发起IO请求,立刻返回;内核在完成Io操作以后通过调用注册的回调函数通知线程进行IO操作。
  • AIO:线程发起IO请求,立刻返回;内存做好IO操作的准备以后,做IO操作,知道操作完成或者失败,通过调用注册的回调函数通知线程做IO操作。
  • BIO:是一个连接一个线程。同步阻塞,适用于连接数目较少,这种对服务器资源消耗较多
  • NIO:是一个请求线程。同步非阻塞,适用于连接数目较多而且连接时间短的架构--聊天jdk1.4以后
  • AIO:是一个有效的请求线程。异步非阻塞适用于连接数目多时间长的数据连接操作。jdk1.7开始

17.Files的常用方法都有哪些?

1.创建功能:

createNewFile()

mkdirs()

renameTo(File dest)

判断功能:

isFile()

isDirectory()

canRead()

canWrite()

idHideden()

获取功能:

getAbsolutepath()//绝对路径

getPath()//相对路径

getName()

length()

public long lastModified()  获取文件最后一次修改的时间(单位,毫秒)

二、容器

  1. java 容器都有哪些?

java容器:

数组/String/Java.util下的集合容器。

数组的限制为:Integer.MAX_VALUE。

String的限制长度:底层是char数组,长度Integer.MAX_VALUE线程安全。

List:存放有序,列表存储,元素可以重复。

Set:无序,元素不可以重复。底层实现使用hashMap=数组+链表

Map:元素不可以重复,LinkedHashMap,TreeHashMap底层用额外的链表和树进行维护

19.Collection 和 Collections 有什么区别?

  • Collections相当于一个工具类,可以reverse()逆序和sort()排序
  • Collection是一个集合的接口,它提提供了具体的实现方法。有ArrayList(),LinkedList()

20.List、Set、Map 之间的区别是什么?

List的元素以线性方式存储,可以存放重复,List主要有一下两个实现类。

ArrayList:长度可变数组,可以对元素进行随机访问,ArrayList中插入与删除元素的速度比较慢。扩容是采用1.5倍扩容。然后调用Arrays.copyof()对原来的数组进行复制。

LinkedList:采用链表数据结构,插入和删除速度较快基于链表。

Set(集合)

Set中的对象不按照特定(hashCode)的方式进行排序,并且没有重复对象,set主要有俩个实现类:

  • HashSet:按照哈希算法来存取集合中的对象,存取速度比较快。当HashSet中的元素超过*loadFactor(默认值为0.75)时,就可以近似两倍扩容。
  • TreeSet:TreeSet实现了SortedSet接口,能够对集合中的集合对象进行排序。这个是使用二叉链表实现。

Map(映射)

Map是一种把键对象和值对象映射的集合,它的每一个元素都包含一个键对象和值对象。 Map主要有以下两个实现类:

HashMap:HashMap基于散列表实现,其插入和查询<K,V>的开销是固定的,可以通过构造器设置容量和负载因子来调整容器的性能。

LinkedHashMap:类似于HashMap,但是迭代遍历它时,取得<K,V>的顺序是其插入次序,或者是最近最少使用(LRU)的次序。

TreeMap:TreeMap基于红黑树实现。查看<K,V>时,它们会被排序。TreeMap是唯一的带有subMap()方法的Map,subMap()可以返回一个子树。

22.如何决定使用 HashMap 还是 TreeMap?

TreeMap<k,v>的key值要求实现java.lang.Comparable,所以迭代的时候TreeMap默认是按照Key值升序排列的;TreeMap的实现也是基于红黑树结构。而HashMap<k,v>的Key值实现散列hashCode(),分布散列的均匀的,不支持排序;数据结构主要是通过桶(数组),链表或红黑树

大多数情况下HashMap有更好的性能,所以不需要排序的时候一般使用HashMap.

26.如何实现数组和 List 之间的转换?

List转数组:toArray(arraylist.size()方法

数组转List:Arrays的asList(a)方法

30.哪些集合类是线程安全的?

vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。

statck:堆栈类,先进后出

hashtable:就比hashmap多了个线程安全

enumeration:枚举,相当于迭代器

31.迭代器 Iterator 是什么?

对于collection进行迭代的类,成为迭代器。根据面向对象的方法,迭代器是专门取出集合元素的对象。但是该元素对象比较特殊,不能直接创建对象(通过New),该对象是以内部类的形式存在于每个集合类的内部。

如何获取迭代器?Collection接口定义了获取集合迭代器的方法(Iterator)所以所有Colletion体系集合都可以获取自身的迭代器。

总来的说:

Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包括了可以返回迭代器实例的迭代方法。迭代器可以在迭代过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object obj)删除,可以通过迭代器的remove()方法删除。

使用方法:

public class TestIterator {
	
	static List<String> list = new ArrayList<String>();
	
	static {
		list.add("111");
		list.add("222");
		list.add("333");
	}
	
 
	public static void main(String[] args) {
		testIteratorNext();
		System.out.println();
		
		testForEachRemaining();
		System.out.println();
		
		testIteratorRemove();
	}
	
	//使用 hasNext 和 next遍历 
	public static void testIteratorNext() {
		Iterator<String> iterator = list.iterator();
		while (iterator.hasNext()) {
			String str = iterator.next();
			System.out.println(str);
		}
	}
	
	//使用 Iterator 删除元素 
	public static void testIteratorRemove() {
		Iterator<String> iterator = list.iterator();
		while (iterator.hasNext()) {
			String str = iterator.next();
			if ("222".equals(str)) {
				iterator.remove();
			}
		}
		System.out.println(list);
	}
	
	//使用 forEachRemaining 遍历
	public static void testForEachRemaining() {
		final Iterator<String> iterator = list.iterator();
		iterator.forEachRemaining(new Consumer<String>() {
 
			public void accept(String t) {
				System.out.println(t);
			}
			
		});
	}

33.Iterator 和 ListIterator 有什么区别?

在使用的时候,list和set都有iterator来取得迭代器,对于list来说可以使用listIterator来获取迭代器,两种迭代器是在某些方面是不能通用的。

1.ListIterator有add()方法,可以向list中添加对象,但是iterator不能

2.ListIterator和Iterator可以实现hasPrevious和previous()

  1. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。

  2. 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。

34.怎么确保一个集合不能被修改?

首先我们很容易想到使用final进行修饰,

使用关键字final可以修饰类、方法、成员变量,使用final修饰的类不可以被继承,使用final修饰的变量必须初始化值,如果这个成员变量是基本类型数据,表述这个变量的值是不可以改变的,如果说这个成员变量是引用类型数据,则表示这个引用类型的地址是不可以改变的,但是引用指向的对象的内容还是可以改变的。

因此,如果要确保一个集合不被修改,我们要清除集合(list,set,map)都是引用类型数据,所以使用final修饰集合的内容还是可以修改的。

我们可以采用Collections包下的unmodifiableMap方法,通过这个方法返回的map,是不可以修改的。他会报 java.lang.UnsupportedOperationException错。

同理:Collections包也提供了对list和set集合的方法。
Collections.unmodifiableList(List)
Collections.unmodifiableSet(Set)

多线程:

35.并行和并发有什么区别?

并发是多个事件在同一时间间隔发生。

并行是多个事件在同一时刻发生。

36.线程和进程的区别?

进程是具有一定独立功能的程序关于某个数据集合上的一次活动,进程是资源分配的最小单位。

线程是进程的一个实体。

区别:

进程有独立的资源单位,线程只是一个进程的不同的执行路径。

线程拥有自己的堆栈和局部变量,线程之间没有独立的地址空间。

1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

37.守护线程是什么?

什么是线程守护,daemon thread,是服务框架,准确的来说就是服务其他线程,这是他的作用-而其他线程只有一种作用,就是用户线程,所以java分为两种。

  • 守护进程,比如垃圾回收
  • 用户线程:就是应用程序自己自定义的线程

这个就是用户自定义的线程都执行完毕,main也执行完毕以后,jvm就会立即退出,当jvm也退出以后,守护线程也会停止。

38.创建线程有哪几种方式?

创建的四种方法:

1.通过继承Thread类创建接口

public class MyThread extends Thread{//继承Thread类

  public void run(){

  //重写run方法

  }

}
public class Main {

  public static void main(String[] args){

    new MyThread().start();//创建并启动线程

  }

}

2.实现Runnable接口创建线程

public class MyThread2 implements Runnable {//实现Runnable接口

  public void run(){

  //重写run方法

  }

}

public class Main {

  public static void main(String[] args){

    //创建并启动线程

    MyThread2 myThread=new MyThread2();

    Thread thread=new Thread(myThread);

    thread().start();

    //或者    new Thread(new MyThread2()).start();

  }

}

3.使用Callable和Future创建

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

public class Main {

  public static void main(String[] args){

   MyThread3 th=new MyThread3();

   //使用Lambda表达式创建Callable对象

     //使用FutureTask类来包装Callable对象

   FutureTask<Integer> future=new FutureTask<Integer>(

    (Callable<Integer>)()->{

      return 5;

    }

    );

   new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程

    try{

    System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回

    }catch(Exception e){

    ex.printStackTrace();

   }

  }

}

4.-使用线程池例如用Executor框架

1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦。要执行任务的人只需把Task描述清楚,然后提交即可。这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。Executor框架的内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用Executor在构造器中。

Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。

ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。

Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。

39.说一下 runnable 和 callable 有什么区别?

Runnable和Callable的区别是:

1)callable规定执行的方法是call(),Runable规定执行的方法是run(),其中Runnable可以提交给Thread来包装下直接启动一个线程
进行执行,但是callable一般是交给ExautorService来执行。
(2)callable的任务执行后可以返回值,但是Runnable是无返回值的
(3)call方法是可以抛出异常的,run不可以
(4)运行callable任务可以拿到一个Future对象,c表示异步计算的结果


/**
 * 通过简单的测试程序来试验Runnable、Callable通过Executor来调度的时候与Future的关系
 */
package com.hadoop.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class RunnableAndCallable2Future {

    public static void main(String[] args) {

        // 创建一个执行任务的服务
        ExecutorService executor = Executors.newFixedThreadPool(3);
        try {
            //1.Runnable通过Future返回结果为空
            //创建一个Runnable,来调度,等待任务执行完毕,取得返回结果
            Future<?> runnable1 = executor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("runnable1 running.");
                }
            });
            System.out.println("Runnable1:" + runnable1.get());

            // 2.Callable通过Future能返回结果
            //提交并执行任务,任务启动时返回了一个 Future对象,
            // 如果想得到任务执行的结果或者是异常可对这个Future对象进行操作
            Future<String> future1 = executor.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    // TODO Auto-generated method stub
                    return "result=task1";
                }
            });
            // 获得任务的结果,如果调用get方法,当前线程会等待任务执行完毕后才往下执行
            System.out.println("task1: " + future1.get());

            //3. 对Callable调用cancel可以对对该任务进行中断
            //提交并执行任务,任务启动时返回了一个 Future对象,
            // 如果想得到任务执行的结果或者是异常可对这个Future对象进行操作
            Future<String> future2 = executor.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {             
                    try {
                        while (true) {
                            System.out.println("task2 running.");
                            Thread.sleep(50);
                        }
                    } catch (InterruptedException e) {
                        System.out.println("Interrupted task2.");
                    }
                    return "task2=false";
                }
            });

            // 等待5秒后,再停止第二个任务。因为第二个任务进行的是无限循环
            Thread.sleep(10);
            System.out.println("task2 cancel: " + future2.cancel(true));

            // 4.用Callable时抛出异常则Future什么也取不到了
            // 获取第三个任务的输出,因为执行第三个任务会引起异常
            // 所以下面的语句将引起异常的抛出
            Future<String> future3 = executor.submit(new Callable<String>() {

                @Override
                public String call() throws Exception {
                    throw new Exception("task3 throw exception!");
                }

            });
            System.out.println("task3: " + future3.get());
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        // 停止任务执行服务
        executor.shutdownNow();
    }
}

结果:
runnable1 running.
Runnable1:null
task1: result=task1
task2 running.
task2 cancel: true
Interrupted task2.
java.util.concurrent.ExecutionException: java.lang.Exception: Bad flag value!

FutureTask则是一个RunnableFuture,即实现了Runnbale又实现了Futrue这两个接口,另外它还可以包装Runnable和Callable,所以一般来讲是一个符合体了,它可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行,并且还可以通过v get()返回执行结果,在线程体没有执行完成的时候,主线程一直阻塞等待,执行完则直接返回结果。

public class FutureTaskTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Callable<String> task = new Callable<String>() {
            public String call() {
                System.out.println("Sleep start.");
                try {
                    Thread.sleep(1000 * 10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("Sleep end.");
                return "time=" + System.currentTimeMillis();
            }
        };

        //直接使用Thread的方式执行
        FutureTask<String> ft = new FutureTask<String>(task);
        Thread t = new Thread(ft);
        t.start();
        try {
            System.out.println("waiting execute result");
            System.out.println("result = " + ft.get());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        //使用Executors来执行
        System.out.println("=========");
        FutureTask<String> ft2 = new FutureTask<String>(task);
        Executors.newSingleThreadExecutor().submit(ft2);
        try {
            System.out.println("waiting execute result");
            System.out.println("result = " + ft2.get());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

结果
waiting execute result
Sleep start.
Sleep end.
result = time=1370844662537
=========
waiting execute result
Sleep start.
Sleep end.
result = time=1370844672542

40.线程有哪些状态?

线程的状态:新建--就绪--运行--阻塞--死亡 一共五种状态。

1.新建状态:

当使用new thread新建一个线程时,线程还没有运行,这个阶段属于新建状态,线程中的代码还没运行。

2.就绪状态:

创建一个新的线程并不自动运行,需要执行线程,必须调用start()方法运行。当线程调用start方法时开始运行,并且调度运行Run()方法运行,当stat()方法返回后,线程属于就绪状态。

处于就绪状态的线程不一定运行run()方法,线程必须还要同其他线程抢占资源,只有获取到cup时间片段后,线程才可以运行。因此在单cup中一个时刻只一个线程处于运行状态,对于处于多个cup的有专门的调度机构。

3.运行状态

当线程获取cup时间状态以后,他才可以进入运行状态,这个时候才处于真正的运行状态。开始执行run的方法。

4.线程阻塞:

线程阻塞一般分为三种:

  • 等待阻塞:线程执行wait()方法,jvm会把该线程放到线程池中。(wait会释放持有的锁)
  • 同步阻塞:线程在获取对象的同步锁时,若该同步锁被别的线程调用,则jvm会把该线程放到线程池中。
  • 其他阻塞:运行时执行sleep()或者join()方法/线程调用一个在I/O上被阻塞,就是在输入或者输出之后不会返回到他的调用者。当sleep()结束 join()终止/超时,线程会重新执行。(sleep不会放弃锁)

5.死亡状态:执行完run()

41.序列化和反序列化

原理:对象序列化过程可以分为两步:

第一: 将对象转换为字节数组

第二: 将字节数组存储到磁盘

序列化就是把是对象转化成字节顺序的过程,反序序列化就是把字节转化为对象的过程。

public class FlyPig implements Serializable {
    //private static final long serialVersionUID = 1L;
    private static String AGE = "269";
    private String name;
    private String color;
    transient private String car;
 
    //private String addTip;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getColor() {
        return color;
    }
 
    public void setColor(String color) {
        this.color = color;
    }
 
    public String getCar() {
        return car;
    }
 
    public void setCar(String car) {
        this.car = car;
    }
 
    //public String getAddTip() {
    //    return addTip;
    //}
    //
    //public void setAddTip(String addTip) {
    //    this.addTip = addTip;
    //}
 
    @Override
    public String toString() {
        return "FlyPig{" +
                "name='" + name + ''' +
                ", color='" + color + ''' +
                ", car='" + car + ''' +
                ", AGE='" + AGE + ''' +
                //", addTip='" + addTip + ''' +
                '}';
    }
}

/**
 * 序列化测试
 *
 * @author lxk on 2017/11/1
 */
public class SerializableTest {
    public static void main(String[] args) throws Exception {
        serializeFlyPig();
        FlyPig flyPig = deserializeFlyPig();
        System.out.println(flyPig.toString());
 
    }
 
    /**
     * 序列化
     */
    private static void serializeFlyPig() throws IOException {
        FlyPig flyPig = new FlyPig();
        flyPig.setColor("black");
        flyPig.setName("naruto");
        flyPig.setCar("0000");
        // ObjectOutputStream 对象输出流,将 flyPig 对象存储到E盘的 flyPig.txt 文件中,完成对 flyPig 对象的序列化操作
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/flyPig.txt")));
        oos.writeObject(flyPig);
        System.out.println("FlyPig 对象序列化成功!");
        oos.close();
    }
 
    /**
     * 反序列化
     */
    private static FlyPig deserializeFlyPig() throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:/flyPig.txt")));
        FlyPig person = (FlyPig) ois.readObject();
        System.out.println("FlyPig 对象反序列化成功!");
        return person;
    }
}

//结果

被transient 修饰的不序列化.

被static修饰的变量应该也是不会被序列化的,因为只有堆内存会被序列化.所以静态变量会天生不会被序列化。

那这里被static修饰的变量反序列化后有值又是什么鬼 这是因为 静态变量在方法区,本来流里面就没有写入静态变量,我们打印静态变量当然会去方法区查找,我们当前 jvm 中有所以静态变量在序列化后任然有值。

分享一份大彬精心整理的大厂面试手册,包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等高频面试题,非常实用,有小伙伴靠着这份手册拿过字节offer~【领取/点击】

为什么要做序列化?

  ①、在分布式系统中,此时需要把对象在网络上传输,就得把对象数据转换为二进制形式,需要共享的数据的 JavaBean 对象,都得做序列化。

  ②、服务器钝化:如果服务器发现某些对象好久没活动了,那么服务器就会把这些内存中的对象持久化在本地磁盘文件中(Java对象转换为二进制文件);如果服务器发现某些对象需要活动时,先去内存中寻找,找不到再去磁盘文件中反序列化我们的对象数据,恢复成 Java 对象。这样能节省服务器内存。

46.线程池中 submit()和 execute()方法有什么区别?

ThreadPoolExecutor线程池发现可以有两种启动线程的方法:submit(Runnable runnable),excute(Runnable runnable)

ThreadPoolExecutor的抽象父类AbstractExecutorService实现了接口ExecutorService和Executor,而submit方法是ExecutorService中的接口,executor方法是Executor中的接口,

查看AbstractExecutorService源码,发现submit方法是在此抽象父类中实现:

/**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

可以看到,submit方法是先构造出一个RunnableFuture(FutureTask) 然后调用execute方法。不管你submit的时候传入的是Runnable还是Callable最后RunnableFuture(FutureTask)里面都会生成Callable对象。任务调用的时候调用RunnableFuture(FutureTask)的run方法,run方法调用Callable对象的call方法。

submit()方法,可以提供Future < T > 类型的返回值。

ThreadPoolExecutor中的executor方法:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

executor()方法,无返回值。

47.在 java 程序中怎么保证多线程的运行安全?

当多个线程要共享一个实例对象的值得时候,那么在考虑安全的多线程并发编程时就要保证下面3个要素:

原子性(Synchronized, Lock)

有序性(Volatile,Synchronized, Lock)

可见性(Volatile,Synchronized,Lock)

当然由于synchronized和Lock保证每个时刻只有一个线程执行同步代码,所以是线程安全的,也可以实现这一功能,但是由于线程是同步执行的,所以会影响效率。

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

在Java中,基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。

可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取共享变量时,它会去内存中读取新值。

更新的主要步骤:设置当前线程缓存无效,然后将新的更新到主内存,当其他线程访问时,清除本地缓存更新新的数据。

有序性:即程序执行的顺序按照代码的先后顺序执行。

48.多线程锁的升级原理是什么?

Sycronized是重量级锁(也是悲观锁)

49.什么是死锁?

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。

当然死锁的产生是必须要满足一些特定条件的:

1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放

2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。

3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用

4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。

50.怎么防止死锁?

  • 加锁顺序(线程按照一定的顺序加锁)
  • 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
  • 死锁检测

51.ThreadLocal 是什么?有哪些使用场景?

ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。

经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。

52.说一下 synchronized 底层实现原理?

53.synchronized 和 volatile 的区别是什么?

54.synchronized 和 Lock 有什么区别?

55.synchronized 和 ReentrantLock 区别是什么?

56.说一下 atomic 的原理?

这个写的头大了 后续更新。。。。


四 设计模式

57.说一下你熟悉的设计模式?

创建型模式,共五种:

  • 工厂方法模式
  • 抽象工厂模式
  • 单例模式
  • 建造者模式
  • 原型模式。

结构型模式,共七种:

  • 适配器模式
  • 装饰器模式
  • 代理模式
  • 外观模式
  • 桥接模式
  • 组合模式
  • 享元模式。

行为型模式,共十一种:

  • 策略模式
  • 模板方法模式
  • 观察者模式
  • 迭代子模式
  • 责任链模式
  • 命令模式
  • 备忘录模式
  • 状态模式
  • 访问者模式
  • 中介者模式
  • 解释器模式

58.简单工厂和抽象工厂有什么区别?

简单工厂 : 用来生产同一等级结构中的任意产品。(对于增加新的产品,无能为力)

工厂方法 :用来生产同一等级结构中的固定产品。(支持增加任意产品)

抽象工厂 :用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)

简单工厂就是:一个工厂可以生产兰州拉面/黄焖鸡

工厂方法:就是一个大工厂下面有中国工厂和美国工厂,中国工厂生产兰州拉面,美国工厂生产牛肉面

抽象工厂:就是一个大工厂下面有英特尔厂和amd厂。英特尔组装英特尔主板+英特尔cup,amd工厂组装amd主板+amdcpu

五、Spring/Spring MVC

59.为什么要使用 spring?

spring是一个大容器,提供良好接口,切面编程。

60.解释一下什么是 aop?

AOP 即 Aspect Oriental Program 面向切面编程

首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。

所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务

所谓的周边功能,比如性能统计,日志,事务管理等等

周边功能在Spring的面向切面编程AOP思想里,即被定义为切面

在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发

然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP

61.解释一下什么是 ioc?

我的理解是,a同学去通过中介所找对象,第一步填写资料,然后中介就根据你的标准给你找一个对象,当过一段时间想换个对象时,这时你就用再次填写资料,中介会直接给你找一个对象分配给你。

62.spring 有哪些主要模块?

Spring有七大功能模块,分别是Spring Core,AOP,ORM,DAO,MVC,WEB,Context。

1,Spring Core

Core模块是Spring的核心类库,Spring的所有功能都依赖于该类库,Core主要实现IOC功能,Sprign的所有功能都是借助IOC实现的。

2,AOP

AOP模块是Spring的AOP库,提供了AOP(拦截器)机制,并提供常用的拦截器,供用户自定义和配置。

3,ORM

Spring 的ORM模块提供对常用的ORM框架的管理和辅助支持,Spring支持常用的Hibernate,ibtas,jdao等框架的支持,Spring本身并不对ORM进行实现,仅对常见的ORM框架进行封装,并对其进行管理

4,DAO模块

Spring 提供对JDBC的支持,对JDBC进行封装,允许JDBC使用Spring资源,并能统一管理JDBC事物,并不对JDBC进行实现。(执行sql语句)

5,WEB模块

WEB模块提供对常见框架如Struts1,WEBWORK(Struts 2),JSF的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器。

6,Context模块

Context模块提供框架式的Bean访问方式,其他程序可以通过Context访问Spring的Bean资源,相当于资源注入。

7,MVC模块

WEB MVC模块为Spring提供了一套轻量级的MVC实现,在Spring的开发中,我们既可以用Struts也可以用Spring自己的MVC框架,相对于Struts,Spring自己的MVC框架更加简洁和方便。

63.spring 常用的注入方式有哪些?

配置文件,注解,构造器

64.spring 中的 bean 是线程安全的吗?

Spring 不保证 bean 的线程安全。

spring 管理的 bean 的线程安全跟 bean 的创建作用域和 bean 所在的使用环境是否存在竞态条件有关,spring 并不能保证 bean 的线程安全。

65.spring 支持几种 bean 的作用域?

  • singleton:单例模式,在整个Spring IoC容器中,使用 singleton 定义的 bean 只有一个实例
  • prototype:原型模式,每次通过容器的getbean方法获取 prototype 定义的 bean 时,都产生一个新的 bean 实例

66.spring 自动装配 bean 有哪些方式?

spring 配置文件中 节点的 autowire 参数可以控制 bean 自动装配的方式:

default - 默认的方式和 "no" 方式一样

no - 不自动装配,需要使用 节点或参数 spring 配置文件,使用 ref 参数注入 bean,必须要有对象的 setter 方法,这里即 Person 的 setFr 方法。

byName - 根据名称进行装配 byName 也是需要相应的 setter 方法才能注入

byType - 根据类型进行装配 byType 也是需要相应的 setter 方法才能注入

constructor - 根据构造函数进行装配 constructor 无需 setter 方法,需要通过 构造方法注入 bean

67.spring 事务实现方式有哪些?

第一种:使用 TransactionTemplate 事务模板对象

  1. 如果使用的是JDBC的话,就用DataSourceTransactionManager。注意需要传入一个DataSource,这样,平台才知道如何和数据库打交道。

  2. 第二: 为了使得平台事务管理器对我们来说是透明的,就需要使用TransactionTemplate。使用TransactionTemplat需要传入一个PlateformTransactionManager 进入,这样,我们就得到了一个TransactionTemplate,而不用关心到底使用的是什么平台了。

3.第三: TransactionTemplate 的重要方法就是 execute 方法,此方法就是调用TransactionCallback 进行处理。实际上我们需要处理的事情全部都是在 TransactionCallback 中编码的。

4.第四: 也就是 TransactionCallback 接口,我们可以定义一个类并实现此接口,然后作为TransactionTemplate.execute 的参数。把需要完成的事情放到 doInTransaction中,并且传入一个TransactionStatus 参数。此参数是来调用回滚的。 也就是说 ,PlateformTransactionManager 和 TransactionTemplate 只需在程序中定义一次,而TransactionCallback 和 TransactionStatus 就要针对不同的任务多次定义了。

步骤:
1.配置事务管理器

<!-- 配置事务管理器 ,封装了所有的事务操作,依赖于连接池 -->
	   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	   		<property name="dataSource" ref="dataSource"></property>
	   </bean>

2.配置事务模板对象


<!-- 配置事务模板对象 -->
       <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
            <property name="transactionManager" ref="transactionManager"></property>
        </bean>

第三种:基于Aspectj AOP开启事务

1.配置事务通知

	   <tx:advice id="txAdvice"  transaction-manager="transactionManager">
      	<tx:attributes>
	      	<tx:method name="*" propagation="REQUIRED" rollback-for="Exception" />
      	</tx:attributes>
       </tx:advice>

2.配置织入

       <aop:config>
       <!--     扫描 cn.sys.service 路径下所有的方法,并加入事务处理 -->
      	<aop:pointcut id="tx"  expression="execution(* cn.sys.service.*.*(..))" />
      	<aop:advisor advice-ref="txAdvice" pointcut-ref="tx" />
      </aop:config>

这样就算是给 cn.sys.service下所有的方法加入了事务

第四种:基于注解的 @Transactional 的声明式事务管理

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.2.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
           
       <!-- 创建加载外部Properties文件对象 -->
       <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
       		<property name="location" value="classpath:dataBase.properties"></property>
       </bean>
    <!-- 引入redis属性配置文件 -->
    <import resource="classpath:redis-context.xml"/>

       <!-- 配置数据库连接资源 -->
       <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" scope="singleton">
       		<property name="driverClassName" value="${driver}"></property>
       		<property name="url" value="${url}"></property>
       		<property name="username" value="${username}"></property>
       		<property name="password" value="${password}"></property>

       		<property name="maxActive" value="${maxActive}"></property>
       		<property name="maxIdle" value="${maxIdle}"></property>
       		<property name="minIdle" value="${minIdle}"></property>
       		<property name="initialSize" value="${initialSize}"></property>
       		<property name="maxWait" value="${maxWait}"></property>
       		<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}"></property>
       		<property name="removeAbandoned" value="${removeAbandoned}"></property>

       		<!-- 配置sql心跳包 -->
       		<property name= "testWhileIdle" value="true"/>
			<property name= "testOnBorrow" value="false"/>
			<property name= "testOnReturn" value="false"/>
			<property name= "validationQuery" value="select 1"/>
			<property name= "timeBetweenEvictionRunsMillis" value="60000"/>
			<property name= "numTestsPerEvictionRun" value="${maxActive}"/>
       </bean>

       <!--创建SQLSessionFactory对象  -->
       <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
       		<property name="dataSource" ref="dataSource"></property>
       		<property name="configLocation" value="classpath:MyBatis_config.xml"></property>
       </bean>

       <!-- 创建MapperScannerConfigurer对象 -->
       <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
       		<property name="basePackage" value="cn.sys.dao"></property>
       </bean>

       <!-- 配置扫描器   IOC 注解 -->
	   <context:component-scan base-package="cn.sys" />

	   <!-- 配置事务管理 ,封装了所有的事务操作,依赖于连接池 -->
	   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	   		<property name="dataSource" ref="dataSource"></property>
	   </bean>

        <!-- 配置事务模板对象 -->
       <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
            <property name="transactionManager" ref="transactionManager"></property>
        </bean>

<!-- 	  配置事务增强 -->
	 <tx:advice id="txAdvice"  transaction-manager="transactionManager">
      	<tx:attributes>
	      	<tx:method name="*" propagation="REQUIRED" rollback-for="Exception" />
      	</tx:attributes>
       </tx:advice>
       
<!--     aop代理事务 -->
       <aop:config>
      	<aop:pointcut id="tx"  expression="execution(* cn.sys.service.*.*(..))" />
      	<aop:advisor advice-ref="txAdvice" pointcut-ref="tx" />
      </aop:config>
</beans>

68.说一下 spring 的事务隔离?

@Transactional(propagation=Propagation.REQUIRED)

如果有事务就新添加一条事务。没有默认添加一个

@ Transactional(propagation=Propagation.NOT_SUPPORTED)

容器不为方法开启事务

@ Transactional(propagation=Propagation.REQUIRES_NEW)

不管事务是否存在直接新建一个事务,如果都创建事务,原来的事务挂起,当第二个事务执行完毕后,在执行挂起的事务

@ Transactional(propagation=Propagation.MANDATORY)

必须在一个事务执行完毕否则抛出异常

@Transactional(propagation=Propagation.NEVER)

必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)

@ Transactional(propagation=Propagation.SUPPORTS)

如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.

事务隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)
读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ)
可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE)
串行化

MYSQL: 默认为REPEATABLE_READ级别
SQLSERVER: 默认为READ_COMMITTED

脏读 : 一个事务读取到另一事务未提交的更新数据
不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说,
后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次
读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
幻读 : 一个事务读到另一个事务已提交的insert数据

69.说一下 spring mvc 运行流程?

70.spring mvc 有哪些组件?

前端控制器(DispatcherServlet)

处理器映射器(HandlerMapping)

处理器适配器(HandlerAdapter)

拦截器(HandlerInterceptor)

语言环境处理器(LocaleResolver)

主题解析器(ThemeResolver)

视图解析器(ViewResolver)

文件上传处理器(MultipartResolver)

异常处理器(HandlerExceptionResolver)

数据转换(DataBinder)

消息转换器(HttpMessageConverter)

请求转视图翻译器(RequestToViewNameTranslator)

页面跳转参数管理器(FlashMapManager)

处理程序执行链(HandlerExecutionChain)

71.@RequestMapping 的作用是什么?

@RequestMapping 是一个注解,用来标识 http 请求地址与 Controller 类的方法之间的映射。

72.@Autowired 的作用是什么?

  1. @Autowired可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作,这里必须明确:@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Qualifier使用;
  2. @Autowired标注可以放在成员变量上,也可以放在成员变量的set方法上。前者,Spring会直接将UserDao类型的唯一一个bean赋值给userDao这个成员变量;后者,Spring会调用setUserDao方法来将UserDao类型的唯一一个bean装配到userDao这个属性。
  3. Spring 2.5 引入了 @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的使用来消除 set ,get方法。

六、 Spring Boot/Spring Cloud

73.什么是 spring boot?

用来简化搭建spring应用,嵌套容器tomcat,jetty,简化xml配置。

74.为什么要用 spring boot?

  • 支持Quartz调度程序
  • 简化安全自动配置
  • 至此嵌入式Netty,tomcat
  • 全新框架,支持springMVC,webFlux
  • 为各种组件的响应式编程提供自动化配置

75.spring boot 核心配置文件是什么?

76.spring boot 配置文件有哪几种类型?它们有什么区别?

Spring boot核心配置的两种形式:

1、.properties: 键值对的配置方式

2、.yml:值与前面的问号必须存在一个问好

server:
  port: 8080
  context-path: spring-webproject

77.spring boot 有哪些方式可以实现热部署?

spring loaded:

//引入依赖
<build>
 <plugins>
 <plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <dependencies>
   <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>springloaded</artifactId>
    <version>1.2.6.RELEASE</version>
   </dependency>
  </dependencies>
 </plugin>
 </plugins>
</build>

//运行方式
运行的时候不能直接点击main运行,需要使用
mvn:spring-boot:run启动

spring-boot-devtools

//引入以来
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-devtools</artifactId>
 <optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
</dependency>
//然后还是使用 mvn spring-boot:run 启动项目,随意更改代码即可看到效果。

如果我们想指定让 devtools 监听指定文件夹,那么可以在 application.yml 配置
spring.devtools.restart.additional-paths=your path,注意这里需要改成 yml 文件的格式。