1.刚才在写wait()的时候提到了sleep(),就说一下二者的区别。
| wait() | sleep() |
| 来自Object类 | 来自Thread类 |
| wait():会释放同步锁,让其他线程进入synchronized代码块执行。 | sleep():如果当前线程持有同步锁,那么sleep是不会让线程释放同步锁的。 |
| wait()只能在同步控制方法或者同步控制块里使用,否则会出现IllegaMonitorStateException异常 | sleep()可以在任何地方使用 |
| 恢复方式不同:两者会暂停当前线程,wait()需要在其他线程调用同一对象的nofify()notifyAll() | sleep()在时间到了会重新恢复 |
2.既然提到了sleep(),就说一下sleep()和yield()方法有什么区别?
| sleep() | yield() |
| 执行sleep()方法后进入超时等待(TIMED_WAITING)状态 | 而yield()方法进入就绪(REDAY)状态 |
| sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程运行的机会; | yield()方法只会给相同优先级或更高优先级的线程以运行的机会。 |
join()方法用法:
| join() |
| 用于等待当前线程终止。如果一个线程A执行了threadB.join()语句,那么就代表着当前线程A等待threadB线程终止之后才从threadB.join()返回继续执行自己的代码。 |
3.run()和start()方法的区别?
| run() | start() |
| 普通方法调用,在主线程中执行,不会新建一个线程来执行。 | 新启动一个线程,这时此线程处于就绪状态(可运行)并没有运行,一旦得到cpu时间片,就开始执行run()方法。 |
4.上边多次提到了运行状态,那就来说一下线程的状态流转
| 状态 | 解释 |
| NEW | 新建但是尚未启动的线程处于此状态,没有调用start()方法 |
| RUNNABLE | 包含就绪(READY)和运行中(RUNNING)两种状态。线程调用start()方法就会进入到就绪(READY)状态,等待获取cpu时间片,如果成功获取到cpu时间片,则会进入运行中(RUNNING)状态 |
| BLOCKED | 线程在进入同步方法、同步块(synchronized)时被阻塞,等待同步锁的线程处于此状态。 |
| WAITING | 无限期等待另一个线程执行特定操作的线程处于此状态,需要被唤醒,否则会一直等待下去例如Object.wait()需要等待另一个线程执行Object.notify()或者Object.notifyAll;对于Thread.join(),需要等待指定的线程终止。 |
| TIMED_WAITING | 在指定的时间内等待另一个线程执行某项操作的线程处于此状态。跟WAITING类似,区别在于该状态有超时时间参数,在超时时间到了之后会自动唤醒,避免了无限期的等待。 |
| TERMINATED | 执行完毕已经退出的线程处于此状态 |
总结:线程在给定的时间内只能处于一种状态,这些状态是虚拟机状态,不反映任何操作系统线程状态。
5.提到了线程,就说一下什么是并发和并行。
并发:两个或多个事件在同一时间间隔发生。(就是你这在吃饭你突然来电话了,你就接电话,然后接着去吃饭)
并行:两个或者多个事件在同一时刻发生。(一边打电话一边吃发)
6.既然提到了线程就说一下线程和进程的区别?
进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。
7.创建线程的几种方式
①. 继承Thread类创建线程类
-
定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
-
创建Thread子类的实例,即创建了线程对象。
-
调用线程对象的start()方法来启动该线程。
②. 通过Runnable接口创建线程类
-
定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
-
创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
-
调用线程对象的start()方法来启动该线程。
③. 通过Callable和Future创建线程
-
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
-
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
-
使用FutureTask对象作为Thread对象的target创建并启动新线程。
-
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
8.既然提到了Callable,就说一下Runnable和Callable的区别?
Runable:接口中的run()方法的返回值是void,它只是单纯的去执行run()方法中的代码而已.无法判断任务是否被线程池执行成功与否。
Callable:接口中call()方法是有返回值的,是一个泛型,和Future,FutureTask配合可以用来获取异步执行的结果。线程池会 返回一个 future 类型的对象,通过这个 future 对象可以判断任务是否执行成 功,并且可以通过 future 的 get()方法来获取返回值,get()方法会阻塞当前线 程直到任务完成,而使用 get(long timeout,TimeUnit unit) 方法则会阻塞 当前线程一段时间后立即返回,这时候有可能任务没有执行完。
9.synchonized各种加锁场景的作用范围(synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。)
| 作用于静态方法,锁住对象实例(this),每一个对象实例有一个锁 public synchronized void method(){}
|
| 作用于Lock.class,锁住的是Lock的Class对象,也是全局只有一个。 synchronized (Lock.Class) |
| 作用于静态方法,锁住的是类Class对象,因为Class的相关数据存储在永久代元空间,元空间是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁住所有调用该方法的线程。 public static synchronized voidmethod(){} |
| 作用于this,锁住的是对象实例,每一个对象实例有一个锁 synchronized (this){} |
| 作用于静态成员变量,锁住的是该静态成员变量对象,由于是静态变量,因此全局只有一个 public static Object monitor=new Objeac();
synchronized(monitor){} |
10.提到了Lock就说一下synchronized 与Lock的区别。
| synchronized | Lock |
| 是java中的关键字,是内置的语言实现 | 是一个接口 |
| 不需要手动获取锁和释放锁,再发生异常时,会自动释放锁,不会导致死锁现象发生 | 发生异常时,如果没有主动通过unLock()去释放锁,很可能会造成死锁现象,因此需要在finally块中释放锁 |
| 使用synchronized时,等待的线程会一直等待下去直到获取到锁。 | 使用更加灵活,可以有响应中断,有超时时间等 |
| 随着synchronized 这些年的优化性能没有明显差距,尽量使用synchronized ,如果synchronized 无法满足需求,才会选择Lock | |
11. synchronized和volatile有什么区别?
| synchronized | volatile |
| 可以修饰方法以及代码块(可以修饰变量) | volatile关键字只能用于变量 |
| 可能会发生阻塞 | 多线程访问不会发生阻塞 |
| 两者都能保证 | 能保证数据的可用性,但不能保证数据的原子性 |
| 解决多个线程之间访问资源的同步性 | 只要用于解决变量在多个线程之间的可见性 |
volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定比synchronized 关键字要好synchronized 关键字在 JavaSE1.6 之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引 入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。
12.提到了死锁就说一下什么是死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
13.提到了死锁,就说一下如何检测死锁
| 死锁的四个必要条件 |
| 互斥条件:进程对所分配到的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。 |
| 请求和保持条件:进程已经获得了至少一个资源,但又对其他资源发出请求,而该资源已被其他进程占有,此时该进程的请求被阻塞,但又对自己获得的资源保持不放。 |
| 不可剥夺条件:进程已获得的资源在未使用完毕之前,不可被其他进程强行剥夺,只能由自己释放。 |
| 环路等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn},其中 Pi 等待的资源被 P(i+1) 占有(i=0, 1, …, n-1),Pn 等待的资源被 P0占 有,如下图所示。 |
14.怎么预防死锁?
| 预防死锁的方式就是打破四个必要条件中的任意一个即可。 |
| 打破互斥条件:在系统里取消互斥。若资源不被一个进程独占使用,那么死锁是肯定不会发生的。但一般来说在所列的四个条件中,“互斥”条件是无法破坏的。因此,在死锁预防里主要是破坏其他几个必要条件,而不去涉及破坏“互斥”条件 |
| 打破请求和保持条件:1)采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待。 2)每个进程提出新的资源申请前,必须先释放它先前所占有的资源。 |
| 打破不可剥夺条件:当进程占有某些资源后又进一步申请其他资源而无法满足,则该进程必须释放它原来占有的资源。 |
| 打破环路等待条件:实现资源有序分配策略,将系统的所有资源统一编号,所有进程只能采用按序号递增的形式申请资源。 |
15.创建线程池有几种方式?
| new FixedThreadPool(int nThreads) | 创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未知预期的错误而结束时,线程池会补充一个新的线程。 |
| new CachedThreadPool() | 创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不受限制 |
| new SingleThreadExecutor() | 这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能够确保依照任务在队列中的顺序来串行执行。 |
| new ScheduledThreadPool(int corePoolSize) | 创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer |
16.线程池的核心属性
| threadFactory(线程工厂) | 用于创建工作线程的工厂 |
| corePoolSize(核心线程数) | 当线程池运行的线程少于 corePoolSize 时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态。 |
| workQueue(队列) | 用于保留任务并移交给工作线程的阻塞队列 |
| maximumPoolSize(最大线程数) | 线程池允许开启的最大线程数 |
| handler(拒绝策略) | 往线程池添加任务时,将在下面两种情况触发拒绝策略:1(线程池运行状态不是RUNNING)2(线程池已达到最大线程数,并且阻塞队列已满) |
| keepAliveTime(保持存活时间) | 如果线程池数超过corePoolSize,ze多余的线程空闲时间超过keepAliveTime时会被终止。 |
17.线程池都有哪种状态?
线程池有五种状态Running、ShutDown、Stop、Tidying、Terminated。
| RUNNING | (1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。 (02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0! |
| SHUTDOWN | (1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。 (2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。 |
| STOP | (1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。 (2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。 |
| TIDYING | (1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。 (2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。 |
| TERMINATED | (1) 状态说明:线程池彻底终止,就变成TERMINATED状态。 (2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。 |
18.线程池的执行流程(提交任务执行的execute()方法)
19.execute()方法和submit()方法的区别
-
接收的参数不一样
-
submit有返回值,而execute没有
-
submit方便Exception处理
20.线程池有哪些拒绝策略?
AbortPolicy:中止策略。默认的拒绝策略,直接抛出 RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。
DiscardPolicy:抛弃策略。什么都不做,直接抛弃被拒绝的任务。
DiscardOldestPolicy:抛弃最老策略。抛弃阻塞队列中最老的任务,相当于就是队列中下一个将要被执行的任务,然后重新提交被拒绝的任务。如果阻塞队列是一个优先队列,那么“抛弃最旧的”策略将导致抛弃优先级最高的任务,因此最好不要将该策略和优先级队列放在一起使用。
CallerRunsPolicy:调用者运行策略。在调用者线程中执行该任务。该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者(调用线程池执行任务的主线程),由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得线程池有时间来处理完正在执行的任务。
21.java中如何保证多线程的线程安全。
线程安全在三个方面体现:
-
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
-
可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
-
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。



