1.java 中线程有哪些状态?
答:共有6中状态:
①初始状态 (NEW) :尚未启动的线程处于此状态。通常是新创建了线程,但还没有调用 start () 方法
②运行状态:Java 线程中将就绪(ready)和运行中(running)两种状态笼统的称为 "运行中"。比如说线程可运行线程池中,等待被调度选中,获取 CPU 的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得 CPU 时间片后变为运行中状态(running)
③阻塞状态 (BLOCKED):表示线程阻塞于锁。
④等待状态 (WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)
⑤超时等待状态 (TIMED_WAITING):进入该状态的线程需要等待其他线程在指定时间内做出一些特定动作(通知或中断),可以在指定的时间自行返回
⑥ 终止状态 (TERMINATED):表示该线程已经执行完毕,已退出的线程处理此状态
其转变如下:线程创建之后它将处于 NEW 状态,调用 start () 方法后开始运行,线程这时候处 于 READY 状 态 。 可 运 行 状 态 的 线 程 获 得 了 CPU 时 间 片 后 就 处于 RUNNING 状态。
线程执行 wait () 方法后,进入 WAITING 状态,此时需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep()或 wait ()可以将线程置于 TIME_WAITING
状态。当超时时间到达又会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED 状态。线程在执行Runnable 的 run () 方法之后将会进入到 TERMINATED 状态
2.线程状态 RUNNABLE 如何变为 BLOCKED?
答:线程状态变为阻塞有多种原因:可能调用 wait () 方法进入等待池,可能执行同步方法 / 同步代码块进入等锁池,可能调用了 sleep ()/join () 等待休眠或其他线程结束,可能发生了 I/O 中断等
3.为什么要使用线程池?
答:①降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
②提高响应速度。 当任务到达时,任务可以不需要等到线程创建就能立即执行
③提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
4.如何使用线程池?
答:工具类 Executors 提供了静态工厂方法以生成常用的线程池:
①newSingleThreadExecutor:创建一个单线程的线程池。如果该线程因为异常而结束,那么会有一个新的线程来替代它
②newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大值,一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
③newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(默认 60 秒不执行任务)的线程。当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线
程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
④newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求
5.java 线程池的核心参数?
答:Executors 也提供自定义的线程池构造方法,里面包括七个参数:
public ThreadPoolExecutor (
int corePoolSize, // 常驻线程数,即使空闲时仍保留在池中的线程数
int maximumPoolSize, // 线程池中允许的最大线程数
long keepAliveTime, // 存活时间。线程数比 corePoolSize 多且处于闲置状态的情况下,这些闲置的线程能存活的最大时间,为 0 表示会立即回收;
TimeUnit unit, //keepAliveTime 的单位
BlockingQueue workQueue, // 被提交尚未被执行的任务阻塞队
ThreadFactory threadFactory, // 创建线程的工厂
RejectedExecutionHandler handler // 饱和拒绝策略,当队列满了并且线程个数达到 maximunPoolSize 后采取的策略。目前支付四种:AbortPolicy (抛出异常),CallerRunsPolicy (调用者线程处理),DiscardOldestPolicy (直接丢弃任务,不予处理也不抛出异常),DiscardPolicy (默默丢弃,不抛出异常)
)
6.如何设置初始化线程池的大小?
答:可根据线程池中的线程处理任务的不同进行分别估计:
①CPU 密集型任务,这类任务需要大量的运算,通常 CPU 利用率很高,无阻塞,因此应配置尽可能少的线程数量,可设置为 CPU 核数 + 1;
②IO 密集型任务,这类任务有大量 IO 操作,伴随着大量线程被阻塞,可配置更多的线程数,通常可设置 CPU 核心数 * 2;
7.shutdown 和 shutdownNow 有什么区别?
答:shutdown,有序停止。将线程池状态设置为 SHUTDOWN。停止接收外部提交的任务;先前提交的任务务会执行(但不保证完成执行);
shutdownNow,尝试立即停止,将线程池状态设置为 STOP,相对来说是一种更优雅的关闭方法。停止接收外部提交的任务;不再处理队列里等待的任务;忽略队列里等待的任务;返回正在等待执行的任务列表。
8.ThreadLocal 的作用是什么?有什么风险?
答:作用是:提供每个线程存储自身专属的局部变量。
有什么风险:内存泄漏。ThreadLocal 被 ThreadLocalMap 中的 entry 的 key 弱引用,如果ThreadLocal 没有被强引用, 那么 GC 时 Entry 的 key 就会被回收,但是对应的 value 却不会回收。就会造成内存泄漏。
解决方案,每次使用完ThreadLocal,都调用它的 remove () 方法,清除数据。
9什么是进程?
答案:
是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。
10.什么是线程?
答案:线程是操作系统能够进行运算调度的最小单位。
它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
11.进程和线程的区别?
答案:
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。
线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
12.多线程和单线程有什么区别?
答:单线程程序:程序执行过程中只有一个有效操作的序列,不同操作之间都有明确的执行先后顺序,容易出现代码阻塞
多线程程序:有多个线程,线程间独立运行,能有效地避免代码阻塞,并且提高程序的运行性能
13.为什么要使用多线程?
答:①使用多线程可以减少程序的响应时间。 在单线程的情况下,如果某个程序很耗时或者陷入长时间等待(如等待网络响应),此时程序将不会相应鼠标和键盘等操作,使用多线程后,可以把这个耗时的线程分配到一个单独的线程去执行,从而是程序具备了更好的交互性。
②与进程相比,线程的创建和切换开销更小。 由于启动一个新的线程必须给这个线程分配独立的地址空间,建立许多数据结构来维护线程代码段、数据段等信息,而运行于同一个进程内的线程共享代码段、数据段,线程的启动或切换的开销就比进程要少很多。同时多线程在数据共享方面效率非常高。
③多CPU或多核心计算机本身就具有执行多线程的能力。 如果使用单个线程,将无法重复利用计算机资源,造成资源的巨大浪费。因此在多CPU计算机上使用多线程能提高CPU的利用率。
④使用多线程能简化程序的结构,使用程序便于理解和维护。 一个非常复杂的进程可以分成多个线程来执行。
14.什么是线程安全?
答:当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替运行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获取正确的结果,那这个对象是线程安全的。
15.为何要使用线程同步?
答:Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突。
因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
16.如何确保线程安全?
答:①对非安全的代码进行加锁控制;
②使用线程安全的类;
③多线程并发情况下,线程共享的变量改为方法级的局部变量
17.线程安全的级别?
答:①不可变。不可变的对象一定是线程安全的,并且永远也不需要额外的同步。Java类库中大多数基本数值类如Integer、String和BigInteger都是不可变的。
②无条件的线程安全。由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。如 Random 、ConcurrentHashMap、Concurrent集合、atomic。
③有条件的线程安全。有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。有条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器。
④非线程安全(线程兼容)。线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。如ArrayList HashMap。
⑤线程对立。线程对立是那些不管是否采用了同步措施,都不能在多线程环境中并发使用的代码。如如System.setOut()、System.runFinalizersOnExit()。
18.为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
答:new 一个 Thread,线程进入了新建状态(NEW);调用 start() 方法,会启动一个线程并使线程进入了就绪状态(READY),当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
19.使用多线程可能带来什么问题?
答:多线程的目的就是为了能提高程序的执行效率提高程序运行速度,但也可能会遇到很多问题:内存泄漏、上下文切换、死锁还有受限于硬件和软件的资源闲置问题。
20.线程池有哪些状态?
答:线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated。
①RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
②SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。
③STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
④TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
⑤TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
21.线程池中 execute()和submit()方法有什么区别?
答:①execute方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
②submit()方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功,
并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,
这时候有可能任务没有执行完。
22.实现Runnable接口和Callable接口的区别?
答:两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。
工具类Executors可以实现Runnable对象和Callable对象之间的相互转换。(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object resule))。
23.创建线程有哪些方式?
答:①继承 Thread 类创建线程类;
②通过 Runnable 接口创建线程类。
③通过 Callable 和 Future 创建线程。
④线程池
24.线程池的拒绝策略有哪些?
答:①AbortPolicy:默认的拒绝策略,直接抛出 RejectedExecutionException 异常;
②CallerRunsPolicy:既不会抛弃任务,也不会抛出异常,由调用线程处理该任务;
③DiscardPolicy: 直接丢弃任务,不予处理也不抛出异常。如果允许任务丢失,是最好的处理策略。
④DiscardOldestPolicy: 抛弃队列中等待最久的任务,然后把当前任务加入队列尝试再次提交(可能会再次失败,导致重复)。
25.synchronized关键字的作用?
答:synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。
如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,
这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。
JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
26.synchronized关键字如何使用?
答:①修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
②修饰静态方法: :也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。
所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,
因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
③修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
27.synchronized 关键字的底层原理是什么?
答:①synchronized 同步语句块的情况。 synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,
monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,
synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。
相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
②synchronized 修饰方法的的情况。 synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,
JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
28.说说 synchronized 关键字和 volatile 关键字的区别?
答:①volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些;
②多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞;
③volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证;
④volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。
29.介绍一下AtomicInteger 类的原理?
答:AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
30.JUC 包中的四种原子类。
1)基本类型, 使用原子的方式更新基本类型
(1)、AtomicInteger:整形原子类;
(2)、AtomicLong:长整型原子类;
(3)、AtomicBoolean:布尔型原子类
2)数组类型, 使用原子的方式更新数组里的某个元素
(1)、AtomicIntegerArray:整形数组原子类
(2)、AtomicLongArray:长整形数组原子类
(3)、AtomicReferenceArray:引用类型数组原子类
3)引用类型
(1)、AtomicReference:引用类型原子类
(2)、AtomicStampedReference:原子更新引用类型里的字段原子类
(3)、AtomicMarkableReference :原子更新带有标记位的引用类型
4)对象的属性修改类型
(1)、AtomicIntegerFieldUpdater:原子更新整形字段的更新器
(2)、AtomicLongFieldUpdater:原子更新长整形字段的更新器
(3)、AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
31.什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
答:线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。
线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择。
32.什么是竞态条件?你怎样发现和解决竞争?
答:当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。在临界区中使用适当的同步就可以避免竞态条件,实现方法有两种,一种是用synchronized,一种是用Lock显式锁实现。
33.你对线程优先级的理解是什么?
答:每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OSdependent)。
可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int变量(从1-10),1代表最低优先级,10代表最高优先级。
34.在多线程中,什么是上下文切换(context-switching)?
答:单核CPU也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)。
操作系统中,CPU时间分片切换到另一个就绪的线程,则需要保存当前线程的运行的位置,同时需要加载需要恢复线程的环境信息。
35.用户线程和守护线程有什么区别?
答:守护线程都是为JVM中所有非守护线程的运行提供便利服务: 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;
只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。
因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
36.如何创建守护线程?以及在什么场合来使用它?
答:任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(bool on);true则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常。
守护线程相当于后台管理者 比如 : 进行内存回收,垃圾清理等工作
37.线程中断是否能直接调用stop,为什么?
答:java提供的终止方法只有一个stop,但是不建议使用此方法,因为它有以下三个问题:
①stop方法是过时的 从Java编码规则来说,已经过时的方式不建议采用;
②stop方法会导致代码逻辑不完整,stop方法是一种"恶意"的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的.
③stop方法会破坏原子逻辑。多线程为了解决共享资源抢占的问题,使用了锁概念,避免资源不同步,但是正因此原因,stop方法却会带来更大的麻烦:它会丢弃所有的锁,导致原子逻辑受损。
38.了解 AQS 吗?说一说 AQS 的原理?有哪些应用?
答:
AQS原理:AQS(AbstractQueuedSynchronizer)核心思想是,如果被请求的资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
如果被请求的资源被占用,则需要一套线程阻塞等待以及唤醒分配的机制,该机制基于一个 FIFO(先进先出)的等待队列实现。
AQS 的应用:作为一个用来构建锁和同步器的框架,AQS 能简单且高效地构造出大量同步器,事实上 java.util.concurrent.concurrent 包内许多并发类都是基于 AQS 构建。
这些同步器从资源共享方式的方式来看,可以分为两类:Exclusive(独占):只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁;
Share( 共 享 ) : 多 个 线 程 可 同 时 执 行 , 如Semaphore/CountDownLatch/CyclicBarrier 等;
39.Semaphore/CountDownLatch/CyclicBarrier 这些同步器各自的原理和应用场景?
答:Semaphore ( 信号量),synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore (信号量) 可以指定多个线程同时访问某个资源。
CountDownLatch (闭锁): CountDownLatch 是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。比如有一个任务 A,它要等待其他 4 个
任务执行完毕之后才能执行,此时就可以利用 CountDownLatch 来实现这种功能了。
CyclicBarrier ( 循环栅栏): CyclicBarrier 和 CountDownLatch 非常类似,主要应用场景也差不多,它也可以实现线程间的技术等待,但是它的功能比CountDownLatch 更加复杂和强大。
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
简单地说,CountDownLatch 表示一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;CyclicBarrier 表示多个线程互相等待,直到达到同一个同步点(屏障),再继续一起执行。