2万字Java并发编程面试题合集(含答案,建议收藏)

Java 并发编程

一、在 java 中守护线程和本地线程区别?
二、线程与进程的区别?
三、什么是多线程中的上下文切换?
四、死锁与活锁的区别,死锁与饥饿的区别?
五、Java 中用到的线程调度算法是什么?
六、什么是线程组,为何在 Java 中不推荐使用?
七、为何使用 Executor 框架?
八、在 Java 中 Executor 和 Executors 的区别?
九、如何在 Windows 和 Linux 上查找哪一个线程使用的 CPU 时间最长?
十、什么是原子操做?在 Java Concurrency API 中有哪些原子类(atomic classes)?
十一、Java Concurrency API 中的 Lock 接口(Lock interface)是什么?对比同步它有什么优点?
十二、什么是 Executors 框架?
1三、什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产者-消费者模型?
1四、什么是 Callable 和 Future?
1五、什么是 FutureTask?使用 ExecutorService 启动任务。
1六、什么是并发容器的实现?
1七、多线程同步和互斥有几种实现方法,都是什么?
1八、什么是竞争条件?你怎样发现和解决竞争?
1九、你将如何使用 thread dump?你将如何分析 Thread dump?
20、为何咱们调用 start()方法时会执行 run()方法,为何咱们不能直接调用 run()方法?
2一、Java 中你怎样唤醒一个阻塞的线程?
2二、在 Java 中 CycliBarriar 和 CountdownLatch 有什么区别?
2三、什么是不可变对象,它对写并发应用有什么帮助?
2四、什么是多线程中的上下文切换?
2五、Java 中用到的线程调度算法是什么?
2六、什么是线程组,为何在 Java 中不推荐使用?
2七、为何使用 Executor 框架比使用应用建立和管理线程好?
2八、java 中有几种方法能够实现一个线程?
2九、如何中止一个正在运行的线程?
30、notify()和 notifyAll()有什么区别?
3一、什么是 Daemon 线程?它有什么意义?
3二、java 如何实现多线程之间的通信和协做?
3三、什么是可重入锁(ReentrantLock)?
3四、当一个线程进入某个对象的一个 synchronized 的实例方法后,其它线程是否可进入此对象的其它方法?
3五、乐观锁和悲观锁的理解及如何实现,有哪些实现方式?
3六、SynchronizedMap 和 ConcurrentHashMap 有什么区别?
3七、CopyOnWriteArrayList 能够用于什么应用场景?
3八、什么叫线程安全?servlet 是线程安全吗?
3九、volatile 有什么用?可否用一句话说明下 volatile 的应用场景?
40、为何代码会重排序?
4一、在 java 中 wait 和 sleep 方法的不一样?
4二、用 Java 实现阻塞队列
4三、一个线程运行时发生异常会怎样?
4四、如何在两个线程间共享数据?
4五、Java 中 notify 和 notifyAll 有什么区别?
4六、为何 wait, notify 和 notifyAll 这些方法不在 thread 类里面?
4七、什么是 ThreadLocal 变量?
4八、Java 中 interrupted 和 isInterrupted 方法的区别?
4九、为何 wait 和 notify 方法要在同步块中调用?
50、为何你应该在循环中检查等待条件?
5一、Java 中的同步集合与并发集合有什么区别?
5二、什么是线程池? 为何要使用它?
5三、怎么检测一个线程是否拥有锁?
5四、你如何在 Java 中获取线程堆栈?
5六、Thread 类中的 yield 方法有什么做用?
5七、Java 中 ConcurrentHashMap 的并发度是什么?
5八、Java 中 Semaphore 是什么?
5九、Java 线程池中 submit() 和 execute()方法有什么区别?
60、什么是阻塞式方法?
6一、Java 中的 ReadWriteLock 是什么?
6二、volatile 变量和 atomic 变量有什么不一样?
6三、能够直接调用 Thread 类的 run ()方法么?
6四、如何让正在运行的线程暂停一段时间?
6五、你对线程优先级的理解是什么?
66 、 什 么 是 线 程 调 度 器 (Thread Scheduler) 和 时 间 分 片 (TimeSlicing )?
6七、你如何确保 main()方法所在的线程是 Java 程序最后结束的线程?
6八、线程之间是如何通讯的?
6九、为何线程通讯的方法 wait(), notify()和 notifyAll()被定义在Object 类里?
70、为何 wait(), notify()和 notifyAll ()必须在同步方法或者同步块中被调用?
7一、为何 Thread 类的 sleep()和 yield ()方法是静态的?
7二、如何确保线程安全?
7三、同步方法和同步块,哪一个是更好的选择?
7四、如何建立守护线程?
7五、什么是 Java Timer 类?如何建立一个有特定时间间隔的任务?


关于Java并发编程的知识总结了个思惟导图分享给你们java


一、在 java 中守护线程和本地线程区别?

java 中的线程分为两种:守护线程(Daemon)和用户线程(User)。
任何线程均可以设置为守护线程和用户线程,经过方法 Thread.setDaemon(boolon);true 则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在 Thread.start()以前调用,不然运行时会抛出异常。
二者的区别:
惟一的区别是判断虚拟机(JVM)什么时候离开,Daemon 是为其余线程提供服务,若是所有的 User Thread 已经撤离,Daemon 没有可服务的线程,JVM 撤离。也能够理解为守护线程是 JVM 自动建立的线程(但不必定),用户线程是程序建立的线程;好比 JVM 的垃圾回收线程是一个守护线程,当全部线程已经撤离,再也不产生垃圾,守护线程天然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线程时,Java 虚拟机会自动离开。
扩展:Thread Dump 打印出来的线程信息,含有 daemon 字样的线程即为守护进程,可能会有:服务守护进程、编译守护进程、windows 下的监听 Ctrl+break的守护进程、Finalizer 守护进程、引用处理守护进程、GC 守护进程。

二、线程与进程的区别?

进程是操做系统分配资源的最小单元,线程是操做系统调度的最小单元。
一个程序至少有一个进程,一个进程至少有一个线程。

三、什么是多线程中的上下文切换?

多线程会共同使用一组计算机上的 CPU,而线程数大于给程序分配的 CPU 数量时,为了让各个线程都有执行的机会,就须要轮转使用 CPU。不一样的线程切换使用 CPU发生的切换数据等就是上下文切换。

四、死锁与活锁的区别,死锁与饥饿的区别?

死锁:是指两个或两个以上的进程(或线程)在执行过程当中,因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法推动下去。
产生死锁的必要条件:
一、互斥条件:所谓互斥就是进程在某一时间内独占资源。
二、请求与保持条件:一个进程因请求资源而阻塞时,对已得到的资源保持不放。
三、不剥夺条件:进程已得到资源,在末使用完以前,不能强行剥夺。
四、循环等待条件:若干进程之间造成一种头尾相接的循环等待资源关系。
活锁:任务或者执行者没有被阻塞,因为某些条件没有知足,致使一直重复尝试,失败,尝试,失败。
活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
饥饿:一个或者多个线程由于种种缘由没法得到所须要的资源,致使一直没法执行的状态。
Java 中致使饥饿的缘由:
一、高优先级线程吞噬全部的低优先级线程的 CPU 时间。
二、线程被永久堵塞在一个等待进入同步块的状态,由于其余线程老是能在它以前持续地对该同步块进行访问。
三、线程在等待一个自己也处于永久等待完成的对象(好比调用这个对象的 wait 方法),由于其余线程老是被持续地得到唤醒。
五、Java 中用到的线程调度算法是什么?
采用时间片轮转的方式。能够设置线程的优先级,会映射到下层的系统上面的优先级上,如非特别须要,尽可能不要用,防止线程饥饿。

六、什么是线程组,为何在 Java 中不推荐使用?

ThreadGroup 类,能够把线程归属到某一个线程组中,线程组中能够有线程对象,也能够有线程组,组中还能够有线程,这样的组织结构有点相似于树的形式。
为何不推荐使用?由于使用有不少的安全隐患吧,没有具体追究,若是须要使用,推荐使用线程池。

七、为何使用 Executor 框架?

每次执行任务建立线程 new Thread()比较消耗性能,建立一个线程是比较耗时、耗资源的。
调用 new Thread()建立的线程缺少管理,被称为野线程,并且能够无限制的建立,线程之间的相互竞争会致使过多占用系统资源而致使系统瘫痪,还有线程之间的频繁交替也会消耗不少系统资源。
接使用 new Thread() 启动的线程不利于扩展,好比定时执行、按期执行、定时按期执行、线程中断等都不便实现。

八、在 Java 中 Executor 和 Executors 的区别?

Executors 工具类的不一样方法按照咱们的需求建立了不一样的线程池,来知足业务的需求。
Executor 接口对象能执行咱们的线程任务。
ExecutorService 接口继承了 Executor 接口并进行了扩展,提供了更多的方法咱们能得到任务执行的状态而且能够获取任务的返回值。
使用 ThreadPoolExecutor 能够建立自定义线程池。
Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的完成,并可使用 get()方法获取计算的结果。

九、如何在 Windows 和 Linux 上查找哪一个线程使用的 CPU 时间最长?

十、什么是原子操做?在 Java Concurrency API 中有哪些原子类(atomic classes)?

原子操做(atomic operation)意为”不可被中断的一个或一系列操做” 。
处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操做。在 Java 中能够经过锁和循环 CAS 的方式来实现原子操做。 CAS 操做——Compare & Set,或是 Compare & Swap,如今几乎全部的 CPU 指令都支持 CAS的原子操做。
原子操做是指一个不受其余操做影响的操做任务单元。原子操做是在多线程环境下避免数据不一致必须的手段。
int++并非一个原子操做,因此当一个线程读取它的值并加 1 时,另一个线程有可能会读到以前的值,这就会引起错误。
为了解决这个问题,必须保证增长操做是原子的,在 JDK1.5 以前咱们可使用同步技术来作到这一点。到 JDK1.5,java.util.concurrent.atomic 包提供了 int 和long 类型的原子包装类,它们能够自动的保证对于他们的操做是原子的而且不须要使用同步。
java.util.concurrent 这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具备排他性,即当某个线程进入方法,执行其中的指令时,不会被其余线程打断,而别的线程就像自旋锁同样,一直等到该方法执行完成,才由 JVM 从等待队列中选择一个另外一个线程进入,这只是一种逻辑上的理解。
原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
解决 ABA 问题的原子类:AtomicMarkableReference(经过引入一个 boolean来反映中间有没有变过),AtomicStampedReference(经过引入一个 int 来累加来反映中间有没有变过)

十一、Java Concurrency API 中的 Lock 接口(Lock interface)是什么?对比同步它有什么优点?

Lock 接口比同步方法和同步块提供了更具扩展性的锁操做。
他们容许更灵活的结构,能够具备彻底不一样的性质,而且能够支持多个相关类的条件对象。
它的优点有:
可使锁更公平
可使线程在等待锁的时候响应中断
可让线程尝试获取锁,并在没法获取锁的时候当即返回或者等待一段时间
能够在不一样的范围,以不一样的顺序获取和释放锁
总体上来讲 Lock 是 synchronized 的扩展版,Lock 提供了无条件的、可轮询的(tryLock 方法)、定时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition 方法)锁操做。另外 Lock 的实现类基本都支持非公平锁(默认)和公平锁,synchronized 只支持非公平锁,固然,在大部分状况下,非公平锁是高效的选择。

十二、什么是 Executors 框架?

Executor 框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。
无限制的建立线程会引发应用程序内存溢出。因此建立一个线程池是个更好的的解决方案,由于能够限制线程的数量而且能够回收再利用这些线程。利用Executors 框架能够很是方便的建立一个线程池。

1三、什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产者-消费者模型?

阻塞队列(BlockingQueue)是一个支持两个附加操做的队列。
这两个附加的操做是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。
阻塞队列经常使用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
JDK7 提供了 7 个阻塞队列。分别是:
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
Java 5 以前实现同步存取时,可使用普通的一个集合,而后在使用线程的协做和线程同步能够实现生产者,消费者模式,主要的技术就是用好,wait ,notify,notifyAll,sychronized 这些关键字。而在 java 5 以后,可使用阻塞队列来实现,此方式大大简少了代码量,使得多线程编程更加容易,安全方面也有保障。
BlockingQueue 接口是 Queue 的子接口,它的主要用途并非做为容器,而是做为线程同步的的工具,所以他具备一个很明显的特性,当生产者线程试图向BlockingQueue 放入元素时,若是队列已满,则线程被阻塞,当消费者线程试图从中取出一个元素时,若是队列为空,则该线程会被阻塞,正是由于它所具备这个特性,因此在程序中多个线程交替向 BlockingQueue 中放入元素,取出元素,它能够很好的控制线程之间的通讯。
阻塞队列使用最经典的场景就是 socket 客户端数据的读取和解析,读取数据的线程不断将数据放入队列,而后解析线程不断从队列取数据解析。

1四、什么是 Callable 和 Future?

Callable 接口相似于 Runnable,从名字就能够看出来了,可是 Runnable 不会返回结果,而且没法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,能够返回值,这个返回值能够被 Future 拿到,也就是说,Future 能够拿到异步执行任务的返回值。
能够认为是带有回调的 Runnable。
Future 接口表示异步任务,是尚未完成的任务给出的将来结果。因此说 Callable用于产生结果,Future 用于获取结果。

1五、什么是 FutureTask?使用 ExecutorService 启动任务。

在 Java 并发程序中 FutureTask 表示一个能够取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,若是运算还没有完成 get 方法将会阻塞。一个 FutureTask 对象能够对调用了 Callable 和 Runnable 的对象进行包装,因为 FutureTask 也是调用了 Runnable接口因此它能够提交给 Executor 来执行。

1六、什么是并发容器的实现?

何为同步容器:能够简单地理解为经过 synchronized 来实现同步的容器,若是有多个线程调用同步容器的方法,它们将会串行执行。好比 Vector,Hashtable,以及 Collections.synchronizedSet,synchronizedList 等方法返回的容器。能够经过查看 Vector,Hashtable 等这些同步容器的实现代码,能够看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在须要同步的方法上加上关键字 synchronized。
并发容器使用了与同步容器彻底不一样的加锁策略来提供更高的并发性和伸缩性,例如在 ConcurrentHashMap 中采用了一种粒度更细的加锁机制,能够称为分段锁,在这种锁机制下,容许任意数量的读线程并发地访问 map,而且执行读操做的线程和写操做的线程也能够并发的访问 map,同时容许必定数量的写操做线程并发地修改 map,因此它能够在并发环境下实现更高的吞吐量。

1七、多线程同步和互斥有几种实现方法,都是什么?

线程同步是指线程之间所具备的一种制约关系,一个线程的执行依赖另外一个线程的消息,当它没有获得另外一个线程的消息时应等待,直到消息到达时才被唤醒。线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任什么时候刻最多只容许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥能够当作是一种特殊的线程同步。
线程间的同步方法大致可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时须要切换内核态与用户态,而用户模式就是不须要切换到内核态,只在用户态完成操做。
用户模式下的方法有:原子操做(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。

1八、什么是竞争条件?你怎样发现和解决竞争?

当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则咱们认为这发生了竞争条件(race condition)。

1九、你将如何使用 thread dump?你将如何分析 Thread dump?

新建状态(New)
用 new 语句建立的线程处于新建状态,此时它和其余 Java 对象同样,仅仅在堆区中被分配了内存。
就绪状态(Runnable)
当一个线程对象建立后,其余线程调用它的 start()方法,该线程就进入就绪状态,Java 虚拟机会为它建立方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待得到 CPU 的使用权。
运行状态(Running)
处于这个状态的线程占用 CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。
阻塞状态(Blocked)
阻塞状态是指线程由于某些缘由放弃 CPU,暂时中止运行。当线程处于阻塞状态时,Java 虚拟机不会给线程分配 CPU。直到线程从新进入就绪状态,它才有机会转到运行状态。
阻塞状态可分为如下 3 种:
位于对象等待池中的阻塞状态(Blocked in object’s wait pool):
当线程处于运行状态时,若是执行了某个对象的 wait()方法,Java 虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通讯”的内容。
位于对象锁池中的阻塞状态(Blocked in object’s lock pool):
当线程处于运行状态时,试图得到某个对象的同步锁时,若是该对象的同步锁已经被其余线程占用,Java 虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。
其余阻塞状态(Otherwise Blocked):
当前线程执行了 sleep()方法,或者调用了其余线程的 join()方法,或者发出了 I/O请求时,就会进入这个状态。
死亡状态(Dead)
当线程退出 run()方法时,就进入死亡状态,该线程结束生命周期。

20、为何咱们调用 start()方法时会执行 run()方法,为何咱们不能直接调用 run()方法?

当你调用 start()方法时你将建立新的线程,而且执行在 run()方法里的代码。
可是若是你直接调用 run()方法,它不会建立新的线程也不会执行调用线程的代码,只会把 run 方法看成普通方法去执行。

2一、Java 中你怎样唤醒一个阻塞的线程?

在 Java 发展史上曾经使用 suspend()、resume()方法对于线程进行阻塞唤醒,但随之出现不少问题,比较典型的仍是死锁问题。
解决方案可使用以对象为目标的阻塞,即利用 Object 类的 wait()和 notify()方法实现线程阻塞。
首 先 ,wait、notify 方法是针对对象的,调用任意对象的 wait()方法都将致使线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对象阻塞的线程,但它须要从新获取改对象的锁,直到获取成功才能往下执行;其次,wait、notify 方法必须在 synchronized 块或方法中被调用,而且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如此一来在调用 wait 以前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当前线程就将以前获取的对象锁释放。

2二、在 Java 中 CycliBarriar 和 CountdownLatch 有什么区别?

CyclicBarrier 能够重复使用,而 CountdownLatch 不能重复使用。
Java 的 concurrent 包里面的 CountDownLatch 其实能够把它看做一个计数器,只不过这个计数器的操做是原子操做,同时只能有一个线程去操做这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。你能够向 CountDownLatch 对象设置一个初始的数字做为计数值,任何调用这个对象上的 await()方法都会阻塞,直到这个计数器的计数值被其余的线程减为 0 为止。
因此在当前计数到达零以前,await 方法会一直受阻塞。以后,会释放全部等待的线程,await 的全部后续调用都将当即返回。这种现象只出现一次——计数没法被重置。若是须要重置计数,请考虑使用 CyclicBarrier。CountDownLatch 的一个很是典型的应用场景是:有一个任务想要往下执行,但必需要等到其余的任务执行完毕后才能够继续往下执行。假如咱们这个想要继续往下执行的任务调用一个 CountDownLatch 对象的 await()方法,其余的任务执行完本身的任务后调用同一个 CountDownLatch 对象上的 countDown()方法,这个调用 await()方法的任务将一直阻塞等待,直到这个 CountDownLatch 对象的计数值减到 0 为止。
CyclicBarrier 一个同步辅助类,它容许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 颇有用。由于该 barrier 在释放等待线程后能够重用,因此称它为循环 的 barrier。

2三、什么是不可变对象,它对写并发应用有什么帮助?

不可变对象(Immutable Objects)即对象一旦被建立它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。
不可变对象的类即为不可变类(Immutable Class)。Java 平台类库中包含许多不可变类,如 String、基本类型的包装类、BigInteger 和 BigDecimal 等。
不可变对象天生是线程安全的。它们的常量(域)是在构造函数中建立的。既然它们的状态没法修改,这些常量永远不会变。
不可变对象永远是线程安全的。
只有知足以下状态,一个对象才是不可变的;
它的状态不能在建立后再被修改;
全部域都是 final 类型;而且,它被正确建立(建立期间没有发生 this 引用的逸出)。

2四、什么是多线程中的上下文切换?

在上下文切换过程当中,CPU 会中止处理当前运行的程序,并保存当前程序运行的具体位置以便以后继续运行。从这个角度来看,上下文切换有点像咱们同时阅读几本书,在来回切换书本的同时咱们须要记住每本书当前读到的页码。在程序中,上下文切换过程当中的“页码”信息是保存在进程控制块(PCB)中的。PCB 还常常被称做“切换桢”(switchframe)。“页码”信息会一直保存到 CPU 的内存中,直到他们被再次使用。
上下文切换是存储和恢复 CPU 状态的过程,它使得线程执行可以从中断点恢复执行。上下文切换是多任务操做系统和多线程环境的基本特征。

2五、Java 中用到的线程调度算法是什么?

计算机一般只有一个 CPU,在任意时刻只能执行一条机器指令,每一个线程只有得到CPU 的使用权才能执行指令.所谓多线程的并发运行,实际上是指从宏观上看,各个线程轮流得到 CPU 的使用权,分别执行各自的任务.在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权.
有两种调度模型:分时调度模型和抢占式调度模型。
分时调度模型是指让全部的线程轮流得到 cpu 的使用权,而且平均分配每一个线程占用的 CPU 的时间片这个也比较好理解。
Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,若是可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。

2六、什么是线程组,为何在 Java 中不推荐使用?

线程组和线程池是两个不一样的概念,他们的做用彻底不一样,前者是为了方便线程的管理,后者是为了管理线程的生命周期,复用线程,减小建立销毁线程的开销。


2七、为何使用 Executor 框架比使用应用建立和管理线程好?

为何要使用 Executor 线程池框架
一、每次执行任务建立线程 new Thread()比较消耗性能,建立一个线程是比较耗时、耗资源的。
二、调用 new Thread()建立的线程缺少管理,被称为野线程,并且能够无限制的建立,线程之间的相互竞争会致使过多占用系统资源而致使系统瘫痪,还有线程之间的频繁交替也会消耗不少系统资源。
三、直接使用 new Thread() 启动的线程不利于扩展,好比定时执行、按期执行、定时按期执行、线程中断等都不便实现。
使用 Executor 线程池框架的优势
一、能复用已存在并空闲的线程从而减小线程对象的建立从而减小了消亡线程的开销。
二、可有效控制最大并发线程数,提升系统资源使用率,同时避免过多资源竞争。
三、框架中已经有定时、按期、单线程、并发数控制等功能。
综上所述使用线程池框架 Executor 能更好的管理线程、提供系统资源使用率。

2八、java 中有几种方法能够实现一个线程?

继承 Thread 类
实现 Runnable 接口
实现 Callable 接口,须要实现的是 call() 方法

2九、如何中止一个正在运行的线程?

使用共享变量的方式
在这种方式中,之因此引入共享变量,是由于该变量能够被多个执行相同任务的线程用来做为是否中断的信号,通知中断线程的执行。
使用 interrupt 方法终止线程
若是一个线程因为等待某些事件的发生而被阻塞,又该怎样中止该线程呢?这种状况常常会发生,好比当一个线程因为须要等候键盘输入而被阻塞,或者调用Thread.join()方法,或者 Thread.sleep()方法,在网络中调用ServerSocket.accept()方法,或者调用了 DatagramSocket.receive()方法时,都有可能致使线程阻塞,使线程处于处于不可运行状态时,即便主程序中将该线程的共享变量设置为 true,但该线程此时根本没法检查循环标志,固然也就没法当即中断。这里咱们给出的建议是,不要使用 stop()方法,而是使用 Thread 提供的interrupt()方法,由于该方法虽然不会中断一个正在运行的线程,可是它可使一个被阻塞的线程抛出一个中断异常,从而使线程提早结束阻塞状态,退出堵塞代码。

30、notify()和 notifyAll()有什么区别?

当一个线程进入 wait 以后,就必须等其余线程 notify/notifyall,使用 notifyall,能够唤醒全部处于 wait 状态的线程,使其从新进入锁的争夺队列中,而 notify 只能唤醒一个。
若是没把握,建议 notifyAll,防止 notigy 由于信号丢失而形成程序异常。

3一、什么是 Daemon 线程?它有什么意义?

所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,而且这个线程并不属于程序中不可或缺的部分。所以,当全部的非后台线程结束时,程序也就终止了,同时会杀死进程中的全部后台线程。反过来讲,只要有任何非后台线程还在运行,程序就不会终止。必须在线程启动以前调用setDaemon()方法,才能把它设置为后台线程。注意:后台进程在不执行 finally子句的状况下就会终止其 run()方法。
好比:JVM 的垃圾回收线程就是 Daemon 线程,Finalizer 也是守护线程。

3二、java 如何实现多线程之间的通信和协做?

中断 和 共享变量

3三、什么是可重入锁(ReentrantLock)?

举例来讲明锁的可重入性
public class UnReentrant{
	Lock lock = new Lock();
	public void outer(){
		lock.lock();
		inner();
		lock.unlock();
	}
	public void inner(){
		lock.lock();
		//do something
		lock.unlock();
	}
}复制代码
outer 中调用了 inner,outer 先锁住了 lock,这样 inner 就不能再获取 lock。其实调用 outer 的线程已经获取了 lock 锁,可是不能在 inner 中重复利用已经获取的锁资源,这种锁即称之为 不可重入可重入就意味着:线程能够进入任何一个它已经拥有的锁所同步着的代码块。
synchronized、ReentrantLock 都是可重入的锁,可重入锁相对来讲简化了并发编程的开发。

3四、当一个线程进入某个对象的一个 synchronized 的实例方法后,其它线程是否可进入此对象的其它方法?

若是其余方法没有 synchronized 的话,其余线程是能够进入的。
因此要开放一个线程安全的对象时,得保证每一个方法都是线程安全的。

3五、乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

悲观锁:老是假设最坏的状况,每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了不少这种锁机制,好比行锁,表锁等,读锁,写锁等,都是在作操做以前先上锁。再好比 Java 里面的同步原语 synchronized 关键字的实现也是悲观锁。
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可使用版本号等机制。乐观锁适用于多读的应用类型,这样能够提升吞吐量,像数据库提供的相似于 write_condition 机制,其实都是提供的乐观锁。在 Java中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。
乐观锁的实现方式:
一、使用版本标识来肯定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时能够采起丢弃和再次尝试的策略。
二、java 中的 Compare and Swap 即 CAS ,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知此次竞争中失败,并能够再次尝试。 CAS 操做中包含三个操做数 —— 须要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。若是内存位置 V 的值与预期原值 A 相匹配,那么处理器会自动将该位置值更新为新值 B。不然处理器不作任何操做。
CAS 缺点:
一、ABA 问题:
好比说一个线程 one 从内存位置 V 中取出 A,这时候另外一个线程 two 也从内存中取出 A,而且 two 进行了一些操做变成了 B,而后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操做发现内存中仍然是 A,而后 one 操做成功。尽管线程 one 的 CAS 操做成功,但可能存在潜藏的问题。从 Java1.5 开始 JDK 的 atomic包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
二、循环时间长开销大:
对于资源竞争严重(线程冲突严重)的状况,CAS 自旋的几率会比较大,从而浪费更多的 CPU 资源,效率低于 synchronized。
三、只能保证一个共享变量的原子操做:
当对一个共享变量执行操做时,咱们可使用循环 CAS 的方式来保证原子操做,可是对多个共享变量操做时,循环 CAS 就没法保证操做的原子性,这个时候就能够用锁。

3六、SynchronizedMap 和 ConcurrentHashMap 有什么区别?

SynchronizedMap 一次锁住整张表来保证线程安全,因此每次只能有一个线程来访为 map。
ConcurrentHashMap 使用分段锁来保证在多线程下的性能。
ConcurrentHashMap 中则是一次锁住一个桶。ConcurrentHashMap 默认将hash 表分为 16 个桶,诸如 get,put,remove 等经常使用操做只锁当前须要用到的桶。
这样,原来只能一个线程进入,如今却能同时有 16 个写线程执行,并发性能的提高是显而易见的。
另外 ConcurrentHashMap 使用了一种不一样的迭代方式。在这种迭代方式中,当iterator 被建立后集合再发生改变就再也不是抛出
ConcurrentModificationException,取而代之的是在改变时 new 新的数据从而不影响原有的数据 ,iterator 完成后再将头指针替换为新的数据 ,这样 iterator线程可使用原来老的数据,而写线程也能够并发的完成改变。

3七、CopyOnWriteArrayList 能够用于什么应用场景?

CopyOnWriteArrayList(免锁容器)的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出 ConcurrentModificationException。在CopyOnWriteArrayList 中,写入将致使建立整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操做能够安全地执行。
一、因为写操做的时候,须要拷贝数组,会消耗内存,若是原数组的内容比较多的状况下,可能致使 young gc 或者 full gc;
二、不能用于实时读的场景,像拷贝数组、新增元素都须要时间,因此调用一个 set操做后,读取到数据可能仍是旧的,虽然 CopyOnWriteArrayList 能作到最终一致性,可是仍是无法知足实时性要求;
CopyOnWriteArrayList 透露的思想
一、读写分离,读和写分开
二、最终一致性
三、使用另外开辟空间的思路,来解决并发冲突

3八、什么叫线程安全?servlet 是线程安全吗?

线程安全是编程中的术语,指某个函数、函数库在多线程环境中被调用时,可以正确地处理多个线程之间的共享变量,使程序功能正确完成。
Servlet 不是线程安全的,servlet 是单实例多线程的,当多个线程同时访问同一个方法,是不能保证共享变量的线程安全性的。
Struts2 的 action 是多实例多线程的,是线程安全的,每一个请求过来都会 new 一个新的 action 分配给这个请求,请求完成后销毁。
SpringMVC 的 Controller 是线程安全的吗?不是的,和 Servlet 相似的处理流程。
Struts2 好处是不用考虑线程安全问题;Servlet 和 SpringMVC 须要考虑线程安全问题,可是性能能够提高不用处理太多的 gc,可使用 ThreadLocal 来处理多线程的问题。

3九、volatile 有什么用?可否用一句话说明下 volatile 的应用场景?

volatile 保证内存可见性和禁止指令重排。
volatile 用于多线程环境下的单次操做(单次读或者单次写)。

40、为何代码会重排序?

在执行程序时,为了提供性能,处理器和编译器经常会对指令进行重排序,可是不能随意重排序,不是你想怎么排序就怎么排序,它须要知足如下两个条件:
在单线程环境下不能改变程序运行的结果;
存在数据依赖关系的不容许重排序
须要注意的是:重排序不会影响单线程环境的执行结果,可是会破坏多线程的执行语义。

4一、在 java 中 wait 和 sleep 方法的不一样?

最大的不一样是在等待时 wait 会释放锁,而 sleep 一直持有锁。Wait 一般被用于线程间交互,sleep 一般被用于暂停执行。

4二、用 Java 实现阻塞队列

4三、一个线程运行时发生异常会怎样?

若是异常没有被捕获该线程将会中止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常形成线程忽然中断状况的一个内嵌接口。当一个未捕获异常将形成线程中断的时候 JVM 会使用 Thread.getUncaughtExceptionHandler()来查询线程的 UncaughtExceptionHandler 并将线程和异常做为参数传递给handler 的 uncaughtException()方法进行处理。

4四、如何在两个线程间共享数据?

在两个线程间共享变量便可实现共享。
通常来讲,共享变量要求变量自己是线程安全的,而后在线程内使用的时候,若是有对共享变量的复合操做,那么也得保证复合操做的线程安全性。

4五、Java 中 notify 和 notifyAll 有什么区别?

notify() 方法不能唤醒某个具体的线程,因此只有一个线程在等待的时候它才有用武之地。而 notifyAll()唤醒全部线程并容许他们争夺锁确保了至少有一个线程能继续运行。

4六、为何 wait, notify 和 notifyAll 这些方法不在 thread类里面?

一个很明显的缘由是 JAVA 提供的锁是对象级的而不是线程级的,每一个对象都有锁,经过线程得到。因为 wait,notify 和 notifyAll 都是锁级别的操做,因此把他们定义在 Object 类中由于锁属于对象。

4七、什么是 ThreadLocal 变量?

ThreadLocal 是 Java 里一种特殊的变量。每一个线程都有一个 ThreadLocal 就是每一个线程都拥有了本身独立的一个变量,竞争条件被完全消除了。它是为建立代价高昂的对象获取线程安全的好方法,好比你能够用 ThreadLocal 让SimpleDateFormat 变成线程安全的,由于那个类建立代价高昂且每次调用都须要建立不一样的实例因此不值得在局部范围使用它,若是为每一个线程提供一个本身独有的变量拷贝,将大大提升效率。首先,经过复用减小了代价高昂的对象的建立个数。其次,你在没有使用高代价的同步或者不变性的状况下得到了线程安全。

4八、Java 中 interrupted 和 isInterrupted 方法的区别?

interrupt
interrupt 方法用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。
注意:线程中断仅仅是置线程的中断状态位,不会中止线程。须要用户本身去监视线程的状态为并作处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。
interrupted
查询当前线程的中断状态,而且清除原状态。若是一个线程被中断了,第一次调用 interrupted 则返回 true,第二次和后面的就返回 false 了。
isInterrupted
仅仅是查询当前线程的中断状态

4九、为何 wait 和 notify 方法要在同步块中调用?

Java API 强制要求这样作,若是你不这么作,你的代码会抛出IllegalMonitorStateException 异常。还有一个缘由是为了不 wait 和 notify之间产生竞态条件。

50、为何你应该在循环中检查等待条件?

处于等待状态的线程可能会收到错误警报和伪唤醒,若是不在循环中检查等待条件,程序就会在没有知足结束条件的状况下退出。

5一、Java 中的同步集合与并发集合有什么区别?

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在 Java1.5 以前程序员们只有同步集合来用且在多线程并发的时候会致使争用,阻碍了系统的扩展性。Java5 介绍了并发集合像ConcurrentHashMap,不只提供线程安全还用锁分离和内部分区等现代技术提升了可扩展性。

5二、什么是线程池? 为何要使用它?

建立线程要花费昂贵的资源和时间,若是任务来了才建立线程那么响应时间会变长,并且一个进程能建立的线程数有限。为了不这些问题,在程序启动的时候就建立若干线程来响应处理,它们被称为线程池,里面的线程叫工做线程。从JDK1.5 开始,Java API 提供了 Executor 框架让你能够建立不一样的线程池。

5三、怎么检测一个线程是否拥有锁?

在 java.lang.Thread 中有一个方法叫 holdsLock(),它返回 true 若是当且仅当当前线程拥有某个具体对象的锁。

5四、你如何在 Java 中获取线程堆栈?

kill -3 [java pid]
不会在当前终端输出,它会输出到代码执行的或指定的地方去。好比,kill -3
tomcat pid, 输出堆栈到 log 目录下。
Jstack [java pid]
这个比较简单,在当前终端显示,也能够重定向到指定文件中。
-JvisualVM:Thread Dump
不作说明,打开 JvisualVM 后,都是界面操做,过程仍是很简单的。

5五、JVM 中哪一个参数是用来控制线程的栈堆栈小的?

-Xss 每一个线程的栈大小

5六、Thread 类中的 yield 方法有什么做用?

使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
当前线程到了就绪状态,那么接下来哪一个线程会从就绪状态变成执行状态呢?多是当前线程,也多是其余线程,看系统的分配了。

5七、Java 中 ConcurrentHashMap 的并发度是什么?

ConcurrentHashMap 把实际 map 划分红若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度得到的,它是 ConcurrentHashMap 类构造函数的一个可选参数,默认值为 16,这样在多线程状况下就能避免争用。
在 JDK8 后,它摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法。同时加入了更多的辅助变量来提升并发度,具体内容仍是查看源码吧。

5八、Java 中 Semaphore 是什么?

Java 中的 Semaphore 是一种新的同步类,它是一个计数信号。从概念上讲,从概念上讲,信号量维护了一个许可集合。若有必要,在许可可用前会阻塞每个acquire(),而后再获取该许可。每一个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。可是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采起相应的行动。信号量经常用于多线程的代码中,好比数据库链接池。

5九、Java 线程池中 submit() 和 execute()方法有什么区别?

两个方法均可以向线程池提交任务,execute()方法的返回类型是 void,它定义在Executor 接口中。
而 submit()方法能够返回持有计算结果的 Future 对象,它定义在ExecutorService 接口中,它扩展了 Executor 接口,其它线程池类像ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有这些方法。

60、什么是阻塞式方法?

阻塞式方法是指程序会一直等待该方法完成期间不作其余事情,ServerSocket 的accept()方法就是一直等待客户端链接。这里的阻塞是指调用结果返回以前,当前线程会被挂起,直到获得结果以后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。


6一、Java 中的 ReadWriteLock 是什么?

读写锁是用来提高并发程序性能的锁分离技术的成果。

6二、volatile 变量和 atomic 变量有什么不一样?

Volatile 变量能够确保先行关系,即写操做会发生在后续的读操做以前, 但它并不能保证原子性。例如用 volatile 修饰 count 变量那么 count++ 操做就不是原子性的。
而 AtomicInteger 类提供的 atomic 方法可让这种操做具备原子性如getAndIncrement()方法会原子性的进行增量操做把当前值加一,其它数据类型和引用变量也能够进行类似操做。

6三、能够直接调用 Thread 类的 run ()方法么?

固然能够。可是若是咱们调用了 Thread 的 run()方法,它的行为就会和普通的方法同样,会在当前线程中执行。为了在新的线程中执行咱们的代码,必须使用Thread.start()方法。

6四、如何让正在运行的线程暂停一段时间?

咱们可使用 Thread 类的 Sleep()方法让线程暂停一段时间。须要注意的是,这并不会让线程终止,一旦从休眠中唤醒线程,线程的状态将会被改变为 Runnable,而且根据线程调度,它将获得执行。

6五、你对线程优先级的理解是什么?

每个线程都是有优先级的,通常来讲,高优先级的线程在运行时会具备优先权,但这依赖于线程调度的实现,这个实现是和操做系统相关的(OS dependent)。咱们能够定义线程的优先级,可是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个 int 变量(从 1-10),1 表明最低优先级,10 表明最高优先级。
java 的线程优先级调度会委托给操做系统去处理,因此与具体的操做系统优先级有关,如非特别须要,通常无需设置线程优先级。

6六、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?

线程调度器是一个操做系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦咱们建立一个线程并启动它,它的执行便依赖于线程调度器的实现。同上一个问题,线程调度并不受到 Java 虚拟机控制,因此由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。
时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU时间能够基于线程优先级或者线程等待的时间。

6七、你如何确保 main()方法所在的线程是 Java 程序最后结束的线程?

咱们可使用 Thread 类的 join()方法来确保全部程序建立的线程在 main()方法退出前结束。

6八、线程之间是如何通讯的?

当线程间是能够共享资源时,线程间通讯是协调它们的重要的手段。Object 类中wait()\notify()\notifyAll()方法能够用于线程间通讯关于资源的锁的状态。

6九、为何线程通讯的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?

Java 的每一个对象中都有一个锁(monitor,也能够成为监视器) 而且 wait(),notify()等方法用于等待对象的锁或者通知其余线程对象的监视器可用。在 Java 的线程中并无可供任何对象使用的锁和同步器。这就是为何这些方法是 Object 类的一部分,这样 Java 的每个类都有用于线程间通讯的基本方法。

70、为何 wait(), notify()和 notifyAll ()必须在同步方法或者同步块中被调用?

当一个线程须要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其余线程调用这个对象上的 notify()方法。一样的,当一个线程须要调用对象的 notify()方法时,它会释放这个对象的锁,以便其余在等待的线程就能够获得这个对象锁。因为全部的这些方法都须要线程持有对象的锁,这样就只能经过同步来实现,因此他们只能在同步方法或者同步块中被调用。

7一、为何 Thread 类的 sleep()和 yield ()方法是静态的?

Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行。因此在其余处于等待状态的线程上调用这些方法是没有意义的。这就是为何这些方法是静态的。它们能够在当前正在执行的线程中工做,并避免程序员错误的认为能够在其余非运行线程调用这些方法。

7二、如何确保线程安全?

在 Java 中能够有不少方法来保证线程安全——同步,使用原子类(atomic concurrent classes),实现并发锁,使用 volatile 关键字,使用不变类和线程安全类。

7三、同步方法和同步块,哪一个是更好的选择?

同步块是更好的选择,由于它不会锁住整个对象(固然你也可让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这一般会致使他们中止执行并须要等待得到这个对象上的锁。
同步块更要符合开放调用的原则,只在须要锁住的代码块锁住相应的对象,这样从侧面来讲也能够避免死锁。

7四、如何建立守护线程?

使用 Thread 类的 setDaemon(true)方法能够将线程设置为守护线程,须要注意的是,须要在调用 start()方法前调用这个方法,不然会抛出IllegalThreadStateException 异常。

7五、什么是 Java Timer 类?如何建立一个有特定时间间隔的任务?

java.util.Timer 是一个工具类,能够用于安排一个线程在将来的某个特定时间执行。Timer 类能够用安排一次性任务或者周期任务。
java.util.TimerTask 是一个实现了 Runnable 接口的抽象类,咱们须要去继承这个类来建立咱们本身的定时任务并使用 Timer 去安排它的执行。

最后

2019年常见的Java面试题总结了一份将近500页的pdf文档,欢迎关注个人公众号:程序员追风,领取这些整理的资料!

喜欢文章记得关注我点个赞哟,感谢支持!程序员

相关文章
相关标签/搜索