1. 并行和并发有什么区别?
并行(Parallel):指两个或者多个事件在同一时刻发生,即同时做不同事的能力。例如垃圾回收时,多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指两个或多个事件在同一时间间隔内发生,即交替做不同事的能力,多线程是并发的一种形式。例如垃圾回收时,用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
2. 线程和进程的基本概念、线程的基本状态以及状态之间的关系?
一个线程是进程的一个顺序执行流程。一个进程中的全部线程共享同一个堆空间。线程本身有一个供程序执行时的栈,一个进程中可以包含多个线程。
线程的基本状态:新建、就绪、运行状态、阻塞状态、死亡状态
新建状态:利用NEW运算创建了线程对象,此时线程状态为新建状态,调用了新建状态线程的start方法,将线程提交给操作系统,准备执行,线程将进入到就绪状态。
就绪状态:由操作系统调度的一个线程,没有被系统分配到处理器上执行,一旦处理器有空闲,操作系统会将它放入处理器中执行,此时线程从就绪状态切换到运行时状态。
运行状态:线程正在运行的过程中,碰到调用Sleep方法,或者等待IO完成,或等待其他同步方法完成时,线程将会从运行状态,进入到阻塞状态。
死亡状态:线程一旦脱离阻塞状态时,将重新回到就绪状态,重新向下执行,最终进入到死亡状态。一旦线程对象是死亡状态,就只能被GC回收,不能再被调用。
3. 守护线程是什么?
守护线程又称为后台线程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件
正常创建的线程都是普通线程,或称为前台线程,守护线程与普通线程在使用上没有什么区别,但是他们有一个最主要的区别是在于进程的结束中。当一个进程中所有普通线程都结束时,那么进程就会结束。如果进程结束时还有守护线程在运行,那么这些守护线程就会被强制结束
在 Java 中垃圾回收线程就是特殊的守护线程
4. 创建线程有哪几种方式?
继承Thread类(真正意义上的线程类),是Runnable接口的实现。
实现Runnable接口,并重写里面的run方法。
使用Executor框架创建线程池。Executor框架是juc里提供的线程池的实现。
5. sleep 和 wait 有什么区别?
类的不同:sleep 来自 Thread,wait 来自 Object。
释放锁:sleep 不释放锁;wait 释放锁。
用法不同:sleep 时间到会自动恢复;wait 可以使用 notify/notifyAll直接唤醒。
6. 线程的 run 和 start 有什么区别?
start 方法用于启动线程,run 方法用于执行线程的运行时代码。
run 可以重复调用,而 start 只能调用一次。
第二次调用start 必然会抛出运行时异常
7. 创建线程池有哪几种方式?
newSingleThreadExecutor:它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;
newCachedThreadPool:它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
newSingleThreadScheduledExecutor:创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;
newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;
ThreadPoolExecutor:是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。
8. 在 Java 程序中怎么保证多线程的运行安全?
使用安全类,比如 Java. util. concurrent 下的类。
使用自动锁 synchronized。
使用手动锁 Lock。
9. 什么是死锁?怎么防止死锁?
当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
防止死锁方法:
尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
尽量使用 Java. util. concurrent 并发类代替自己手写锁。
尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
尽量减少同步的代码块。
10. synchronized 和 volatile 的区别是什么?
volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。