1、带着BAT大厂的面试问题去理解
请带着这些问题继续后文,会很大程度上帮助你更好的理解线程基础。
- 线程有哪几种状态? 分别说明从一种状态到另一种状态转变有哪些方式?
- 通常线程有哪几种使用方式?
- 基础线程机制有哪些?
- 线程的中断方式有哪些?
- 线程的互斥同步方式有哪些? 如何比较和选择?
- 线程之间有哪些协作方式?
2、线程状态转换
2.1、新建(New)
创建后尚未启动。
2.2、可运行(Runnable)
可能正在运行,也可能正在等待 CPU 时间片。
包含了操作系统线程状态中的 Running 和 Ready。
2.3、阻塞(Blocking)
等待获取一个排它锁,如果其线程释放了锁就会结束此状态。
2.4、无限期等待(Waiting)
等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
进入方法 | 退出方法 |
没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() / Object.notifyAll() |
没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
LockSupport.park() 方法 | - |
2.5、限期等待(Timed Waiting)
无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。
调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。
睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。
阻塞和等待的区别在于,阻塞是被动的,它是在等待获取一个排它锁。而等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。
进入方法 | 退出方法 |
Thread.sleep() 方法 | 时间结束 |
设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |
设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
LockSupport.parkNanos() 方法 | - |
LockSupport.parkUntil() 方法 | - |
2.6、死亡(Terminated)
可以是线程结束任务之后自己结束,或者产生了异常而结束。
3、线程使用方式
常见的Java线程的4种创建方式分别为:
- 实现 Runnable 接口;
- 通过ExecutorService和Callable实现有返回值的线程
- 继承 Thread 类;
- 基于线程池。
如下图所示:
实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。
3.1、实现 Runnable 接口
实现 Runnable 接口创建线程类,实现该类的 run()
方法,基于Java编程语言的规范,如果子类已经继承(extends)了一个类,就无法再直接继承Thread类,此时可以通过实现Runnable接口创建线程。具体的实现过程为:通过实现Runnable接口创建ChildrenClassThread 线程,实例化名称为childrenThread的线程实例,创建Thread类的实例并传入childrenThread线程实例,调用线程的start方法启动线程。
- 通过 Thread 调用 start() 方法来启动线程。
public class MyRunnable implements Runnable { public void run() { // ... } } public static void main(String[] args) { MyRunnable instance = new MyRunnable(); Thread thread = new Thread(instance); thread.start(); }
- 优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想;
- 缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法
3.2、实现 Callable 接口
- 通过 Callable 和 Future 接口创建线程。
- Callable接口,是一种让线程执行完成后,能够返回结果的。
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
public class MyCallable implements Callable<Integer> { public Integer call() { return 123; } } public static void main(String[] args) throws ExecutionException, InterruptedException { MyCallable mc = new MyCallable(); FutureTask<Integer> ft = new FutureTask<>(mc); Thread thread = new Thread(ft); thread.start(); System.out.println(ft.get()); }
3.3、继承 Thread 类
通过继承 Thread 类创建线程类,该子类重写Thread类的 run
方法,Thread类实现了Runnable接口并定义了操作线程的一些方法,我们可以通过继承Thread类的方式创建一个线程
- 当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。
public class MyThread extends Thread { public void run() { // ... } } public static void main(String[] args) { MyThread mt = new MyThread(); mt.start(); }
- 优点:编写简单,如果需要访问当前线程,无需使用
Thread.currentThread()
方法,直接使用this,即可获得当前线程; - 缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
3.4、使用线程池
- 使用线程池
- 具体见 JUC第十六讲:深入理解 Java 中的线程池
3.5、实现接口 VS 继承 Thread
实现接口会更好一些,因为:
- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
- 类可能只要求可执行就行,继承整个 Thread 类开销过大。
4、基础线程机制
4.1、Executor
Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。
主要有三种 Executor:
- CachedThreadPool: 一个任务创建一个线程;
- FixedThreadPool: 所有任务只能使用固定大小的线程;
- SingleThreadExecutor: 相当于大小为 1 的 FixedThreadPool。
public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) { executorService.execute(new MyRunnable()); } executorService.shutdown(); }
4.2、Daemon
守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
main() 属于非守护线程。
使用 setDaemon() 方法将一个线程设置为守护线程。
public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.setDaemon(true); }
4.3、sleep()
Thread.sleep(millisec)
方法会休眠当前正在执行的线程,millisec 单位为毫秒。
sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。
public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }
4.4、yield()
对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
public void run() { Thread.yield(); }
5、线程中断
一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束。
5.1、InterruptedException
通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。
对于以下代码,在 main() 中启动一个线程之后再中断它,由于线程中调用了 Thread.sleep() 方法,因此会抛出一个 InterruptedException,从而提前结束线程,不执行之后的语句。
public class InterruptExample { private static class MyThread1 extends Thread { @Override public void run() { try { Thread.sleep(2000); System.out.println("Thread run"); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { Thread thread1 = new MyThread1(); thread1.start(); thread1.interrupt(); // 下面这行代码不会被执行 System.out.println("Main run"); }
Main run java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at InterruptExample.lambda$main$0(InterruptExample.java:5) at InterruptExample$$Lambda$1/713338599.run(Unknown Source) at java.lang.Thread.run(Thread.java:745)
5.2、interrupted()
如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的interrupt() 方法就无法使线程提前结束。
但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。
public class InterruptExample { private static class MyThread2 extends Thread { @Override public void run() { while (!interrupted()) { // .. } System.out.println("Thread end"); } } } public static void main(String[] args) throws InterruptedException { Thread thread2 = new MyThread2(); thread2.start(); thread2.interrupt(); }
Thread end
5.3、Executor 的中断操作
调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。
以下使用 Lambda 创建线程,相当于创建了一个匿名内部线程。
public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> { try { Thread.sleep(2000); System.out.println("Thread run"); } catch (InterruptedException e) { e.printStackTrace(); } }); executorService.shutdownNow(); System.out.println("Main run"); }
Main run java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at ExecutorInterruptExample.lambda$main$0(ExecutorInterruptExample.java:9) at ExecutorInterruptExample$$Lambda$1/1160460865.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future<?> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。
Future<?> future = executorService.submit(() -> { // .. }); future.cancel(true);
6、线程互斥同步
Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。
6.1、synchronized
1. 同步一个代码块
public void func() { synchronized (this) { // ... } }
它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。
对于以下代码,使用 ExecutorService 执行了两个线程,由于调用的是同一个对象的同步代码块,因此这两个线程会进行同步,当一个线程进入同步语句块时,另一个线程就必须等待。
public class SynchronizedExample { public void func1() { synchronized (this) { for (int i = 0; i < 10; i++) { System.out.print(i + " "); } } } } public static void main(String[] args) { SynchronizedExample e1 = new SynchronizedExample(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> e1.func1()); executorService.execute(() -> e1.func1()); }
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
对于以下代码,两个线程调用了不同对象的同步代码块,因此这两个线程就不需要同步。从输出结果可以看出,两个线程交叉执行。
public static void main(String[] args) { SynchronizedExample e1 = new SynchronizedExample(); SynchronizedExample e2 = new SynchronizedExample(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> e1.func1()); executorService.execute(() -> e2.func1()); }
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
2. 同步一个方法
public synchronized void func () { // ... }
它和同步代码块一样,作用于同一个对象。
3. 同步一个类
public void func() { synchronized (SynchronizedExample.class) { // ... } }
作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。
public class SynchronizedExample { public void func2() { synchronized (SynchronizedExample.class) { for (int i = 0; i < 10; i++) { System.out.print(i + " "); } } } } public static void main(String[] args) { SynchronizedExample e1 = new SynchronizedExample(); SynchronizedExample e2 = new SynchronizedExample(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> e1.func2()); executorService.execute(() -> e2.func2()); }
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
4. 同步一个静态方法
public synchronized static void fun() { // ... }
作用于整个类。
6.2、ReentrantLock
ReentrantLock 是 java.util.concurrent(JUC)包中的锁。
public class LockExample { private Lock lock = new ReentrantLock(); public void func() { lock.lock(); try { for (int i = 0; i < 10; i++) { System.out.print(i + " "); } } finally { // 确保释放锁,从而避免发生死锁。 lock.unlock(); } } } public static void main(String[] args) { LockExample lockExample = new LockExample(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> lockExample.func()); executorService.execute(() -> lockExample.func()); }
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
6.3、比较
1. 锁的实现
synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。
2. 性能
新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。
3. 等待可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
ReentrantLock 可中断,而 synchronized 不行。
4. 公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。
synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。
5. 锁绑定多个条件
一个 ReentrantLock 可以同时绑定多个 Condition 对象。
6.4、使用选择
除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。
7、线程之间的协作 面试必备
当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。
7.1、join()
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。
public class JoinExample { private class A extends Thread { @Override public void run() { System.out.println("A"); } } private class B extends Thread { private A a; B(A a) { this.a = a; } @Override public void run() { try { a.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B"); } } public void test() { A a = new A(); B b = new B(a); b.start(); a.start(); } } public static void main(String[] args) { JoinExample example = new JoinExample(); example.test(); }
A B
7.2、Object的wait()/notify()/notifyAll()
调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
它们都属于 Object 的一部分,而不属于 Thread。
只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateExeception。
使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。
public class WaitNotifyExample { public synchronized void before() { System.out.println("before"); notifyAll(); } public synchronized void after() { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("after"); } } public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); WaitNotifyExample example = new WaitNotifyExample(); executorService.execute(() -> example.after()); executorService.execute(() -> example.before()); }
before after
wait() 和 sleep() 的区别
- wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
- wait() 会释放锁,sleep() 不会。
7.3、Condition的await()/signal()/signalAll()
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。
使用 Lock 来获取一个 Condition 对象。
public class AwaitSignalExample { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void before() { lock.lock(); try { System.out.println("before"); condition.signalAll(); } finally { lock.unlock(); } } public void after() { lock.lock(); try { condition.await(); System.out.println("after"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); AwaitSignalExample example = new AwaitSignalExample(); executorService.execute(() -> example.after()); executorService.execute(() -> example.before()); }
before after
7.4、LockSupport 的park()/unpark()
使用park/unpark实现线程同步
class MyThread extends Thread { private Object object; public MyThread(Object object) { this.object = object; } public void run() { System.out.println("before unpark"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 获取blocker System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object)); // 释放许可 LockSupport.unpark((Thread) object); // 休眠500ms,保证先执行park中的setBlocker(t, null); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 再次获取blocker System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object)); System.out.println("after unpark"); } } public class test { public static void main(String[] args) { MyThread myThread = new MyThread(Thread.currentThread()); myThread.start(); System.out.println("before park"); // 获取许可 LockSupport.park("ParkAndUnparkDemo"); System.out.println("after park"); } }
运行结果:
before park before unpark Blocker info ParkAndUnparkDemo after park Blocker info null after unpark
具体可以参考这篇文章:JUC第八讲:JUC锁: LockSupport详解
Action1:有ABC 3 个线程,线程C需要等待线程AB执行完成才能执行?
方法一:LockSupport + AtomicInteger
- 先执行线程C,用 park() 挂起线程C,线程A、B各自执行完成时,flag 减1并判断是否为0,若为0则用unpark( c )给线程C 颁发许可
public static void main(String[] args) { AtomicInteger flag = new AtomicInteger(2); Thread c = new Thread(()->{ System.out.println("线程C开启,等待线程A、B执行完成才继续执行"); LockSupport.park(); System.out.println("线程C开始执行"); }); c.start(); new Thread(()->{ System.out.println("线程A开始执行"); try { TimeUnit.SECONDS.sleep(new Random().nextInt(10)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程A执行完成"); if (flag.decrementAndGet() == 0){ //唤醒指定线程 LockSupport.unpark(c); } }).start(); new Thread(()->{ System.out.println("线程B开始执行"); try { TimeUnit.SECONDS.sleep(new Random().nextInt(10)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程B执行完成"); if (flag.decrementAndGet() == 0){ LockSupport.unpark(c); } }).start(); }
方法二:CountDownLatch
- CountDownLatch 有一个计数器,countDown方法 对计数器做减操作,await 方法等待计数器达到0。所有await的线程都会阻塞直到计数器为0或者等待线程中断或者超时
public static void main(String[] args) { CountDownLatch latch = new CountDownLatch(2); new Thread(() -> { System.out.println("线程A开始执行"); try { TimeUnit.SECONDS.sleep(new Random().nextInt(10)); latch.countDown(); } catch (Exception e) { e.printStackTrace(); } System.out.println("线程A执行完成"); }).start(); new Thread(() -> { System.out.println("线程B开始执行"); try { TimeUnit.SECONDS.sleep(new Random().nextInt(10)); latch.countDown(); } catch (Exception e) { e.printStackTrace(); } System.out.println("线程B执行完成"); }).start(); new Thread(() -> { System.out.println("线程C开启,等待线程A、B执行完成才继续执行"); try { latch.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("线程C执行完成"); }).start(); }
方法三:CyclicBarrier
CyclicBarrier 与 CountDownLatch 类似 ,它能阻塞一组线程全部到某个状态再同时执行。 CyclicBarrier 与 CountDownLatch 的关键区别在于,所有的线程必须全部到达位置,才能继续执行。CountDownLatch 用于等待事件,而 CyclicBarrier 用于等待其他线程,在任意一个线程没有完成之前,所有线程都不能继续执行。
public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(3); //只有所有线程执行到了 await(),所有线程才会继续往下执行 new Thread(() -> { System.out.println("线程A开始执行"); try { //执行业务 TimeUnit.SECONDS.sleep(new Random().nextInt(10)); System.out.println("线程A执行完成,等待其它线程一起冲破栅栏"); barrier.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("线程A执行完成"); }).start(); new Thread(() -> { System.out.println("线程B开始执行"); try { //执行业务 TimeUnit.SECONDS.sleep(new Random().nextInt(10)); System.out.println("线程B执行完成,等待其它线程一起冲破栅栏"); barrier.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println("线程B执行完成"); }).start(); new Thread(() -> { try { System.out.println("线程C开启,等待线程AB执行完成一起冲破栅栏"); barrier.await(); //执行业务 } catch (Exception e) { e.printStackTrace(); } System.out.println("线程C执行完成"); }).start(); }
Action2:一个线程 OOM 后,其他线程还能运行吗?(美团)
涉及的知识点:线程知识、jvm内存分配、作用域、gc
结论:能运行
分析堆溢出对应用带来的影响
代码如下:
public class JvmThread { public static void main(String[] args) { // 线程一 每隔1s,向集合里面增加1M的数据 Thread-0 new Thread(() -> { List<byte[]> list = new ArrayList<byte[]>(); while (true) { System.out.println(new Date().toString() + Thread.currentThread() + "=="); byte[] b = new byte[1024 * 1024 * 1]; list.add(b); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } }).start(); // 线程二 每隔1s打印一次线程信息 Thread-1 new Thread(() -> { while (true) { System.out.println(new Date().toString() + Thread.currentThread() + "=="); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
结果展示:
Wed Nov 07 14:42:18 CST 2018Thread[Thread-1,5,main]== Wed Nov 07 14:42:18 CST 2018Thread[Thread-0,5,main]== Wed Nov 07 14:42:19 CST 2018Thread[Thread-1,5,main]== Wed Nov 07 14:42:19 CST 2018Thread[Thread-0,5,main]== Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space at com.gosaint.util.JvmThread.lambda$main$0(JvmThread.java:21) at com.gosaint.util.JvmThread$$Lambda$1/521645586.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) Wed Nov 07 14:42:20 CST 2018Thread[Thread-1,5,main]== Wed Nov 07 14:42:21 CST 2018Thread[Thread-1,5,main]== Wed Nov 07 14:42:22 CST 2018Thread[Thread-1,5,main]==
JVM启动参数设置:堆最小值16M 堆最大值 32M
上图是JVM堆空间的变化。我们仔细观察一下在14:42:05~14:42:25之间曲线变化,你会发现使用堆的数量,突然间急剧下滑!这代表这一点,当一个线程抛出OOM异常后,它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行!
讲到这里大家应该懂了,此题的答案为一个线程溢出后,进程里的其他线程还能照常运行。注意了,这个例子我只演示了堆溢出的情况。如果是栈溢出,结论也是一样的,大家可自行通过代码测试。
总结:其实发生OOM的线程一般情况下会死亡,也就是会被终结掉,该线程持有的对象占用的heap都会被gc了,释放内存。因为发生OOM之前要进行gc,就算其他线程能够正常工作,也会因为频繁gc产生较大的影响。