- 1.说说线程的生命周期及五种基本状态?
- 2.Java 中用到的线程调度算法是什么?
- 3.线程的调度策略
- 4.什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
- 5.请说出与线程同步以及线程调度相关的方法。
- 6.sleep() 和 wait() 有什么区别?
- 7.你是如何调用wait()方法的?使用 if 块还是循环?为什么?
- 8.为什么线程通信的方法wait(), notify()和notifyAll()被定义在Object 类里?
- 9.为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?
- 10.Thread 类中的 yield 方法有什么作用?
- 11.为什么 Thread 类的 sleep()和 yield ()方法是静态的?
- 12.线程的 sleep()方法和 yield()方法有什么区别?
- 13.如何停止一个正在运行的线程?
- 14.Java中interrupted和isInterrupted方法的区别?
- 15.什么是阻塞式方法?
- 16.Java 中你怎样唤醒一个阻塞的线程?
- 17.notify() 和 notifyAll() 有什么区别?
- 18.如何在两个线程间共享数据?
- 19.Java 如何实现多线程之间的通讯和协作?
- 20.同步方法和同步块,哪个是更好的选择?
- 21.什么是线程同步和线程互斥,有哪几种实现方式?
- 22.在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?
- 23.如果你提交任务时,线程池队列已满,这时会发生什么
- 24.什么叫线程安全?servlet 是线程安全吗?
- 25.在 Java 程序中怎么保证多线程的运行安全?
1.说说线程的生命周期及五种基本状态?

- 新建(new):新创建了一个线程对象。
- 可运行(runnable):线程对象创建后,当调用线程对象的 start()方法, 该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。
- 运行(running):可运行状态(runnable)的线程获得了cpu时间片 (timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入 口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
- 阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU 的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有 机会再次被 CPU 调用以进入到运行状态。
- 阻塞的情况分三种:
- 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列(waitting queue)中,使本线程进入到等待阻塞状态;
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),则JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态;
- 其他阻塞:通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进 入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处 理完毕时,线程重新转入就绪状态。
- 阻塞的情况分三种:
- 死亡(dead):线程run()、main()方法执行结束,或者因异常退出了 run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
2.Java 中用到的线程调度算法是什么?
计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看, 各个线程轮流获得 CPU 的使用权,分别执行各自的任务。
在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权。 有两种调度模型:分时调度模型和抢占式调度模型:
- 分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU 的时间片这个也比较好理解。
Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用 CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用 CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。
3.线程的调度策略
线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:
线程体中调用了 yield 方法让出了对 cpu 的占用权利
- 线程体中调用了 sleep 方法使线程进入睡眠状态
- 线程由于 IO 操作受到阻塞
- 另外一个更高优先级线程出现
- 在支持时间片的系统中,该线程的时间片用完
4.什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时 间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。
线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是更好的选择 (也就是说不要让你的程序依赖于线程的优先级)
5.请说出与线程同步以及线程调度相关的方法。
- wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;
- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
6.sleep() 和 wait() 有什么区别?
两者都可以暂停线程的执行
- 类的不同:sleep()是Thread线程类的静态方法,wait() 是 Object类的方法。
- 是否释放锁:sleep() 不释放锁;wait() 释放锁。
- 用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
- 用法不同:wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个
对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
7.你是如何调用wait()方法的?使用 if 块还是循环?为什么?
处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。
wait()方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:
synchronized(monitor){ // 判断条件谓词是否满足while(!locked){ // 等待唤醒monitor.wait();}// 其他业务处理}
8.为什么线程通信的方法wait(), notify()和notifyAll()被定义在Object 类里?
Java中,任何对象都可以作为锁,并且wait(),notify()等方法用于等待对象的锁或者唤醒线程,在Java的线程中并没有可提供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。wait(), notify()和 notifyAll()这些方法在同步代码块中调用。
有的人会说,既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面啊,新定义的线程继承于Thread类,也不需要重新定义wait()方法的实现。然而,这样做有一个非常大的问题,一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是不能实现,只是管理起来更加复杂。 综上所述,wait()、notify()和notifyAll()方法要定义在Object类中。
9.为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?
当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁, 接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这 个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。
10.Thread 类中的 yield 方法有什么作用?
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢?可能是当前线程,也可能是其他线程,看系统的分配了。
11.为什么 Thread 类的 sleep()和 yield ()方法是静态的?
线程的实现基本都是依靠Thread类的,如果这两个方法是实例方法,那么就会造成混乱,一个线程可以获取其他线程的引用,然后通过引用调用其他线程的sleep方法和yield方法,让其他线程让出CPU的使用权。如果每个线程都可以通过sleep方法和yield方法使其他线程让出cpu使用权,那么就会产生混乱。线程之间相互调用,谁也无法使用cpu使用权。
12.线程的 sleep()方法和 yield()方法有什么区别?
- sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
- 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready)状态;
- sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。
13.如何停止一个正在运行的线程?
在java中有以下3种方法可以终止正在运行的线程:
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
- 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及 resume一样都是过期作废的方法。
- 使用interrupt方法中断线程
14.Java中interrupted和isInterrupted方法的区别?
- interrupt:用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。 注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出 interruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状 态被置为“中断状态”,就会抛出中断异常。
- interrupted:是静态方法,查看当前中断信号是true还是false并且清除中断信号。如果一个线程被中断了,第一次调用 interrupted 则返回 true,第二次和后 面的就返回 false 了。
isInterrupted:查看当前中断信号是true还是false
15.什么是阻塞式方法?
阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket 的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前, 当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。
16.Java 中你怎样唤醒一个阻塞的线程?
首先 ,wait()、notify() 方法是针对对象的,调用任意对象的 wait()方法都将导致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取该对象的锁,直到获取成功才能往下执行;
其次,wait、notify 方法必须在 synchronized 块或方法中被调用,并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如此一来在调用 wait 之前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放。17.notify() 和 notifyAll() 有什么区别?
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
notifyAll() 会唤醒所有的线程,notify() 只会唤醒一个线程。
- notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
18.如何在两个线程间共享数据?
寻找一个共享变量即可,比如设置一个一个共享对象(或者将共享数据设置到对象中),这个共享对象内部的数据是线程安全的。
一般来说,共享变量要求变量本身是线程安全的,然后在线程内使用的时候,如果有对共享变量的复合操作,那么也得保证复合操作的线程安全性。
共享对象例子:
public class MyData {private int j = 0;public synchronized void add(){j++;System.out.println("线程"+Thread.currentThread().getName()+"j为:"+j);}public synchronized void dec(){j--;System.out.println("线程"+Thread.currentThread().getName()+"j为:"+j);}public int getData(){return j;}}
19.Java 如何实现多线程之间的通讯和协作?
可以通过中断和共享变量的方式实现线程间的通讯和协作。比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。
因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。 同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。 这种互相通信的过程就是线程间的协作。 Java中线程通信协作的最常见的两种方式:
- 1.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
- 2.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll() 线程间直接的数据交换
20.同步方法和同步块,哪个是更好的选择?
从理论上来讲,同步块更好一些,因为同步意味着不能并发执行,这会影响执行效率,所以同步的内容越少越好,而同步块可以尽可能减少同步的内容,因此使用同步块更好,当然如果整个方法都需要同步,那就使用同步方法也没啥问题。
21.什么是线程同步和线程互斥,有哪几种实现方式?
当一个线程对共享的数据进行操作时,应使之成为一个“原子操作”,即在没有完成相关操作之前,不允许其他线程打断它,否则,就会破坏数据的完整性,必然会得到错误的处理结果,这就是线程的同步。
在多线程应用中,考虑不同线程之间的数据同步和防止死锁。当两个或多个线程之间同时等待对方释放资源的时候就会形成线程之间的死锁。为了防止死锁的发生,需要通过同步来实现线程安全。
线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。
线程间的同步方法大体可分为两类:用户模式和内核模式。
- 内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态。
- 用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。
内核模式下的方法有:事件、信号量、互斥量。
实现线程同步的方法
- 同步代码方法:sychronized 关键字修饰的方法
- 同步代码块:sychronized 关键字修饰的代码块
- 使用特殊变量域volatile实现线程同步:volatile关键字为域变量的访问提供了一种免锁机制
- 使用重入锁实现线程同步:reentrantlock类是可冲入、互斥、实现了lock接口的锁与sychronized方法具有相同的基本行为和语义
22.在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?
在 java 虚拟机中,每个对象( Object 和 class )通过某种逻辑关联监视器,每个监视器和一个对象引用相关联,为了实现监视器的互斥功能,每个对象都关联着一 把锁。
一旦方法或者代码块被 synchronized 修饰,那么这个部分就放入了监视器的监视区域,确保一次只能有一个线程执行该部分的代码,线程在获取锁之前不允许执行该部分的代码
另外 java 还提供了显式监视器( Lock )和隐式监视器( synchronized )两种锁方案23.如果你提交任务时,线程池队列已满,这时会发生什么
这里区分一下:
- 如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务
- 如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到 ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据 maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来, ArrayBlockingQueue 继续满,那么则会使用拒绝策略 RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy
24.什么叫线程安全?servlet 是线程安全吗?
线程安全是编程中的术语,指某个方法在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
Servlet 不是线程安全的,servlet 是单实例多线程的,当多个线程同时访问同一 个方法,是不能保证共享变量的线程安全性的。
Struts2 的 action 是多实例多线程的,是线程安全的,每个请求过来都会 new 一个新的 action 分配给这个请求,请求完成后销毁。
SpringMVC 的 Controller 是线程安全的吗?不是的,和 Servlet 类似的处理流 程。
Struts2 好处是不用考虑线程安全问题;Servlet 和 SpringMVC 需要考虑线程安 全问题,但是性能可以提升不用处理太多的 gc,可以使用 ThreadLocal 来处理多线程的问题。25.在 Java 程序中怎么保证多线程的运行安全?
- 方法一:使用安全类,比如 java.util.concurrent 下的类,使用原子类 AtomicInteger
- 方法二:使用自动锁 synchronized。
- 方法三:使用手动锁 Lock。
手动锁 Java 示例代码如下:
Lock lock = new ReentrantLock();lock.lock();try {System. out. println("获得锁");} catch (Exception e) {// TODO: handle exception} finally {System. out. println("释放锁");lock.unlock();}
