Java常见面试题 Java面试必看 (一)

     本篇博客是本人收集网上Java相关的资料整理所得,仅供参考。

    一、Java基础

1、JDK 和 JRE区别

    JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。Java Runtime Environment(JRE)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。JVM是Java Virtual Machine(Java虚拟机)的缩写,是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。

2、== 和 equals 的区别

    == 号比较的是内存地址;equals()比较的是字符串的内容。

    总结:

    1.对于基本数据类型 (byte short int long float double char boolean),只有==,没有equals()(注意String类型不是基本数据类型)
    2.对于字符串,== 指比较两者的内存地址,equals()则重写了Object类的equals(),比较的是内容。对于字符串是存放在方法区的字符串常量池里的,无论定义多少,只要字符串值相等都指同一块内存地址,所以==和equals()结果没区别。
    3.对于实例对象而言,==比较的依然是内存地址(所以你可以看出无论什么情况,==一直比较的都是内存地址),而equals()默认重写Object类的equals()方法(比较地址)。但是一般我们会自己重写equals(),让它比较值是否相等。

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

    不一定,因为两个对象equals相等,则它们的hashcode必须相等,反之则不一定。

4、final 在 java 中的作用

    在Java中,final关键字可以用来修饰类、方法和变量
    当用final修饰类时,表明这个类不能被继承。
    当用final修饰方法,此方法不能被重写(可以重载多个final修饰的方法)
    当用final修饰变量,此变量表示常量,只能被赋值一次,赋值后值不再改变。

5、java 中的 Math.round(-1.5) 等于多少

    答案:-1

    Math.round(1.5) = 2

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

     String不是基本的数据类型,是final修饰的java类

    java中的基本类型一共有8个,它们分别为:
    1 字符类型:byte,char
    2 基本整型:short,int,long
    3 浮点型:float,double
    4 布尔类型:boolean

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

    String、StringBuffer、StringBuilder

    String : final修饰,String类的方法都是返回new String。即对String对象的任何改变都不影响到原对象,对字符串的修改操作都会生成新的对象。
    StringBuffer : 对字符串的操作的方法都加了synchronized,保证线程安全。
    StringBuilder : 不保证线程安全,在方法体内需要进行字符串的修改操作,可以new StringBuilder对象,调用StringBuilder对象的append、replace、delete等方法修改字符串。

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

    String str = "i"; 这个只是一个引用,内存中如果有“i"的话,str就指向它;如果没有,才创建它;
    如果你以后还用到"i"这个字符串的话并且是这样用:
    String str1 = "i"; String str2 = "i"; String str3 = "i"; 这4个变量都共享一个字符串"a"。
    而String str = new String("i");是根据"i"这个String对象再次构造一个String对象,将新构造出来的String对象的引用赋给str。

    8.1、String str="i"与 String str1=new String(“i”)一样吗

        (a)str==str1 的判断为false;
        (b)str.equals(str1)为true (因为值相等,所以只能往HashSet里面放一个)

    总结:String str = new String(“i”) 会创建2(1)个对象,String str = “i” 创建1(0)个对象。 
    注:当字符串常量池中有对象 i 时括号内成立!


9、如何将字符串反转

    一、提到字符串的反转,最先想到的应该是StringBuiler / StringBuffer的reverse()的方法,方便快捷。

    public static String reverseStringBuilder(String s) {
        StringBuilder sb = new StringBuilder(s);                      
        String afterreverse = sb.reverse().toString();
        return afterreverse;
    }

    二、通过String类的charAt()的方法来获取字符串中的每一个字符,然后将其拼接为一个新的字符串。

    /**
      * 该方法是通过charAt()方法获得每一个char的字符,i=0时获得第一个字符a然后赋值给reverse
      * 此时reverse="a";i=1时获得第二个字符b然后加上reverse再赋值给reverse,此时reverse="ba";
      * 以次类推
      */
     public static String CharAtreverse(String s) {
          int length = s.length();
          String reverse = "";
          for (int i=0; i<length; i++)
              reverse = s.charAt(i) + reverse;
          return reverse;
      }

    三、    通过String的toCharArray()方法可以获得字符串中的每一个字符串并转换为字符数组,然后用一个空的字符串从后向前一个个的拼接成新的字符串。
    public static String reverseCharArray(String s) {
        char[] array = s.toCharArray();
        String reverse = "";
        for (int i = array.length - 1; i >= 0; i--) {
            reverse += array[i];
        }
        return reverse;
    }

    四、    通过递归的方式
    public static String reverseRecursive(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);
        String afterReverse = reverse1(right) + reverse1(left);
        return afterReverse;
    }

10、String 类的常用方法都有哪些

    String(String)、length()、isEmpty()、charAt(int)、getBytes()、equals(Object)、hashCode()、indexOf(int)、substring(int)、concat(String)、replace(char, char)、matches(String)、contains(CharSequence) 

11、抽象类必须要有抽象方法吗

    抽象类可以没有抽象方法,但是如果你的一个类已经声明成了抽象类,即使这个类中没有抽象方法,它也不能再实例化,即不能直接构造一个该类的对象。
如果一个类中有了一个抽象方法,那么这个类必须声明为抽象类,否则编译通不过。

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

    1.抽象类不能被实例化。
    2.抽象类可以有构造函数,被继承时子类必须继承父类一个构造方法,抽象方法不能被声明为静态。
    3.抽象方法只需申明,而无需实现,抽象类中可以允许普通方法有主体
    4.含有抽象方法的类必须申明为抽象类
    5.抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类。

13、抽象类能使用 final 修饰吗

       Java抽象类不可以被 final修饰
  抽象类需要被继承才能使用,而被final修饰的类无法被继承,所以abstract和final是不能共存的。

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

    1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
    2、抽象类要被子类继承,接口要被类实现。
    3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
    4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
    5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
    6、抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果
    7、抽象类里可以没有抽象方法
    8、如果一个类里有抽象方法,那么这个类只能是抽象类
    9、抽象方法要被实现,所以不能是静态的,也不能是私有的。
    10、接口可继承接口,并可多继承接口,但类只能单根继承。

15、java 中 IO 流分为几种

    1.按方向分类
    输入流、输出流

    输入流    
    InputStream    所有字节输入流的超类
    FileInputStream    文件字节输入流
    ByteArrayInputStream 字节数组输入流
    Reader 读取字符流的抽象类
    FileReader 文件字符输入流

    输出流    
    OutputStream 所有字节输出流的超类
    FileOutputStream 文件字节输出流
    ByteArrayOutputStream 字节数组输出流
    Writer 写入字符流的抽象类
    FileWriter 文件字符输出流

    2.按数据单元分类
    以字节为单位的称为字节流,以字符为单位的称为字符流.字节流可以读写任意资源,字符流是为了更便捷的读写文字.

    字节流    
    FileInputStream 文件字节输入流
    ByteArrayOutputStream 字节数组输出流


    字符流    
    BufferedReader 缓冲字符输入流
    BufferedWriter 缓冲字符输出流

    3.按功能分类
    直接与底层文件资源连接的称为节点流,对节点流进行包装从而完成更高级功能的称为处理流,处理流在构造时须为其指定一个节点流.

    节点流    
    FileInputStream 文件流
    ByteArrayInputStream 字节数组流
    PipedInputStream 管道流

    处理流    
    InputStreamReader 桥梁流
    BufferedReader 缓冲流
    DataInputStream 数据流
    ObjectInputStream 对象流
    SequenceInputStream 合并流

16、BIO、NIO、AIO 有什么区别

    同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO

    一、同步阻塞I/O(BIO):
    同步阻塞I/O,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制来改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务端资源要求比较高,并发局限于应用中,在jdk1.4以前是唯一的io现在,但程序直观简单易理解
    二、同步非阻塞I/O(NIO):
    同步非阻塞I/O,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,jdk1,4开始支持
    三、异步非阻塞I/O(AIO):
    异步非阻塞I/O,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程进行处理。AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,jdk1.7开始支持。

17、Files的常用方法有哪些

    一,创建功能
    1,public boolean createNewFile() throws IOException     创建新文件
    2,public boolean mkdirs()    创建新的目录,若父目录不存在,会自动创建
    3,public boolean renameTo(File dest) 重命名文件

    二,判断功能
    1,public boolean isFile()     判断是否是文件
    2,public boolean isDirectory()    判断是否是目录
    3,public boolean exists()    判断文件或者目录是否存在
    4,public boolean canRead()    判断文件是否可读
    5,public boolean canWrite()    判断文件是否可写
    6,public boolean isHidden()    判断文件是否隐藏

    三,获取功能
    1,public String getAbsolutePath()    获取绝对路径
    2,public String getPath()    获取相对路径
    3,public String getName()   获取文件或目录名
    4,public long length()    获取文件大小(应用例如:用于限制上传文件大小)
    5,public long lastModified()    获取文件最后一次修改的时间(单位,毫秒)

    四,高级获取功能
    1,public String[] list()     获取路径表示目录下的所有文件和目录名称
    2,public String[] list(FilenameFilter filter)     获取满足过滤器FilenameFilter条件的所有目录或文件
    3,public File[] listFiles()    获取路径表示目录下的所有文件和目录对象(文件类型)
    4,public File[] listFiles(FilenameFilter filter)    获取满足过滤器FilenameFilter条件的所有目录或文件对象(文件类型)
 

    二、容器

18、java 容器都有哪些

    数组,String,java.util下的集合容器

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

    List:存放有序,列表存储,元素可重复(ArrayList、LinkedList、Vector)
    Set:无序,元素不可重复(HashSet、TreeSet、LinkedHashSet)
    Map:无序,元素可重复(HashMap、LinkedHashMap、Hashtable 、TreeMap)

19、Collection 和 Collections 有什么区别

    Collection是集合类的上级接口,继承与他有关的接口主要有List和Set
    Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全等操作

20、List、Set、Map 之间的区别

    List、Set是实现了Collection接口的子接口;而Map是另一个集合接口;(Collection接口和Map接口是平级的)
    1) 元素重复性:
    ① List允许有重复的元素。任何数量的重复元素都可以在不影响现有重复元素的值及其索引的情况下插入到List集合中;
    ② Set集合不允许元素重复。Set以及所有实现了Set接口的类都不允许重复值的插入,若多次插入同一个元素时,在该集合中只显示一个;
    ③ Map以键值对的形式对元素进行存储。Map不允许有重复键,但允许有不同键对应的重复的值;

    2) 元素的有序性:
    ① List及其所有实现类保持了每个元素的插入顺序;
    ② Set中的元素都是无序的;但是某些Set的实现类以某种特殊形式对其中的元素进行排序,如:LinkedHashSet按照元素的插入顺序进行排序;
    ③ Map跟Set一样对元素进行无序存储,但其某些实现类对元素进行了排序。如:TreeMap根据键对其中的元素进行升序排序;

    3) 元素是否为空值:
    ① List允许任意数量的空值;
    ② Set最多允许一个空值的出现;[ 当向Set集合中添加多个null值时,在该Set集合中只会显示一个null元素]
    ③ Map只允许出现一个空键,但允许出现任意数量的空值;

    总结:List中的元素,有序、可重复、可为空;
          Set中的元素,无序、不重复、只有一个空元素;
          Map中的元素,无序、键不重,值可重、可一个空键、多个空值;
21、HashMap 和 Hashtable 区别

    HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap
    Hashtable是线程安全的,能用于多线程环境中。

    Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map、Cloneable(可复制)、Serializable(可序列化)接口。

    Hashtable既不支持Null key也不支持Null value。
    HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能用get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。

    Hashtable的初始长度是11,之后每次扩充容量变为之前的2n+1(n为上一次的长度)
    而HashMap的初始长度为16,之后每次扩充变为原来的两倍

22、如何决定使用 HashMap 还是 TreeMap

    (1)HashMap:适用于在Map中插入、删除和定位元素。 
    (2)Treemap:适用于按自然顺序或自定义顺序遍历键(key)。 
    (3)HashMap通常比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在需要排序的Map时候才用TreeMap. 
    (4)HashMap 非线程安全 TreeMap 非线程安全 
    (5)HashMap的结果是没有排序的,而TreeMap输出的结果是排好序的。

23、HashMap 的实现原理

    简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
    
    HashMap的resize
       当hashmap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对hashmap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,所以这是一个通用的操作,很多人对它的性能表示过怀疑,不过想想我们的“均摊”原理,就释然了,而在hashmap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
       那么hashmap什么时候进行扩容呢?当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。

24、HashSet 的实现原理

     HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素。
     对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成

25、ArrayList 和 LinkedList 的区别

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

    ArrayList更适合读取数据,linkedList更多的时候添加或删除数据

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

    数组转List
        List<String> list = Arrays.asList(arrays);
        Collections.addAll(list, arrays);

    List转数组
        String[] arrays = list.toArray(new String[list.size()]);

27、ArrayList 和 Vector 的区别

    Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。 
    当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。

28、Array 和 ArrayList 区别

    1.ArrayList是Array的复杂版本;
    2.存储的数据类型:Array只能存储相同数据类型的数据,而ArrayList可以存储不同数据类型的数据;
    3.长度的可变:Array的长度是固定的,而ArrayList的长度是可变的。

29、在 Queue 中 poll()和 remove()有什么区别

    1. remove方法和poll方法都是删除队列的头元素,remove方法在队列为空的情况下将抛异常,而poll方法将返回null;
    2. queue的增加元素方法add和offer的区别在于,add方法在队列满的情况下将选择抛异常的方法来表示队列已经满了,而offer方法通过返回false表示队列已经满了;在有限队列的情况,使用offer方法优于add方法;
    3. element和peek方法都是返回队列的头元素,但是不删除头元素,区别在于element方法在队列为空的情况下,将抛异常,而peek方法将返回null.

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

    线程安全(Thread-safe)的集合对象:
        Vector
        Stack(栈,继承于Vector)
        HashTable
        StringBuffer
        ConcurrentHashMap
        
    非线程安全的集合对象:
        ArrayList
        LinkedList
        HashMap
        HashSet
        TreeMap
        TreeSet
        StringBulider

31、迭代器 Iterator 是什么

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

32、Iterator 怎么使用?有什么特点?

    public class MyIterator {
        public static void main(String[] agrs) {
            Collection mCollection = new ArrayList<String>();
            mCollection.add("string1");
            ...
            ...
            Iterator<String> mIterator = mCollection.iterator();
            
            //第一种
            mIterator.forEachRemaining(object->System.out.println("@"+object ));
            
            //第二种
            mIterator = mCollection.iterator();//需要重新获得Iterator对象,否则在forEachRemaining()方法后调用haxNext()会直接返回false
            while (mIterator.hasNext()) {
                String temp = (String) mIterator.next();
                System.out.println(temp);
                
                if (temp.equals("string4")) {
                    mIterator.remove();//通过Iterator来操作ArrayList中的元素,这里删除的是之前Iterator.next返回的元素
                    //mCollection.remove(temp);//在Iterator遍历集合元素的过程中,一旦集合中的元素被修改,就会抛出ConcurrentModificationException的异常
                }
            }
     
            System.out.println(mCollection);
        }
    }

    Iterator遍历集合元素的过程中不允许线程对集合元素进行修改,否则会抛出ConcurrentModificationEception的异常。
    Iterator遍历集合元素的过程中可以通过remove方法来移除集合中的元素。
    Iterator必须依附某个Collection对象而存在,Iterator本身不具有装载数据对象的功能。
    Iterator.remove方法删除的是上一次Iterator.next()方法返回的对象。
    强调一下next()方法,该方法通过游标指向的形式返回Iterator下一个元素。

    Iterator的常用方法:
    boolean hasNext() ;判断迭代器中是否还有下一个元素,有则返回true
    Object  next();  返回迭代器中下一个元素
    void  remove() ; 删除集合里上一个next()方法调用的时候返回的对象元素
    void forEachRemaining(Consumer action) ;使用Lambdda表达式的形式输出Iterator中所有的元素。forEachRemaining也是通过调用next方法来遍历迭代器中的元素的,所以再次使用next方法的时候,游标已经指向了Iterator中的末尾,此时hasNext()方法返回的为false

33、Iterator 和 ListIterator 区别

    一.相同点
    都是迭代器,当需要对集合中元素进行遍历不需要干涉其遍历过程时,这两种迭代器都可以使用。

    二.不同点
    1.使用范围不同,Iterator可以应用于所有的集合,Set、List和Map和这些集合的子类型。而ListIterator只能用于List及其子类型。
    2.ListIterator有add方法,可以向List中添加对象,而Iterator不能。
    3.ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator不可以。
    4.ListIterator可以定位当前索引的位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
    5.都可实现删除操作,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。

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

    Collections.unmodifiableXXX:Collection、List、Set、Map...
    Guava:ImmutableXXX:Collection、List、Set、Map...

    三、多线程

35、并行和并发有什么区别

    并发的关键是你有处理多个任务的能力,不一定要同时。  
    并行的关键是你有同时处理多个任务的能力。 

    通熟理解:

    你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
    你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。  (不一定是同时的)
    你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

36、线程和进程的区别

    进程是资源分配最小单位,线程是程序执行的最小单位;
    进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;
    CPU切换一个线程比切换进程花费小;
    创建一个线程比进程开销小;
    线程占用的资源要比进程少很多。
    线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)
    多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间);
    进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;

37、守护线程是什么

    守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程,这是它的作用——而其他的线程只有一种,那就是用户线程。所以java里线程分2种,
    1、守护线程,比如垃圾回收线程,就是最典型的守护线程。
    2、用户线程,就是应用程序里的自定义线程。

38、创建线程有哪几种方式

    不返回结果
        继承Thread类
        implements Runnable接口

    返回结果
        实现Callable接口通过FutureTask包装器来创建Thread线程
        使用ExecutorService、Callable、Future实现有返回结果的多线程

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

    相同点:
        两者都是接口;
        两者都可用来编写多线程程序;
        两者都需要调用Thread.start()启动线程;
     
    不同点:
        两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
        Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

    注意点:
        Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

40、线程有哪些状态

    新建状态、就绪状态、运行状态、阻塞状态及死亡状态

41、sleep() 和 wait() 有什么区别

    sleep()方法属于Thread类中;wait()方法属于Object类中

    sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法
    sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
    Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。

    wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 

42、notify()和 notifyAll()有什么区别

    notify方法只唤醒一个等待(对象的)线程并使该线程开始执行(随机)。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
    notifyAll会唤醒所有等待(对象的)线程,哪一个线程将会第一个处理取决于操作系统的实现。

43、线程的 run()和 start()有什么区别

    每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
    实现并启动线程有两种方法:
        1、写一个类继承自Thread类,重写run方法。用start方法启动线程
        2、写一个类实现Runnable接口,实现run方法。用new Thread(Runnable target).start()方法来启动

    /**
     * run()相当于线程的任务处理逻辑的入口方法
     * start()的作用是启动相应的线程
     */
    public class startAndRun {
        public static void  main(String[] args) {
            Thread t = new Thread(){
                public void run(){
                    world();
                }
            };
            
            t.start();
            System.out.print(" Hello ");
        }
        
        static void world(){
            System.out.print(" world ");
        }
    }

44、创建线程池有哪几种方式

    通常开发者都是利用Executors提供的通用线程池创建方法,去创建不同配置的线程池
    Executors目前提供了5种不同的线程池创建配置:
    1、newCachedThreadPool(),它是用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置时间超过60秒,则被终止并移除缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用SynchronousQueue作为工作队列。
    2、newFixedThreadPool(int nThreads),指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有nThreads个工作线程是活动的。这意味着,如果任务数量超过了活动线程数目,将在工作队列中等待空闲线程出现;如果工作线程退出,将会有新的工作线程被创建,以补足指定数目nThreads。
    3、newSingleThreadExecutor(),它的特点在于工作线程数目限制为1,操作一个无界的工作队列,所以它保证了所有的任务都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免改变线程数目。
    4、newSingleThreadScheduledExecutor()和newScheduledThreadPool(int corePoolSize),创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。
    5、newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。

45、线程池都有哪些状态

    1、RUNNING
        状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。 
        状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!

    2、SHUTDOWN
        状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。 
        状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

    3、STOP
        状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。 
        状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

    4、TIDYING
        状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。 
        状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 
                  当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

    5、TERMINATED
        状态说明:线程池彻底终止,就变成TERMINATED状态。 
        状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

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

    接收的参数不一样
        excute入参Runnable
        submit入参可以为Callable,也可以为Runnable。

    submit有Future返回值,而execute没有返回值

    submit方便Exception处理
        希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get()抛出的异常。

public class MainTest {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        Future submit = pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("submit");
                System.out.println(0/0);
            }
        });
        
        try {
            System.out.println("result=" + submit.get());
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

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

    线程安全在三个方面体现
        原子性(Synchronized, Lock)
        有序性(Volatile,Synchronized, Lock)
        可见性(Volatile,Synchronized,Lock)

    1、synchronized关键字
        最简单的方式是加入synchronized关键字,只要将操作共享数据的语句加入synchronized关键字,在某一时段只会让一个线程执行完,在执行过程中,其他线程不能进来执行
        
   2、使用锁Lock

    区别:
        a.Lock使用起来比较灵活,但需要手动释放和开启;采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;
        b.Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
        c.在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降很严重,此时Lock是个不错的方案。
        d.使用Lock的时候,等待/通知 是使用的Condition对象的await()/signal()/signalAll(),而使用synchronized的时候,则是对象的wait()/notify()/notifyAll();由此可以看出,使用Lock的时候,粒度更细了,一个Lock可以对应多个Condition。
        e.虽然Lock缺少了synchronized隐式获取释放锁的便捷性,但是却拥有了锁获取与是释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized所不具备的同步特性;

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

    锁升级类型:锁偏向、轻量级锁、自旋锁、重量级锁

    总结:所谓的锁升级,其实就是从偏向锁 -> 轻量级锁(自旋锁) -> 重量级锁,其实说白了,一切一切的开始源于java对synchronized同步机制的性能优化,最原始的synchronized同步机制是直接跳过前几个步骤,直接进入重量级锁的,而重量级锁因为需要线程进入阻塞状态(从用户态进入内核态)这种操作系统层面的操作非常消耗资源,这样的话,synchronized同步机制就显得很笨重,效率不高。那么为了解决这个问题,java才引入了偏向锁,轻量级锁,自旋锁这几个概念。拿这几个锁有何优化呢?其实说白了就是,偏向锁是为了避免CAS操作,尽量在对比对象头就把加锁问题解决掉,只有冲突的情况下才指向一次CAS操作,而轻量级锁和自旋锁呢,其实两个是一体使用的,为的是尽量避免线程进入内核的阻塞状态,这对性能非常不利,试图用CAS操作和循环把加锁问题解决掉,而重量级锁是最终的无奈解决方案,说白了就是能通过内存读取判断解决加速问题优于 〉通过CAS操作和空循环优于 〉CPU阻塞,唤醒线程。

49、什么是死锁

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

50、怎么防止死锁

    java 死锁产生的四个必要条件 
    1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用 
    2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。 
    3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。 
    4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

    理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。只要打破四个必要条件之一就能有效预防死锁的发生:打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。

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

    翻译过来中文意思就叫线程局部变量(thread local variable),就是为每个线程都创建一个这样的变量(以ThreadLocal对象为键、任意对象为值的存储结构),这个变量被附带在线程上,每个线程之接相互隔离,互不干扰,该变量副本只能创建它的线程能使用。

    最常见的ThreadLocal使用场景为 用来解决数据库连接、Session管理等。

    //数据库连接
    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
        public Connection initialValue() {  
            return DriverManager.getConnection(DB_URL);  
        }  
    };  
      
    public static Connection getConnection() {  
        return connectionHolder.get();  
    }

    //Session管理:
    private static final ThreadLocal threadSession = new ThreadLocal();  
      
    public static Session getSession() throws InfrastructureException {  
        Session s = (Session) threadSession.get();  
        try {  
            if (s == null) {  
                s = getSessionFactory().openSession();  
                threadSession.set(s);  
            }  
        } catch (HibernateException ex) {  
            throw new InfrastructureException(ex);  
        }  
        return s;  
    }

52、说一下 synchronized 底层实现原理

     Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题。
    从语法上讲,Synchronized总共有三种用法:
        (1)修饰普通方法
        (2)修饰静态方法
        (3)修饰代码块

    Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

53、synchronized 和 volatile 的区别是什么

    volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
    volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
    volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
    volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
    volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

54、synchronized 和 Lock 有什么区别

    1.用法不一样。synchronized既可以加在方法上,也可以加载特定的代码块上,括号中表示需要锁的对象。而Lock需要显示地指定起始位置和终止位置。synchronzied是托管给jvm执行的,Lock锁定是通过代码实现的。 
    2.在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。 
    3.锁的机制不一样。synchronized获得锁和释放的方式都是在块结构中,而且是自动释放锁。而Lock则需要开发人员手动去释放,并且必须在finally块中释放,否则会引起死锁问题的发生。 
    4.Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
    5.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
    6.Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。Lock可以提高多个线程进行读操作的效率。

55、synchronized 和 ReentrantLock 区别是什么

    锁的实现:
        Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的,有什么区别,说白了就类似于操作系统来控制实现和用户自己敲代码实现的区别。前者的实现是比较难见到的,后者有直接的源码可供阅读。

    性能的区别:
        在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。

    功能区别:
        便利性:很明显Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。
        锁的细粒度和灵活度:很明显ReenTrantLock优于Synchronized

    ReenTrantLock独有的能力:
        1.ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
        2.ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
        3.ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。

56、说一下 atomic 的原理

    基于CAS的乐观锁实现
    CAS(compare-and-swap)直译即比较并交换,提供原子化的读改写能力,是Java 并发中所谓 lock-free 机制的基础。
    CAS的思想很简单:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。

    四、反射

57、什么是反射

    Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。
58、什么是 java 序列化?什么情况下需要序列化?

    什么是Java序列化:把Java对象转换为字节序列的过程,也就是将对象的内容进行流化。
    反序列化:就是把二进制数据反序列化成对象数据。

    为什么要序列:
         a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
         b)当你想用套接字在网络上传送对象的时候;
         c)当你想通过RMI传输对象的时候;

    JAVA序列化有哪些方式(性能由低至高)
        Java Serialization(主要是采用JDK自带的Java序列化实现,性能很不理想)
        Json(目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自己实现的简单json库)
        FastJson(阿里的fastjson库)
        Hession(它基于HTTP协议传输,使用Hessian二进制序列化,对于数据包比较大的情况比较友好。)
        Dubbo Serialization(阿里dubbo序列化)
        FST(高性能、序列化速度大概是JDK的4-10倍,大小是JDK大小的1/3左右)

59、动态代理是什么?有哪些应用?

    动态代理,利用Java的反射技术(Java Reflection),在运行时创建一个实现某些给定接口的新类(也称“动态代理类”)及其实例(对象)

    动态代理的应用:Spring的AOP,rpc框架的实现,加事务,加权限,加日志。

60、怎么实现动态代理

    Java实现动态代理的两种方式,一种是JDK反射机制提供的代理,另一种是CGLIB代理

    JDK:实现 InvocationHandler 接口,重写invoke 方法。
    CGLIB:实现 MethodInterceptor 接口,重写 intercept 方法。

    五、对象拷贝

61、为什么要使用克隆

    想对一个对象进行处理,又想保留原有的数据进行接下来的操作,就需要克隆了。克隆分浅克隆和深克隆,浅克隆后的对象中非基本对象和原对象指向同一块内存,因此对这些非基本对象的修改会同时更改克隆前后的对象。深克隆可以实现完全的克隆,可以用反射的方式或序列化的方式实现。

62、如何实现对象克隆

    Object中的clone()方法,克隆的对象必须实现Cloneable这个接口,而且需要重写clone方法

    利用serializable实现深复制(这个是利用Serializable,利用序列化的方式来实现深复制(深克隆),在其中利用了Io流的方式将这个对象写到IO流里面,然后在从IO流里面读取,这样就实现了一个复制,然后实现序列化的这个会将引用的那个对象也一并进行深复制,这样就实现了这个机制,同时在IO里面读取数据的时候还使用了装饰者模式)

63、深拷贝和浅拷贝区别是什么

    数据类型分为两种:基础类型和引用类型
        1、基础类型:像Number、String、Boolean等这种为基本类型
        2、引用类型:Object和Array

    浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝
    深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝

    六、Java Web

64、jsp 和 servlet 有什么区别

    jsp和servlet的区别:
        1.jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)
        2.jsp更擅长表现于页面显示,servlet更擅长于逻辑控制.
        3.Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到.
        4.Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如何镶嵌到一个类中,由Jsp容器完成。而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。

    联系:JSP是Servlet技术的扩展,本质上就是Servlet的简易方式。JSP编译后是“类servlet”。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。JSP侧重于视图,Servlet主要用于控制逻辑

65、jsp 有哪些内置对象?作用分别是什么?

    JSP共有以下9个内置的对象:Page,pageContext,request,response,session,application,out,config,exception

    Page指的是JSP被翻译成Servlet的对象的引用.
    pageContext对象可以用来获得其他8个内置对象,还可以作为JSP的域范围对象使用.pageContext中存的值是当前的页面的作用范围
    request代表的是请求对象,可以用于获得客户机的信息,也可以作为域对象来使用,使用request保存的数据在一次请求范围内有效。
    Session代表的是一次会话,可以用于保存用户的私有的信息,也可以作为域对象使用,使用session保存的数据在一次会话范围有效
    Application:代表整个应用范围,使用这个对象保存的数据在整个web应用中都有效。
    Response是响应对象,代表的是从服务器向浏览器响应数据.
    Out:JSPWriter是用于向页面输出内容的对象
    Config:指的是ServletConfig用于JSP翻译成Servlet后 获得Servlet的配置的对象.
    Exception:在页面中设置isErrorPage=”true”,即可使用,是Throwable的引用.用来获得页面的错误信息。

66、说一下 jsp 的 4 种作用域

    4个JSP内置对象的作用域分别为:application、session、request、page

    名称            作用域
    application   在整个应用程序中有效
    session        在当前会话中有效
    request        在当前请求中有效
    page            在当前页面有效

67、session 和 cookie 有什么区别

    存放位置:session 保存在服务器,cookie 保存在客户端;
    存放的形式:session 是以对象的形式保存在服务器,cookie 以字符串的形式保存在客户端;
    用途:session 适合做客户的身份验证,cookie 适合保存用户的个人设置,爱好等;
    路径:session 不能区分路径,同一用户在访问一个网站期间,所有的 session 在任何一个地方都可以访问到;cookie 中如果设置了参数路径,那么同一个网站下的 cookie 互相访问不到;
    安全性:cookie 不是很安全,别人可以分析存放在本地的 cookie 并进行 cookie 欺骗,session 较 cookie 更安全一些;
    大小及数量限制:单个 cookie 在客户端的限制是 3K,就是说一个站点在客户端存放的 COOKIE 不能 3K。不同浏览器所含 cookie 的最大个数不同,一般 30 到 50 个;一般认为 session 没有大小限制。

68、说一下 session 的工作原理

    Session内容保存在服务器端的,通常是保存在内存中。客户端跟服务器端通过SessionId来关联。
    SessionId通常以Cookie的形式存储在客户端。每次HTTP请求,SessionId都会随着Cookie被传递到服务器端,这时就可以通过SessionId取到对应的信息,来判断这个请求来自于哪个客户端/用户。

    SessionId 负责标识客户端/用户
    HTTP 负责传递SessionId
    Cookie 负责保存SessionId
    服务器 负责保存Session内容

69、如果客户端禁止 cookie ,session 还能用吗

    不一定。session 需要借助 cookie 才能正常工作,如果客户端完全禁止 cookie,session 将失效,因为 session 是由应用服务器维持的一个服务端的存储空间,用户在连接服务器时,会由服务器生成唯一的 sesssion id,用该 session id 为标识来存取服务端的 session 空间。而 session id 存储在 cookie 中,用户提交页面时会将这个 session id 提交到服务端,来存取 session 数据.这一过程是不用开发人员干预的,所以一旦客户端禁用 cookie,那么 session 也会失效。

   可以通过URL传值、隐藏表单传递Session ID

70、spring mvc 和 struts 的区别是什么

    1、Struts2是类级别的拦截, 一个类对应一个request上下文,SpringMVC是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上SpringMVC就容易实现restful url,而struts2的架构实现起来要费劲,因为Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了。
    2、由上边原因,SpringMVC的方法之间基本上独立的,独享request response数据,请求数据通过参数获取,处理结果通过ModelMap交回给框架,方法之间不共享变量,而Struts2搞的就比较乱,虽然方法之间也是独立的,但其所有Action变量是共享的,这不会影响程序运行,却给我们编码 读程序时带来麻烦,每次来了请求就创建一个Action,一个Action对象对应一个request上下文。
    3、由于Struts2需要针对每个request进行封装,把request,session等servlet生命周期的变量封装成一个一个Map,供给每个Action使用,并保证线程安全,所以在原则上,是比较耗费内存的。
    4、拦截器实现机制上,Struts2有以自己的interceptor机制,SpringMVC用的是独立的AOP方式,这样导致Struts2的配置文件量还是比SpringMVC大。
    5、SpringMVC的入口是servlet,而Struts2是filter(这里要指出,filter和servlet是不同的。以前认为filter是servlet的一种特殊),这就导致了二者的机制不同,这里就牵涉到servlet和filter的区别了。
    6、SpringMVC集成了Ajax,使用非常方便,只需一个注解@ResponseBody就可以实现,然后直接返回响应文本即可,而Struts2拦截器集成了Ajax,在Action中处理时一般必须安装插件或者自己写代码集成进去,使用起来也相对不方便。
    7、SpringMVC验证支持JSR303,处理起来相对更加灵活方便,而Struts2验证比较繁琐,感觉太烦乱。
    8、Spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高(当然Struts2也可以通过不同的目录结构和相关配置做到SpringMVC一样的效果,但是需要xml配置的地方不少)。
    9、设计思想上,Struts2更加符合OOP的编程思想, SpringMVC就比较谨慎,在servlet上扩展。
    10、SpringMVC开发效率和性能高于Struts2。
    11、SpringMVC可以认为已经100%零配置。

71、如何避免 sql 注入

    1、采用预编译语句集:PreparedStatement
        sql注入只对sql语句的准备(编译)过程有破坏作用,而PreparedStatement已经准备好了,执行阶段只是把输入串作为数据处理,而不再对sql语句进行解析,准备,因此也就避免了sql注入问题.
    2、使用正则表达式过滤传入的参数
    3、#{} 这种取值是编译好SQL语句再取值,${} 这种是取值以后再去编译SQL语句,所以#{}方式能够很大程度防止sql注入。${}方式无法防止Sql注入。 

72、什么是 XSS 攻击,如何避免?

    XSS原称为CSS(Cross-Site Scripting),因为和层叠样式表(Cascading Style Sheets)重名,所以改称为XSS(X一般有未知的含义,还有扩展的含义)。XSS攻击涉及到三方:攻击者,用户,web server。用户是通过浏览器来访问web server上的网页,XSS攻击就是攻击者通过各种办法,在用户访问的网页中插入自己的脚本,让其在用户访问网页时在其浏览器中进行执行。攻击者通过插入的脚本的执行,来获得用户的信息,比如cookie,发送到攻击者自己的网站(跨站了)。所以称为跨站脚本攻击。XSS可以分为反射型XSS和持久性XSS,还有DOM Based XSS。(一句话,XSS就是在用户的浏览器中执行攻击者自己定制的脚本。)

    自己写 filter 拦截来实现,但要注意的时,在WEB.XML 中配置 filter 的时候,请将这个 filter 放在第一位.

    web.xml添加过滤器
        <!-- 解决xss漏洞 -->
        <filter>
            <filter-name>xssFilter</filter-name>
            <filter-class>com.quickly.exception.common.filter.XssFilter</filter-class>
        </filter>
        <!-- 解决xss漏洞 -->
        <filter-mapping>
            <filter-name>xssFilter</filter-name>
            <url-pattern>*</url-pattern>
        </filter-mapping>
        过滤器代码

    新建XssFilter类
        package com.quickly.exception.common.filter;
        import javax.servlet.*;
        import javax.servlet.http.HttpServletRequest;
        import java.io.IOException;
         
        /**
         * 作用:Xss过滤器
         **/
        public class XssFilter implements Filter {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
         
            }
         
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                //使用包装器
                XssFilterWrapper xssFilterWrapper=new XssFilterWrapper((HttpServletRequest) servletRequest);
                filterChain.doFilter(xssFilterWrapper,servletResponse);
            }
         
            @Override
            public void destroy() {
         
            }
        }

    过滤器包装器代码
        package com.quickly.exception.common.filter;
        import org.springframework.web.util.HtmlUtils;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletRequestWrapper;
        /**
         * 作用:防Xss过滤器[包装器]
         **/
        public class XssFilterWrapper extends HttpServletRequestWrapper {
            public XssFilterWrapper(HttpServletRequest request) {
                super(request);
            }
            /**
             * 对数组参数进行特殊字符过滤
             */
            @Override
            public String[] getParameterValues(String name) {
                if("content".equals(name)){//不想过滤的参数,此处content参数是 富文本内容
                    return super.getParameterValues(name);
                }
                String[] values = super.getParameterValues(name);
                String[] newValues = new String[values.length];
                for (int i = 0; i < values.length; i++) {
                    newValues[i] = HtmlUtils.htmlEscape(values[i]);//spring的HtmlUtils进行转义
                }
                return newValues;
            }
        }
    总结:
        主要是使用Java Web的过滤器,将所有的request请求参数修改(主要是把存在xss风险的标签转义,如:<script></script>),在转义时我没有自己实现替换与转义,是直接使用的spring自带的HtmlUtils类的htmlEscape方法转义的,方便很多

73、什么是 CSRF 攻击,如何避免?

    1. CSRF攻击是什么
        我们首先来认识一下CSRF。CSRF(Cross-site request forgery)也被称为 one-click attack或者 session riding,中文全称是叫跨站请求伪造。一般来说,攻击者通过伪造用户的浏览器的请求,向访问一个用户自己曾经认证访问过的网站发送出去,使目标网站接收并误以为是用户的真实操作而去执行命令。常用于盗取账号、转账、发送虚假消息等。攻击者利用网站对请求的验证漏洞而实现这样的攻击行为,网站能够确认请求来源于用户的浏览器,却不能验证请求是否源于用户的真实意愿下的操作行为。

    2.如何防范?
        1.验证 HTTP Referer 字段
            HTTP头中的Referer字段记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,而如果黑客要对其实施 CSRF攻击,他一般只能在他自己的网站构造请求。因此,可以通过验证Referer值来防御CSRF攻击。
        2.使用验证码
            关键操作页面加上验证码,后台收到请求后通过判断验证码可以防御CSRF。但这种方法对用户不太友好。
        3.在请求地址中添加token并验证
            CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于cookie中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有token或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于session之中,然后在每次请求时把token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。而对于 POST 请求来说,要在 form 的最后加上 <input type="hidden" name="csrftoken" value="tokenvalue"/>,这样就把token以参数的形式加入请求了。
        4.在HTTP 头中自定义属性并验证
            这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。

            AngularJS提供的CSRF方案:
            AngularJS提供了对CSRF防御的支持,当cookie中存在名为XSRF-TOKEN的cookie时,会在POST/PUT/DELETE请求中带上名为X-XSRF-TOKEN的http header。(For Angular, it expects the cookie named XSRF-TOKEN and will do POST/PUT/DELETE requests with X-XSRF-TOKEN header.)
            因此很容易就能采用上面的在HTTP头中带上token验证的方案来防御CSFR。
            如果要修改默认的名称,可以设置下面两项:
            $httpProvider.defaults.xsrfCookieName = 'csrftoken';
            $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';

    七、异常

74、throw 和 throws 的区别

    throws是用来声明一个方法可能抛出的所有异常信息,throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理。而throw则是指抛出的一个具体的异常类型。

75、final、finally、finalize 有什么区别

    final是java中的关键字,可用于修饰类,方法,变量。
        当修饰类时,表明这个类不可被继承。Java 中有一些核心类都被 final 修饰了,比如 String,System。当考虑到安全性原因时,可以将该类设计成 final。
        当修饰方法时,表明该方法不可被重写。一般是某些流程控制不希望被修改掉时,可以将这些方法声明成 final,比如 View 中的 measure(),requestFocus(),findViewById()。
        当修饰变量时,表明该变量为常量,不允许被重新赋值,因此声明成 final 的变量都需要显示的进行赋值,否则编译会报错。

    finally 是确保 try-catch 方式最后执行的一种机制,通常的用法都是在 finally 里进行一些资源的关闭,回收。比如 IO 流的关闭等等。
        建议最好不要利用 finally 来控制流程,也不要在 finally 中有返回值,否则很容易影响正常流程,导致流程结构特别杂乱。另外,有些特殊情况下,finally 中的代码并不会被执行到(程序退出、断电、try未执行等极端情况)

    finalize 是 Object 中的一个方法,是由垃圾收集器即将要回收该对象时会调用该方法,用户可在这里做一些最后的资源释放工作。
        我们无法保证 finalize 什么时候执行,执行是否符合预期,使用不当还会影响性能,导致程序死锁、挂起等问题。

76、try-catch-finally 中哪个部分可以省略

    try语句后面是可以省略catch语句的,但是必须有finally语句。也可以省略finally语句,但是必须要有catch语句。也就是说try语句后面必须要有一个别的语句跟在后面。
        try-catch
        try-finally
        try-catch-finally

    切记:catch和finally语句不能同时省略!!!

77、try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

    会执行,在return 前执行

    1、不管有没有异常,finally中的代码都会执行
    2、当try、catch中有return时,finally中的代码依然会继续执行
    3、finally是在return后面的表达式运算之后执行的,此时并没有返回运算之后的值,而是把值保存起来,不管finally对该值做任何的改变,返回的值都不会改变,依然返回保存起来的值。也就是说方法的返回值是在finally运算之前就确定了的。
    4、如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。
    5、finally代码中最好不要包含return,程序会提前退出,也就是说返回的值不是try或catch中的值

78、常见的异常类有哪些

    算术异常类:ArithmeticExecption
    空指针异常类:NullPointerException
    类型强制转换异常:ClassCastException
    数组负下标异常:NegativeArrayException
    数组下标越界异常:ArrayIndexOutOfBoundsException
    违背安全原则异常:SecturityException
    文件已结束异常:EOFException
    文件未找到异常:FileNotFoundException
    字符串转换为数字异常:NumberFormatException
    操作数据库异常:SQLException
    输入输出异常:IOException
    方法未找到异常:NoSuchMethodException

    八、网络

79、http 响应码 301 和 302 代表的是什么?有什么区别?

    301,302 都是HTTP状态的编码,都代表着某个URL发生了转移,不同之处在于: 
        301 redirect: 301 代表永久性转移(Permanently Moved)。
        302 redirect: 302 代表暂时性转移(Temporarily Moved )。 --尽量使用301跳转!

    详细来说,301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。他们的不同在于。301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。

80、forward 和 redirect 的区别?

    是servlet种的两种主要的跳转方式。forward又叫转发,redirect叫做重定向

    1.从地址栏显示来说:
        1)forword是服务器内部的重定向,服务器直接访问目标地址的 url网址,把里面的东西读取出来,但是客户端并不知道,因此用forward的话,客户端浏览器的网址是不会发生变化的。
        2)redirect是服务器根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,所以地址栏显示的是新的地址。
    2.从数据共享来说:
        1)由于在整个定向的过程中用的是同一个request,因此forward会将request的信息带到被重定向的jsp或者servlet中使用。即可以共享数据
        2)redirect不能共享
    3.从运用的地方来说
        1)forword 一般用于用户登录的时候,根据角色转发到相应的模块
        2)redirect一般用于用户注销登录时返回主页面或者跳转到其他网站
    4.从效率来说:
        1)forword效率高,而redirect效率低
    5.从本质来说:
        forword转发是服务器上的行为,而redirect重定向是客户端的行为
    6.从请求的次数来说:
        forword只有一次请求;而redirect有两次请求

81、简述 tcp 和 udp的区别?

    TCP与UDP区别总结:
        1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
        2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
        3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
        4.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
        5、TCP对系统资源要求较多,UDP对系统资源要求较少。

82、tcp 为什么要三次握手,两次不行吗?为什么?

    为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤
    如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认

83、说一下 tcp 粘包是怎么产生的? 

    (1)发送方原因
      我们知道,TCP默认会使用Nagle算法。而Nagle算法主要做两件事:1)只有上一个分组得到确认,才会发送下一个分组;2)收集多个小分组,在一个确认到来时一起发送。
      所以,正是Nagle算法造成了发送方有可能造成粘包现象。

    (2)接收方原因
      TCP接收到分组时,并不会立刻送至应用层处理,或者说,应用层并不一定会立即处理;实际上,TCP将收到的分组保存至接收缓存里,然后应用程序主动从缓存里读收到的分组。这样一来,如果TCP接收分组的速度大于应用程序读分组的速度,多个包就会被存至缓存,应用程序读时,就会读到多个首尾相接粘到一起的包

84、OSI 的七层模型都有哪些?

    模型把网络通信的工作分为7层。1至4层被认为是低层,这些层与数据移动密切相关。5至7层是高层,包含应用程序级的数据。每一层负责一项具体的工作,然后把数据传送到下一层。由低到高具体分为:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
    
    第7层应用层—直接对应用程序提供服务,应用程序可以变化,但要包括电子消息传输
    第6层表示层—格式化数据,以便为应用程序提供通用接口。这可以包括加密服务
    第5层会话层—在两个节点之间建立端连接。此服务包括建立连接是以全双工还是以半双工的方式进行设置,尽管可以在层4中处理双工方式
    第4层传输层—常规数据递送-面向连接或无连接。包括全双工或半双工、流控制和错误恢复服务 
    第3层网络层—本层通过寻址来建立两个节点之间的连接,它包括通过互连网络来路由和中继数据
    第2层数据链路层—在此层将数据分帧,并处理流控制。本层指定拓扑结构并提供硬件寻址
    第1层物理层—原始比特流的传输电子信号传输和硬件接口数据发送时,从第七层传到第一层,接受方则相反。

85、get 和 post 请求有哪些区别?

    (1)post更安全(不会作为url的一部分,不会被缓存、保存在服务器日志、以及浏览器浏览记录中) 
    (2)post发送的数据更大(get有url长度限制) 
    (3)post能发送更多的数据类型(get只能发送ASCII字符) 
    (4)post比get慢 
    (5)post用于修改和写入数据,get一般用于搜索排序和筛选之类的操作(淘宝,支付宝的搜索查询都是get提交),目的是资源的获取,读取数据 

86、如何实现跨域?

    JSONP
    CORS
    代理

    图片ping或script标签跨域
    window.name+iframe
    window.postMessage()
    修改document.domain跨子域。前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域,所以只能跨子域
    WebSocket

87、说一下 JSONP 实现原理?

    jsonp通过在服务端用一个回调函数把数据一起包裹起来并返回给客户端(jsonp名字就是这样来的json padding),然后客户端写好回调(处理数据),并动态创建一个script节点,通过src属性来调用服务端返回的回调函数。

    九、设计模式

88、说一下你熟悉的设计模式?

    Java中23种设计模式
        创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。
        结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
        行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
        
    单例模式:保证一个类仅有一个实例,并提供一个访问他的全局访问点例如框架中的数据库连接
    策略模式:针对一组算法,将每一个算法封装到具有共同接口的独立的类中,例如进入个人主页时,根据浏览者的不同,给予不同的显示与操作。
    注册模式:提供了在程序中有条理的存放并管理一组全局对象 (object),例如ZF框架中的Zend_Registry::set。
    适配器模式:将不同接口适配成统一的API接口,例如数据操作有mysql、mysqli、pdo等,可利用适配器模式统一接口
    观察者模式:一个对象通过添加一个方法使本身变得可观察。当可观察的对象更改时,它会将消息发送到已注册的观察者。例如实现实现消息推送
    装饰器模式:不修改原类代码和继承的情况下动态扩展类的功能,例如框架的每个Controller文件会提供before和after方法
    迭代器模式:提供一个方法顺序访问一个聚合对象中各个元素
    代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。
    工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
    模板方法模式:提供一个抽象类,将部分逻辑以具体方法或构造器的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法(多态实现),从而实现不同的业务逻辑。

89、简单工厂和抽象工厂有什么区别

    区别:
        简单工厂 :用来生产同一等级结构中的任意产品。(对于增加新的产品,无能为力)
        工厂模式 :用来生产同一等级结构中的固定产品。(支持增加任意产品) 
        抽象工厂 :用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)

    优点:
        简单工厂:客户端可以免除直接创建产品对象的责任,而仅仅是“消费”产品。简单工厂模式通过这种做法实现了对责任的分割。
        工厂方法:允许系统在不修改具体工厂角色的情况下引进新产品。 
        抽象工厂:向客户端提供一个接口,使得客户端在不必指定产品具体类型的情况下,创建多个产品族中的产品对象 

    十、Spring/Spring MVC

90、为什么要使用 spring?

    1.方便解耦,便于开发(Spring就是一个大工厂,可以将所有对象的创建和依赖关系维护都交给spring管理)
    2.spring支持aop编程(spring提供面向切面编程,可以很方便的实现对程序进行权限拦截和运行监控等功能)
    3.声明式事务的支持(通过配置就完成对事务的支持,不需要手动编程)
    4.方便程序的测试,spring 对junit4支持,可以通过注解方便的测试spring 程序
    5.方便集成各种优秀的框架
    6.降低javaEE API的使用难度(Spring 对javaEE开发中非常难用的一些API 例如JDBC,javaMail,远程调用等,都提供了封装,是这些API应用难度大大降低)

91、解释一下什么是 aop

    AOP是面向切面编程,也就是说面向某个功能模块编程,典型的应用就是Spring的声明式事务

    AOP使用
        声明式事务、日记记录、权限控制

92、解释一下什么是 ioc

    Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
    DI—Dependency Injection,即“依赖注入”
    IoC和DI是什么关系呢?其实它们是同一个概念的不同角度描述。

93、spring 有哪些主要模块?

    Spring有七大功能模块,分别是Spring Core,AOP,ORM,DAO,MVC,WEB,Context。
    
    1,Spring Core
        Core模块是Spring的核心类库,Spring的所有功能都依赖于该类库,Core主要实现IOC功能,Spring的所有功能都是借助IOC实现的。
    2,AOP
        AOP模块是Spring的AOP库,提供了AOP(拦截器)机制,并提供常用的拦截器,供用户自定义和配置。
    3,ORM
        Spring的ORM模块提供对常用的ORM框架的管理和辅助支持,Spring支持常用的Hibernate,ibatis,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框架更加简洁和方便。

94、spring 常用的注入方式有哪些?

    Spring常用的三种注入方式
        构造方法注入,setter注入,基于注解的注入。

95、spring 中的 bean 是线程安全的吗?

    Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究。

    Spring 的 bean 作用域(scope)类型
        1、singleton:单例,默认作用域。
        2、prototype:原型,每次创建一个新对象。
        3、request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。
        4、session:会话,同一个会话共享一个实例,不同会话使用不用的实例。
        5、global-session:全局会话,所有会话共享一个实例。

    线程安全这个问题,要从单例与原型Bean分别进行说明。
    原型Bean
        对于原型Bean,每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。
    单例Bean
        对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。对于有状态的bean,Spring官方提供的bean,一般提供了通过ThreadLocal去解决线程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等。

    使用ThreadLocal的好处
        使得多线程场景下,多个线程对这个单例Bean的成员变量并不存在资源的竞争,因为ThreadLocal为每个线程保存线程私有的数据。这是一种以空间换时间的方式。当然也可以通过加锁的方法来解决线程安全,这种以时间换空间的场景在高并发场景下显然是不实际的。

96、spring 支持几种 bean 的作用域?

    Spring支持如下5种作用域:
        singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
        prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
        request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
        session:对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
        globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效

97、spring 自动装配 bean 有哪些方式

    Spring支持5种自动装配模式,如下:
        no    ——默认情况下,不自动装配,通过“ref”attribute手动设定。
        buName    ——根据Property的Name自动装配,如果一个bean的name,和另一个bean中的Property的name相同,则自动装配这个bean到Property中。
        byType    ——根据Property的数据类型(Type)自动装配,如果一个bean的数据类型,兼容另一个bean中Property的数据类型,则自动装配。
        constructor    ——根据构造函数参数的数据类型,进行byType模式的自动装配。
        autodetect    ——如果发现默认的构造函数,用constructor模式,否则,用byType模式。

98、spring 事务实现方式有哪些?

    Spring提供了编程式事务和声明式事务两种实现方式,
        编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。

99、说一下 spring 的事务隔离?

    事务的传播特性,Spring定义了7中传播行为:
        (1)propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。
        (2)propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
        (3)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
        (4)propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
        (5)propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
        (6)propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
        (7)propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。

    事务的隔离级别
        (1)default 使用后端数据库默认的隔离级别
        (2)read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。可能导致脏读、幻影读或不可重复读。
        (3)read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。可防止脏读,但幻影读和不可重复读仍可能会发生。
        (4)repeatable read:对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。
        (5)serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读
    
    Spring中,默认使用DEFAULT,即当前连接池中使用的数据库的隔离级别。Oracle默认的隔离级别为:READ_COMMITTED,Mysql默认的隔离级别为:REPEATABLE_READ

    (5)脏读、不可重复读、幻象读概念说明:
        a.脏读:指当一个事务正字访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
        b.不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
        c.幻象读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)

100、说一下 spring mvc 运行流程

    spring mvc 先将请求发送给 DispatcherServlet。
    DispatcherServlet 查询一个或多个 HandlerMapping,找到处理请求的 Controller。
    DispatcherServlet 再把请求提交到对应的 Controller。
    Controller 进行业务逻辑处理后,会返回一个ModelAndView。
    DispatcherServlet 查询一个或多个 ViewResolver 视图解析器,找到 ModelAndView 对象指定的视图对象。
    视图对象负责渲染返回给客户端。

101、spring mvc 有哪些组件?

五大核心组件
  1.DispatcherServlet  请求入口
  2.HandlerMapping    请求派发,负责请求和控制器建立一一对应的关系
  3.Controller       处理器
  4.ModelAndView     封装模型信息和视图信息
  5.ViewResolver    视图处理器,定位页面

102、@RequestMapping 的作用是什么?

    @RequestMapping是一个用来处理请求地址映射的注解,可用于类或者方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

103、@Autowired 的作用是什么?

    作用
        @Autowired表示被修饰的类需要注入对象,spring会扫描所有被@Autowired标注的类,然后根据 类型 在ioc容器中找到匹配的类注入。

    @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
        @Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
        @Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

                           Java常见面试题 Java面试必看 (二)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值