2020最新Java面试题(常见面试题及答案汇总)

Java基础篇

点此原文连接!

1. JDK 和 JRE 的区别是什么?

  • JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
  • JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。

总结: JDK 包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了不少 java 程序调试和分析的工具。简单来讲:若是你须要运行 java 程序,只需安装 JRE 就能够了,若是你须要编写 java 程序,就须要安装 JDK。java

2. Java中有哪些数据类型?

Java中数据类型分为基本数据类型和引用数据类型2种web

  • 基本类型:byte(默认值0,占1字节)、short(默认值0,占2字节)、int(默认值0,占4字节)、long(默认值0,占8字节)、float(默认值0.0,占4字节)、double(默认值0.0,占8字节)、char(默认值\u0000,占2字节)、boolean(默认值false)
  • 引用类型:类(默认值null)、接口(默认值null)、数组(默认值null)

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

基本类型和引用类型比较,== 的做用效果是不一样的。算法

  • 基本类型:比较的是值是否相同设计模式

  • 引用类型:比较的是引用是否相同数组

    int x = 10;
    int y = 10;
    String a = "panda";
    String b = "panda";
    String c = new String("panda");
    // true 基本类型比较值是否相同
    System.out.println(x == y);
    // true 引用类型比较引用是否相同,这里引用相同
    System.out.println(a == b);
    // false 引用不一样
    System.out.println(a == c);
    // true 引用不一样,String重写了equals,使其用值比较
    System.out.println(a.equals(c));
    复制代码

    12345678910111213缓存

equals 本质上就是 ==,Object类中定义的 equals 方法以下安全

public boolean equals(Object obj) {
        return (this == obj);
    }
123
复制代码

总结:== 对于基本类型比较的是值,对于引用类型比较的是引用;而 equals 默认状况下是引用比较,只是不少类重写了 equals 方法,好比 String、Integer 等把它变成了值比较,因此通常状况下 equals 比较的是值是否相等。服务器

4. 两个对象的 hashCode() 相同,则 equals() 也必定为 true 正确吗?

不正确,两个对象的 hashCode() 相同,equals() 不必定 true。好比在 map 中,hashCode() 相等,只能说明这两个键值对的哈希值相同,不表明这两个键值对相等。markdown

String str1 = "通话";
	String str2 = "重地";
	// str1: 1179395 | str2: 1179395
	System.out.println(String.format("str1: %d | str2: %d",str1.hashCode(),str2.hashCode()));
	// false
	System.out.println(str1.equals(str2));
123456
复制代码

5. Java 中 final 关键字的做用是什么?

  • final 修饰的类叫最终类,不能被继承
  • final 修饰的方法叫最终方法,不能被重写,但能够被继承
  • final 修饰的变量叫常量,必须初始化,初始化以后值不能被修改

6. String、StringBuffer、StringBuilder 的区别是什么?

String 是字符串常量,每次操做都会生产新的对象,适用于少许字符串操做的状况;StringBuffer、StringBuilder 是字符串变量,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,因此在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。数据结构

7. String str=“donkey” 与 String str=new String(“donkey”) 同样吗?

不同,由于内存的分配方式不同。String str=“donkey”,java 虚拟机会将其分配到常量池中;而 String str=new String(“donkey”) 则会被分到堆内存中。

8. 字符串如何反转?

使用 StringBuilder 或者 stringBuffer 的 reverse() 方法

StringBuffer stringBuffer = new StringBuffer();
	stringBuffer.append("abcdefg");
	System.out.println(stringBuffer.reverse()); // gfedcba
	StringBuilder stringBuilder = new StringBuilder();
	stringBuilder.append("abcdefg");
	System.out.println(stringBuilder.reverse()); // gfedcba
123456
复制代码

9. String 类中经常使用方法都有哪些?

String str = " app le ";
	// indexOf(): 返回指定字符的索引
	System.out.println(str.indexOf("a")); // 1
	// charAt(): 返回指定索引处的字符
	System.out.println(str.charAt(5)); // l
	// replace(): 字符串替换
	System.out.println(str.replace("pp", "cc")); // " acc le "
	// trim(): 去除字符串两端空白
	System.out.println(str.trim()); // "app le"
	// split(): 分割字符串,返回一个分割后的字符串数组
	String[] arr = str.split(" ");
	// getBytes(): 返回字符串的 byte 类型数组
	byte[] bytes = str.getBytes();
	// length(): 返回字符串长度
	System.out.println(str.length()); // 8
	// toLowerCase(): 将字符串转成小写字母
	System.out.println(str.toLowerCase()); // " app le "
	// toUpperCase(): 将字符串转成大写字符
	System.out.println(str.toUpperCase()); // " APP LE "
	// substring(): 截取字符串
	System.out.println(str.substring(2)); // "pp le "
	// equals(): 字符串比较
	System.out.println("apple".equals(str)); // false
1234567891011121314151617181920212223
复制代码

10. 抽象类是什么?

拥有抽象方法(指没有方法体的方法,同时抽象方法还必须使用关键字abstract 作修饰)的类就是抽象类,抽象类要使用 abstract 关键字声明。

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

不须要,抽象类不必定非要有抽象方法,以下代码能够正常运行

public abstract class elephant {
    String str = "apple";
    public void test01(){
        System.out.println("aaaa");
    }
}
123456
复制代码

12. 普通类和抽象类的区别是什么?

  • 普通类不能包含抽象方法,抽象类能够有抽象方法
  • 普通类能够直接实例化,抽象类不能直接实例化

13. 抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其余类继承的,若是定义为 final 该类就不能被继承,这样彼此就会产生矛盾,因此 final 不能修饰抽象类。

14. 接口是什么?

接口能够理解为一种特殊的类,里面所有是由全局常量和公共的抽象方法所组成。接口是解决Java没法使用多继承的一种手段,可是接口在实际中更多的做用是制定标准。

15. 接口和抽象类的区别是什么?

  • 接口必须使用 implements 来实现接口;抽象类的子类使用 extends 来继承
  • 接口不能有构造函数;抽象类能够有构造函数
  • 一个类只能继承一个抽象类,但能够实现多个接口
  • 抽象类中成员变量默认 default,可在子类中被从新定义,也可被从新赋值,抽象方法被 abstract 修饰,不能被 private、static、synchronized 和 native 等修饰;接口中成员变量默认为 public static final 修饰,必须赋初值,不能被修改,其全部的成员方法默认使用 public abstract 修饰的

16. Java 中 IO 流分几种?

  • 按功能分:输入流(input)、输出流(output)
  • 按类型分:字节流、字符流

字节流和字符流的区别:字节流按 8 位传输,以字节为单位输入输出数据;字符流按 16 位传输,以字符为单位输入输出数据。

17. BIO、NIO、AIO 有什么区别?

  • BIO:同步阻塞 IO,就是咱们日常使用的传统 IO,服务器的实现模式是一个请求链接一个线程,并发处理能力低,可能形成没必要要的线程开销,严重的还将致使服务器内存溢出。
  • NIO:同步非阻塞 IO,是传统 IO 的升级,服务器的实现模式是多个请求一个线程,即请求会注册到多路复用器Selector上,多路复用器轮询到链接有IO请求时才启动一个线程处理。
  • AIO:异步非阻塞 IO,是 NIO 的升级,也叫 NIO2,服务器的实现模式为多个有效请求一个线程,客户端的IO请求都是由OS先完成再通知服务器应用去启动线程处理(回调)。

18. Files 的经常使用方法都有哪些?

// Files.exists():检测文件路径是否存在
	Path path1 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test");
	System.out.println(Files.exists(path1, new LinkOption[]{LinkOption.NOFOLLOW_LINKS})); // true
	// Files.createFile():建立文件
	Path path2 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\a.txt");
	try {
	    Path newFilw = Files.createFile(path2);
	} catch (FileAlreadyExistsException e){
	    System.out.println("exists");
	} catch (IOException e) {
	    System.out.println("other wrong");
	}
	// Files.createDirectory():建立文件夹
	Path path3 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\newDirectory");
	try {
	    Path newDirectory = Files.createDirectory(path3);
	} catch (FileAlreadyExistsException e) {
	    System.out.println("exists");
	} catch (IOException e){
	    System.out.println("other wrong");
	}
	// Files.delete():删除一个文件或目录
	try {
	    Files.delete(path2);
	} catch (IOException e) {
	    e.printStackTrace();
	}
	// Files.copy():复制文件
	Path source = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\b.txt");
	Path target = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\newb.txt");
	try {
	    Files.copy(source,target);
	} catch (FileAlreadyExistsException e) {
	    System.out.println("targetFile already exists");
	} catch (IOException e){
	    System.out.println("other wrong");
	}
	// Files.move():移动文件
	Path source1 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\b.txt");
	Path target1 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\newDirectory\\a.txt");
	try {
	    Files.move(source1,target1);
	} catch (FileAlreadyExistsException e) {
	    System.out.println("targetFile1 already exists");
	} catch (IOException e){
	    System.out.println("other wrong");
	}
	// Files.size():查看文件个数
	// Files.read():读取文件
	// Files.write():写入文件
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
复制代码

Java 容器篇

1. Java 中都有哪些容器?

在这里插入图片描述

2. Collection 和 Collections 的区别是什么?

  • java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操做的通用接口方法。Collection 接口在 Java 类库中有不少具体的实现。Collection 接口的意义是为各类具体的集合提供了最大化的统一操做方式,其直接继承接口有 List 与 Set。
  • Collections 则是集合类的一个工具类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各类操做。

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

在这里插入图片描述

4. HashMap 和 Hashtable 的区别是什么?

  • hashMap 去除了 HashTable 的 contains() 方法,可是加上了 containsValue() 和 containsKey() 方法。
  • hashTable 是线程同步的,而 HashMap 是非同步的,HashMap 效率上比 hashTable 要高。
  • hashMap 容许空键空值,而 hashTable 不容许。

5. 如何决定使用 HashMap 仍是 TreeMap?

在 Map 中作插入、删除和定位元素这类操做,HashMap 是最好的选择。假如你须要对一个有序的 key 集合进行遍历,TreeMap 是更好的选择。

6. HashMap 的实现原理是什么?

HashMap概述: HashMap 是基于哈希表的Map接口的非同步实现。此实现提供全部可选的映射操做,并容许使用 null 值和 null 键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

HashMap的数据结构: HashMap 其实是一个“链表散列”的数据结构,即数组和链表的结合体。

当咱们往 HashMap 中 put 元素时,首先根据 key 的 hashcode 从新计算 hash 值,根据 hash 值获得这个元素在数组中的位置(下标),若是该数组在该位置上已经存放了其余元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最早加入的放入链尾。若是数组中该位置没有元素,就直接将该元素放到数组的该位置上。

Jdk1.8 中对 HashMap 的实现作了优化,当链表中的节点数据超过八个以后,该链表会转为红黑树来提升查询效率,从原来的 O(n) 到 O(logn)。

7. HashSet 的实现原理是什么?

HashSet 实现 Set 接口,由哈希表(其实是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变;此类容许使用 null 元素;HashSet 中不容许有重复元素。

HashSet 是基于 HashMap 实现的,HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是统一的一个 private static final Object PRESENT = new Object()。HashSet跟HashMap同样,都是一个存放链表的数组。

8. ArrayList 和 LinkedList 的区别是什么?

ArrayList 底层基于动态数组,随机访问元素效率高,向集合尾部添加元素效率高,删除或者在其余位置添加元素效率低(须要移动数组);LinkedList 基于链表的动态数组,数据添加和删除效率高,只须要改变指针指向便可,可是访问数据的平均效率低,须要对链表进行遍历。

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

// List 转 数组
	String[] strArr = {"apple","pear","banana","peach"};
	List<String> list = Arrays.asList(strArr);
	// 数组 转 List
	String[] arr = (String[]) list.toArray();
12345
复制代码

10. ArrayList 和 Vector 的区别是什么?

  • Vector 是同步的,线程安全,而 ArrayList 不是。所以,ArrayList 的性能要高于Vector,若是你想在迭代的时候对列表进行改变,应该使用 CopyOnWriteArrayList。
  • 超过初始容量时,ArrayList 扩容1.5倍,Vector 扩容2倍。

11. Array 和 ArrayList 的区别是什么?

  • Array 能够容纳基本类型和对象,而 ArrayList 只能容纳对象。
  • Array 是指定大小的,而 ArrayList 初始化大小是固定的。
  • Array 没有提供 ArrayList 那么多功能,好比addAll、removeAll和iterator等。

12. 在 Queue 中 poll() 和 remove() 的区别是什么?

Queue 中 remove() 和 poll() 都是用来从队列头部删除一个元素,在队列元素为空的状况下,remove() 方法会抛出 NoSuchElementException 异常,poll() 方法只会返回 null。

13. 线程安全的集合类有哪些?

  • vector:就比 ArrayList 多了个同步化机制(线程安全),由于效率较低,如今已经不太建议使用
  • statck:堆栈类,先进后出
  • hashtable:就比 HashMap 多了个线程安全
  • enumeration:枚举,至关于迭代器

14. 迭代器 Iterator 是什么?

迭代器是一种设计模式,它是一个对象,它能够遍历并选择序列中的对象,而开发人员不须要了解该序列的底层结构。迭代器一般被称为“轻量级”对象,由于建立它的代价小。

15. Iterator 怎么使用?有什么特色?

  • Iterator 功能比较简单,而且只能单向移动

  • 使用方法 iterator() 要求容器返回一个 Iterator。第一次调用 Iterator 的 next() 方法时,它返回序列的第一个元素。注意:iterator() 方法是 java.lang.Iterable 接口,被 Collection 继承。

  • 使用 hasNext() 检查序列中是否还有元素

  • 使用 next() 得到序列中的下一个元素

  • 使用 remove() 将迭代器新返回的元素删除

  • Iterator 是 Java 迭代器最简单的实现,为 List 设计的 ListIterator 具备更多的功能,它能够从两个方向遍历 List,也能够从 List 中插入和删除元素。

    public static void main(String[] args) {
        // List
        ArrayList<String> list = new ArrayList<>();
        list.add("apple");
        list.add("pear");
        list.add("banana");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String s = iterator.next();
            if ("apple".equals(s)){
                iterator.remove();
            }
        }
        list.forEach(item -> System.out.println(item));
    
        // Map<key,value>
        Map<String,String> map=new HashMap<>();
        map.put("pig","猪");
        map.put("cat","猫");
        map.put("dog","狗");
        Iterator<String> iterator1 = map.keySet().iterator();
        Iterator<String> iterator2 = map.values().iterator();
        while (iterator1.hasNext()){
            System.out.println(iterator1.next());
        }
        while (iterator2.hasNext()){
            System.out.println(iterator2.next());
        }
    }
    复制代码

    1234567891011121314151617181920212223242526272829

16. Iterator 和 ListIterator 的区别是什么?

  • Iterator 可用来遍历 Set 和 List 集合,可是 ListIterator 只能用来遍历 List。
  • Iterator 对集合只能是前向遍历,ListIterator 既能够前向也能够后向。
  • ListIterator 实现了 Iterator 接口,并包含其余的功能,好比:增长元素,替换元素,获取前一个和后一个元素的索引等等。

Java多线程

1. 并行和并发的区别是什么?

  • 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生
  • 并行是在不一样实体上的多个事件,并发是在同一实体上的多个事件
  • 在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如 hadoop 分布式集群

普通解释:

  • 并发:交替作不一样事情的能力
  • 并行:同时作不一样事情的能力

专业术语:

  • 并发:不一样的代码块交替执行
  • 并行:不一样的代码块同时执行

2. 线程和进程的区别是什么?

进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程当中拥有独立的内存单元,而多个线程共享内存资源,减小切换次数,从而效率更高。线程是进程的一个实体,是 cpu 调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间能够并发执行。

3. 守护线程是什么?

守护线程(即 daemon thread),是个服务线程,准确地来讲就是服务其余的线程。它可以自我结束。若是 JVM 中没有一个正在运行的非守护线程,这个时候,JVM 会退出。JVM 中的垃圾回收线程就是典型的守护线程,若是说没有守护线程,JVM 就永远不会退出了

4. 建立线程有哪几种方式?

① 继承 Thread 类建立线程

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("run task");
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
123456789101112
复制代码

② 实现 Runnable 接口建立线程

public class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println("run task");
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread);
        thread.start();
    }
}
12345678910111213
复制代码

③ 经过 Callable 和 Future 建立线程

public class MyThread implements Callable {
    @Override
    public Object call() {
        System.out.println("run!");
        return "run task success";
    }
}
public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask<String>(myThread);
        Thread thread = new Thread(futureTask);
        thread.start();
        // 得到子线程执行结束后的返回值
        System.out.println(futureTask.get()); // run task success
    }
}
1234567891011121314151617
复制代码

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

  • Runnable 接口中的 run() 方法的返回值是 void,它作的事情只是纯粹地去执行run()方法中的代码
  • Callable 接口中的 call() 方法是有返回值的,是一个泛型,和 Future、FutureTask 配合能够用来获取异步执行的结果

6. 线程有哪些状态?

线程一般都有五种状态,建立、就绪、运行、阻塞和死亡。

  • 建立状态:在生成线程对象,并无调用该对象的start方法,这时线程处于建立状态。
  • 就绪状态:当调用了线程对象的 start 方法以后,该线程就进入了就绪状态,可是此时线程调度程序尚未把该线程设置为当前线程,此时处于就绪状态。在线程运行以后,从等待或者睡眠中回来以后,也会处于就绪状态。
  • 运行状态:线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行 run 函数当中的代码。
  • 阻塞状态:线程正在运行的时候,被暂停,一般是为了等待某个事件的发生(好比说某项资源就绪)以后再继续运行。sleep,suspend,wait 等方法均可以致使线程阻塞。
  • 死亡状态:若是一个线程的 run 方法执行结束或者调用 stop 方法后,该线程就会死亡。对于已经死亡的线程,没法再使用 start 方法令其进入就绪。

7. sleep() 和 wait() 有什么区别?

  • sleep():是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其余线程,等到休眠时间结束后,线程进入就绪状态和其余线程一块儿竞争cpu的执行时间。由于 sleep() 是 static 静态的方法,他不能改变对象的锁,当一个 synchronized 块中调用了 sleep() 方法,线程虽然进入休眠,可是对象的锁没有被释放,其余线程依然没法访问这个对象。
  • wait():是 Object 类的方法,当一个线程执行到 wait 方法时,它就进入到一个和该对象相关的等待池,同时释放对象的锁,使其余线程可以访问,能够经过 notify,notifyAll 方法来唤醒等待的线程。

8. notify() 和 notifyAll() 有什么区别?

若是线程调用了对象的 wait() 方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的 notifyAll() 方法(唤醒全部 wait 线程)或 notify() 方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了 notify 后只有一个线程会由等待池进入锁池,而 notifyAll 会将该对象等待池内的全部线程移动到锁池中,等待锁竞争。

优先级高的线程竞争到对象锁的几率大,倘若某线程没有竞争到该对象锁,它还会留在锁池中,惟有线程再次调用 wait() 方法,它才会从新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

9. run() 和 start() 有什么区别?

每一个线程都是经过某个特定 Thread 对象所对应的方法 run() 来完成其操做的,方法 run() 称为线程体。经过调用 Thread 类的 start() 方法来启动一个线程。

  • start() 方法来启动一个线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,能够直接继续执行下面的代码; 这时此线程是处于就绪状态, 并无运行。 而后经过此 Thread 类调用方法 run() 来完成其运行状态, 这里方法 run() 称为线程体,它包含了要执行的这个线程的内容,run 方法运行结束, 此线程终止。而后CPU再调度其它线程。
  • run() 方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 若是直接调用 run(),其实就至关因而调用了一个普通函数而已,直接调用 run() 方法必须等待 run() 方法执行完毕才能执行下面的代码,因此执行路径仍是只有一条,根本就没有线程的特征,因此在多线程执行时要使用 start() 方法而不是 run() 方法。

10. 建立线程池有哪几种方式?

  • newFixedThreadPool(int nThreads):建立一个固定长度的线程池,每当提交一个任务就建立一个线程,直到达到线程池的最大数量,这时线程规模将再也不变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
  • newCachedThreadPool():建立一个可缓存的线程池,若是线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增长时,则能够自动添加新线程,线程池的规模不存在任何限制。
  • newSingleThreadExecutor():这是一个单线程的 Executor,它建立单个工做线程来执行任务,若是这个线程异常结束,会建立一个新的来替代它;它的特色是能确保依照任务在队列中的顺序来串行执行。
  • newScheduledThreadPool(int corePoolSize):建立一个固定长度的线程池,并且以延迟或定时的方式来执行任务,相似于Timer。

11. 线程池都有哪些状态?

线程池有5种状态:Running、ShutDown、Stop、Tidying、Terminated。

  • Running:这是最正常的状态,接受新的任务,处理等待队列中的任务。线程池的初始化状态是Running。线程池被一旦被建立,就处于 Running 状态,而且线程池中的任务数为 0
  • ShutDown:不接受新的任务提交,可是会继续处理等待队列中的任务。调用线程池的 shutdown() 方法时,线程池由Running -> ShutDown。
  • Stop:不接受新的任务提交,再也不处理等待队列中的任务,中断正在执行任务的线程。调用线程池的shutdownNow() 方法时,线程池由 (Running or ShutDown) -> Stop。
  • Tidying:全部的任务都销毁了,workCount 为 0,线程池的状态在转换为 Tidying 状态时,会执行钩子方法 terminated()。由于 terminated() 在 ThreadPoolExecutor 类中是空的,因此用户想在线程池变为 Tidying 时进行相应的处理;能够经过重载 terminated() 函数来实现。 当线程池在 ShutDown 状态下,阻塞队列为空而且线程池中执行的任务也为空时,就会由 ShutDown -> Tidying。当线程池在Stop 状态下,线程池中执行的任务为空时,就会由Stop -> Tidying。
  • Terminated:线程池处在 Tidying 状态时,执行完 terminated() 以后,就会由 Tidying -> Terminated。

线程池各个状态切换框架图:
在这里插入图片描述

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

  • 接收的参数不同

    submit(Runnable task) submit(Runnable task,T result) submit(Callable task) execute(Runnable command) 1234

  • submit() 有返回值,而 execute() 没有。用到返回值的例子,好比说我有不少个作 validation 的task,我但愿全部的 task 执行完,而后每一个 task 告诉我它的执行结果,是成功仍是失败,若是是失败,缘由是什么。

  • submit()方便 Exception 处理,若是你在你的 task 里会抛出 checked 或者 unchecked exception,而你又但愿外面的调用者可以感知这些 exception 并作出及时的处理,那么就须要用到submit,经过捕获 Future.get 抛出的异常。

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

线程安全在三个方面体现:

  • 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操做(atomic,synchronized)
  • 可见性:一个线程对主内存的修改能够及时地被其余线程看到(synchronized,volatile)
  • 有序性:一个线程观察其余线程中的指令执行顺序,因为指令重排序,该观察结果通常杂乱无序(happens-before 原则)

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

在 Java 中,锁共有 4 种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争状况逐渐升级。锁能够升级但不能降级。

锁升级的图示过程:
在这里插入图片描述

15. 什么是死锁?

死锁是指两个或两个以上的进程在执行过程当中,因为竞争资源或者因为彼此通讯而形成的一种阻塞的现象,若无外力做用,它们都将没法推动下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操做系统层面的一个错误,是进程死锁的简称,最先在 1965 年由 Dijkstra 在研究银行家算法时提出的,它是计算机操做系统乃至整个并发程序设计领域最难处理的问题之一。

16. 怎么防止死锁?

死锁的四个必要条件:

  • 互斥条件:进程对所分配到的资源不容许其余进程进行访问,若其余进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
  • 请求和保持条件:进程得到必定的资源以后,又对其余资源发出请求,可是该资源可能被其余进程占有,此时请求阻塞,但又对本身得到的资源保持不放
  • 不可剥夺条件:是指进程已得到的资源,在未完成使用以前,不可被剥夺,只能在使用完后本身释放
  • 环路等待条件:是指进程发生死锁后,若干进程之间造成一种头尾相接的循环等待资源关系

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不知足,就不会发生死锁。

理解了死锁的缘由,尤为是产生死锁的四个必要条件,就能够最大可能地避免、预防和 解除死锁。因此,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确 定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的状况下占用资源。所以,对资源的分配要给予合理的规划。

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

线程局部变量是局限于线程内部的变量,属于线程自身全部,不在多个线程间共享。Java 提供ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。可是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别当心,在这种状况下,工做线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工做完成后没有释放,Java 应用就存在内存泄露的风险。

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

synchronized 能够保证方法或者代码块在运行时,同一时刻只有一个方法能够进入到临界区,同时它还能够保证共享变量的内存可见性。

Java 中每个对象均可以做为锁,这是 synchronized 实现同步的基础:

  • 普通同步方法,锁是当前实例对象
  • 静态同步方法,锁是当前类的class对象
  • 同步方法块,锁是括号里面的对象

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

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

20. synchronized 和 Lock 有什么区别?

  • synchronized 是 java 内置关键字,在 jvm 层面,Lock 是个 java 类
  • synchronized 没法判断是否获取锁的状态,Lock 能够判断是否获取到锁
  • synchronized 会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程当中发生异常会释放锁),Lock 需在 finally 中手工释放锁(unlock() 方法释放锁),不然容易形成线程死锁
  • 用 synchronized 关键字的两个线程 1 和线程 2 ,若是当前线程 1 得到锁,线程 2 等待。若是线程 1阻塞,线程 2 则会一直等待下去,而 Lock 锁就不必定会等待下去,若是尝试获取不到锁,线程能够不用一直等待就结束了
  • synchronized 的锁可重入、不可中断、非公平,而 Lock锁 可重入、可判断、可公平(二者皆可)
  • Lock 锁适合大量同步的代码的同步问题,synchronized 锁适合代码少许的同步问题

21. synchronized 和 ReentrantLock 区别是什么?

synchronized 是和 if、else、for、while 同样的关键字,ReentrantLock 是类,这是两者的本质区别。既然 ReentrantLock 是类,那么它就提供了比 synchronized 更多更灵活的特性,能够被继承、能够有方法、能够有各类各样的类变量,ReentrantLock 比 synchronized 的扩展性体如今几点上:

  • ReentrantLock 能够对获取锁的等待时间进行设置,这样就避免了死锁
  • ReentrantLock 能够获取各类锁的信息
  • ReentrantLock 能够灵活地实现多路通知

另外,两者的锁机制其实也是不同的:ReentrantLock 底层调用的是 Unsafe 的 park 方法加锁,synchronized 操做的应该是对象头中 mark word。

22. 说一下 atomic 的原理?

Atomic 包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操做时,具备排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程能够向自旋锁同样,继续尝试,一直等到执行成功。

Atomic 系列的类中的核心方法都会调用 unsafe 类中的几个本地方法。咱们须要先知道一个东西就是Unsafe 类,全名为:sun.misc.Unsafe,这个类包含了大量的对 C 代码的操做,包括不少直接内存分配以及原子操做的调用,而它之因此标记为非安全的,是告诉你这个里面大量的方法调用都会存在安全隐患,须要当心使用,不然会致使严重的后果,例如在经过 unsafe 分配内存的时候,若是本身指定某些区域可能会致使一些相似 C++ 同样的指针越界到其余进程的问题。

相关文章
相关标签/搜索