最近面试的小伙伴不少,对此我整理了一份Java面试题手册:基础知识、JavaOOP、Java集合/泛型面试题、Java异常面试题、Java中的IO与NIO面试题、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、Memcached、MongoDB、Spring、SpringBoot、SpringCloud、RabbitMQ、Dubbo、MyBatis、ZooKeeper、数据结构、算法、Elasticsearch、Kafka、微服务、Linux等等。能够分享给你们学习。【持续更新中】java
完整版Java面试题地址:【2021最新版】Java面试真题汇总web
序号 | 内容 | 地址连接 |
---|---|---|
1 | 【2021最新版】JavaOOP面试题总结 | http://www.noobyard.com/article/p-rncfmibs-oe.html |
2 | 【2021最新版】Java基础面试题总结 | http://www.noobyard.com/article/p-ykqnztan-oe.html |
3 | 【2021最新版】JVM面试题总结 | 未更新 |
4 | 【2021最新版】Mysql面试题总结 | 未更新 |
5 | 【2021最新版】Redis面试题总结 | 未更新 |
6 | 【2021最新版】Memcached面试题总结 | 未更新 |
7 | 【2021最新版】MongoDB面试题总结 | 未更新 |
8 | 【2021最新版】Spring面试题总结 | 未更新 |
9 | 【2021最新版】Spring Boot面试题总结 | 未更新 |
10 | 【2021最新版】Spring Cloud面试题总结 | 未更新 |
11 | 【2021最新版】RabbitMQ面试题总结 | 未更新 |
12 | 【2021最新版】Dubbo面试题总结 | 未更新 |
13 | 【2021最新版】MyBatis面试题总结 | 未更新 |
14 | 【2021最新版】ZooKeeper面试题总结 | 未更新 |
15 | 【2021最新版】数据结构面试题总结 | 未更新 |
16 | 【2021最新版】算法面试题总结 | 未更新 |
17 | 【2021最新版】Elasticsearch面试题总结 | 未更新 |
18 | 【2021最新版】Kafka面试题总结 | 未更新 |
19 | 【2021最新版】微服务面试题总结 | 未更新 |
20 | 【2021最新版】Linux面试题总结 | 未更新 |
答:
Thread 类本质上是实现了Runnable接口的一个实例,表明一个线程的实例。 启动线程的惟一方法就是经过Thread类的start()实例方法。 start()方法是一个native方法,它将启动一个新线程,并执行run()方法。面试
public class MyThread extends Thread { public void run() { System.out.println("MyThread.run()"); } } MyThread myThread1 = new MyThread(); myThread1.start();
答:算法
若是本身的类已经extends另外一个类,就没法直接extends Thread,此时,能够实现一个Runnable接口。sql
public class MyThread extends OtherClass implements Runnable { public void run() { System.out.println("MyThread.run()"); } } //启动 MyThread,须要首先实例化一个 Thread,并传入本身的 MyThread 实例: MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start(); //事实上,当传入一个 Runnable target 参数给 Thread 后, Thread 的 run()方法就会调用 target.run() public void run() { if (target != null) { target.run(); } }
答:
有返回值的任务必须实现Callable接口,相似的,无返回值的任务必须Runnable接口。执行Callable任务后,能够获取一个 Future的对象,在该对象上调用get就能够获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就能够实现传说中有返回结果的多线程了。数据库
//建立一个线程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 建立多个有返回值的任务 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 执行任务并获取 Future 对象 Future f = pool.submit(c); list.add(f); }// 关闭线程池 pool.shutdown(); // 获取全部并发任务的运行结果 for (Future f : list) { // 从 Future 对象上获取任务的返回值,并输出到控制台 System.out.println("res: " + f.get().toString()); }
答:
Java 里面线程池的顶级接口是Executor,可是严格意义上讲Executor并非一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
newCachedThreadPool数组
建立一个可根据须要建立新线程的线程池,可是在之前构造的线程可用时将重用它们。对于执行不少短时间异步任务的程序而言,这些线程池一般可提升程序性能。 调用execute将重用之前构造的线程(若是线程可用)。若是现有线程没有可用的,则建立一个新线程并添加到池中。终止并从缓存中移除那些已有60秒钟未被使用的线程。 所以,长时间保持空闲的线程池不会使用任何资源。缓存
newFixedThreadPool安全
建立一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数nThreads线程会处于处理任务的活动状态。若是在全部线程处于活动状态时提交附加任务,则在有可用线程以前,附加任务将在队列中等待。若是在关闭前的执行期间因为失败而致使任何线程终止,那么一个新线程将代替它执行后续的任务(若是须要)。在某个线程被显式地关闭以前,池中的线程将一直存在。数据结构
newScheduledThreadPool
建立一个线程池,它可安排在给定延迟后运行命令或者按期地执行。
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3); scheduledThreadPool.schedule(newRunnable(){ @Override public void run() { System.out.println("延迟三秒"); } } , 3, TimeUnit.SECONDS); scheduledThreadPool.scheduleAtFixedRate(newRunnable(){ @Override public void run() { System.out.println("延迟 1 秒后每三秒执行一次"); } } ,1,3,TimeUnit.SECONDS);
newSingleThreadExecutor
Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程池能够在线程死后(或发生异常时)从新启动一个线程来替代原来的线程继续执行下去!
答:
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰以后,那么就具有了两层语义:
1)保证了不一样线程对这个变量进行操做时的可见性,即一个线程修改了某个变量的值,这新值对其余线程来讲是当即可见的,volatile关键字会强制将修改的值当即写入主存。
2)禁止进行指令重排序。
volatile 不是原子性操做
什么叫保证部分有序性?
当程序执行到volatile变量的读操做或者写操做时,在其前面的操做的更改确定所有已经进行,且结果已经对后面的操做可见;在其后面的操做确定尚未进行;
x = 2; //语句1 y = 0; //语句2 flag = true; //语句3 x = 4; //语句4 y = -1; //语句5
因为flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句一、语句2前面,也不会讲语句3放到语句四、语句5后面。
可是要注意语句1和语句2的顺序、语句4和语句5的顺序是不做任何保证的。使用 Volatile 通常用于状态标记量和单例模式的双检锁。
答:
在多线程中有多种方法让线程按特定顺序执行,你能够用线程类的join()方法在一个线程中启动另外一个线程,另一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。实际上先启动三个线程中哪个都行,由于在每一个线程的run方法中用join方法限定了三个线程的执行顺序
public class JoinTest2 { // 1.如今有T一、T二、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行 public static void main(String[] args) { final Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("t1"); } } ); final Thread t2 = new Thread(new Runnable() { @Override public void run() { try { // 引用t1线程,等待t1线程执行完 t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } S ystem.out.println("t2"); } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { try { // 引用t2线程,等待t2线程执行完 t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } S ystem.out.println("t3"); } }); t3.start();//这里三个线程的启动顺序能够任意,你们能够试下! t2.start(); t1.start(); } }
答:
若是问到了这样的问题,能够展开的说一下线程池如何用、线程池的好处、线程池的启动策略)合理利用线程池可以带来三个好处。
第一:下降资源消耗。经过重复利用已建立的线程下降线程建立和销毁形成的消耗。
第二:提升响应速度。当任务到达时,任务能够不须要等到线程建立就能当即执行。
第三:提升线程的可管理性。线程是稀缺资源,若是无限制的建立,不只会消耗系统资源,还会下降系统的稳定性,使用线程池能够进行统一的分配,调优和监控。
答:
Semaphore 是一种基于计数的信号量。它能够设定一个阈值,基于此,多个线程竞争获取许可信号,作完本身的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。 Semaphore 能够用来构建一些对象池,资源池之类的, 好比数据库链接池
实现互斥锁(计数器为 1)咱们也能够建立计数为 1 的 Semaphore,将其做为一种相似互斥锁的机制,这也叫二元信号量,
表示两种互斥状态。
代码实现
// 建立一个计数阈值 // 只能 5 个线程同时访问 Semaphore semp = new Semaphore(5); try { // 申请许可 semp.acquire(); try { // 业务逻辑 } catch (Exception e) { } finally { // 释放许可semp.release(); } } catch (InterruptedException e) { }
答:
Hotspot 的做者通过以往的研究发现大多数状况下锁不只不存在多线程竞争,并且老是由同一线程屡次得到。 偏向锁的目的是在某个线程得到锁以后,消除这个线程锁重入(CAS)的开销,看起来让这个线程获得了偏护。
引入偏向锁是为了在无多线程竞争的状况下尽可能减小没必要要的轻量级锁执行路径,由于轻量级锁的获取及释放依赖屡次CAS原子指令, 而偏向锁只须要在置换ThreadID的时候依赖一次CAS原子指令(因为一旦出现多线程竞争的状况就必须撤销偏向锁,因此偏向锁的撤销操做的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。
上面说过, 轻量级锁是为了在线程交替执行同步块时提升性能, 而偏向锁则是在只有一个线程执行同步块时进一步提升性能
答:
不少状况下,主线程生成并启动了子线程,须要用到子线程返回的结果,也就是须要主线程须要在子线程结束后再结束,这时候就要用到 join() 方法 。
System.out.println(Thread.currentThread().getName() + "线程运行开始!"); Thread6 thread1 = new Thread6(); thread1.setName("线程 B"); thread1.join(); System.out.println("这时 thread1 执行完毕以后才能执行主线程");
答:
用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。 默认状况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的全部生产者线程或消费者线程,当队列可用时,能够按照阻塞的前后顺序访问队列,即先阻塞的生产者线程,能够先往队列里插入元素,先阻塞的消费者线程,能够先从队列里获取元素。一般状况下为了保证公平性会下降吞吐量。咱们可使用如下代码建立一个公平的阻塞队列
ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
答:
是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue 来实现。队列中的元素必须实现Delayed接口,在建立元素时能够指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。咱们能够将DelayQueue运用在如下应用场景:
缓存系统的设计:能够用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue 中获取元素时,表示缓存有效期到了。
定时任务调度 :使用DelayQueue保存当天将会执行的任务和执行时间 ,一旦从DelayQueue中获取到任务就开始执行,从好比TimerQueue就是使用DelayQueue实现的。
答:
进程是操做系统分配资源的最小单元,线程是操做系统调度的最小单元。一个程序至少有一个进程,一个进程至少有一个线程。
答:
采用时间片轮转的方式。能够设置线程的优先级,会映射到下层的系统上面的优先级上,如非特别须要,尽可能不要用,防止线程饥饿。
答:
Callable接口相似于Runnable,从名字就能够看出来了,可是Runnable不会返回结果,而且没法抛出返回结果的异常,而 Callable功能更强大一些,被线程执行后,能够返回值,这个返回值能够被Future拿到,也就是说,Future能够拿到异步执行任务的返回值。能够认为是带有回调的Runnable。
Future接口表示异步任务,是尚未完成的任务给出的将来结果。因此说Callable用于产生结果,Future用于获取结果。
答:
不可变对象(Immutable Objects)即对象一旦被建立它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(MutableObjects)。不可变对象的类即为不可变类(Immutable Class)。
Java平台类库中包含许多不可变类,如String、基本类型的包装类、BigInteger和BigDecimal等。不可变对象天生是线程安全的。它们的常量(域)是在构造函数中建立的。既然它们的状态没法修改,这些常量永远不会变。
不可变对象永远是线程安全的。
只有知足以下状态,一个对象才是不可变的;它的状态不能在建立后再被修改;全部域都是final类型;而且,它被正确建立(建立期间没有发生this引用的逸出)。
答:
线程组和线程池是两个不一样的概念,他们的做用彻底不一样,前者是为了方便线程的管理,后者是为了管理线程的生命周期,复用线程,减小建立销毁线程的开销。
该面试题答案解析完整文档获取方式:Java多线程&并发面试题总结
篇幅有限,其余内容就不在这里一 一展现了,整理不易,欢迎你们一块儿交流,喜欢小编分享的文章记得关注我点赞哟,感谢支持!重要的事情说三遍,转发+转发+转发,必定要记得转发哦!!!