自己整理的一些多线程面试题,持续更新

多线程编程的好处

程序中启用多个线程并发执行以提高程序的效率,多个线程共享heap memory,创建多个线程充分利用CPU资源,创建多个线程执行任务比创建多个进程要好

用户线程和守护线程

用户线程是用户在java程序中创建的线程,称为用户线程;
守护线程是程序在后台执行且并不会阻止JVM终止的线程,当没有用户线程运行的时候,JVM关闭程序并且推出,但守护线程仍然继续执行;守护线程创建的子线程依然是守护线程

守护线程简介

- 守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程;如果用户线程已经全部退出运行了,只剩下守护线程存在,虚拟机也就退出了
- 在线程启动之前,通过setDaemon(true)方法来将线程设置为守护线程:
daemonThread.setDaemon(true);

- 守护线程的优先级很低,不能安排重要的任务
- 当全部用户线程结束时,虚拟机推出,守护线程也自动结束

简述线程生命周期

- java线程生命周期包括新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态
- 5个生命周期描述:
1. 新建(New):当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,JVM为其分配内存并初始化成员变量
2. 就绪(Runnable):调用线程的start()方法后,线程就处于就绪状态,JVM会为其创建方法调用栈和程序计数器,等待调度运行
3. 运行(Running):就绪中的线程获得了CPU时间片,开始执行run()方法的线程体
4. 阻塞(Blocked):当发生如下情况时,线程将会进入阻塞状态;从阻塞状态只能进入Runnable状态,无法直接进入Running状态
```
1 线程调用sleep()方法主动放弃所占用的处理器资源

2 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞

3 线程试图获得一个同步监视器(即对象的同步锁),但该同步监视器正被其他线程所持有

4 线程中运行的对象调用了wait()方法,线程进入了等待队列,在等待某个通知(notify or notifyALl())

5 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法
```
5. 死亡(Dead):线程会以如下3种方式结束,结束后就处于死亡状态:
```
1 run()或call()方法执行完成,线程正常结束

2 线程抛出一个未捕获的Exception或Error

3 直接调用该线程stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用
```

线程生命周期示意图:

简单描述这三个方法wait(), notify()和notifyAll()三个方法

- java线程并没有适用于所有对象的锁和同步器,因此需要Object类自己拥有这样的方法来保证线程间通信
- 当前线程调用对象的wait()方法,释放该对象的同步锁,进入该对象的wait queue;该方法必须在对象的同步方法和同步块中被调用;当其他线程调用notify()或notifyAll()时,wait queue中的某个线程或者全部线程被唤醒,进入对象的锁池;唤醒后的线程何时能够获得对象锁,完全凭运气

什么是上下文切换(context-switching)

存储和恢复CPU状态的过程,使得线程执行能够从中断点恢复执行。上下文切换是多任务操作系统和多线程环境的基本特征。

yield()方法简介

- 当前线程调用此方法,放弃获取的CPU时间片,由Running状态变回Runnable状态,让系统再次选择线程
- 实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中
- yield()方法不会导致线程Blocked
- 该方法是Thread的静态方法,当前线程调用Thread.yield()使其让出CPU时间片

sleep()方法简介

- 当前线程调用Thread.sleep(long millis)方法强制该线程休眠,进入Blocked状态
- 线程进入休眠后,不会释放之前已经获得的同步锁
- 休眠一定时间后线程回到Runnable状态

简单介绍Thread对象的join()方法

- 使用场景:如果需要让子线程subThread执行完再结束主线程,那么可以在main方法中直接调用子线程subThread.join()方法,让主线程进入wait状态,让subThread执行完,再重新执行主线程
- 原理:API中叙述如下:

public final void join() throws InterruptedException Waits for this thread to die. Throws: InterruptedException  - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.

意思就是说,join()可以让调用这个方法的线程进入wait状态直到子线程结束

从join()的JDK源码角度来理解:

//这个方法是个同步方法,就是说调用子线程的父线程必须拿到子线程对象的锁才能使join()起作用
public final synchronized void join(long millis) throws InterruptedException {  long base = System.currentTimeMillis();  //调用开始时间long now = 0;  if (millis < 0) {  //join方法中传入的毫秒值不能为负,否则报错throw new IllegalArgumentException("timeout value is negative");  }  //我们可以看到这里使用了while循环做判断的,然后调用wait方法的,所以说join方法的执行是完全通过wait方法实现的  //等待时间为0的时候,就是无限等待,直到线程死亡了(即线程执行完了)  if (millis == 0) {  //join()传入的毫秒值默认为0while (isAlive()) {  //只有当子线程为就绪、运行或阻塞状态时返回ture,新建但未start或者死亡状态返回false//调用该线程的join方法的父线程拿到锁之后,进入等待队列(wait queue),直到子线程执行结束(即子线程的isAlive()方法返回false)wait(0);  }  } else {  //如果父线程调用join()方法时传入了特定的毫秒值while (isAlive()) {  //同样是子线程状态为就绪、运行或阻塞状态时返回turelong delay = millis - now;if (delay <= 0) {  break;  }  wait(delay);  now = System.currentTimeMillis() - base;//父线程在等待队列中先等待delay时间,等delay时间过了就恢复(前提是子线程还没结束)}  }
}  

综上所述,父线程调用子线程的join方法目的就是让父线程暂停执行,待子线程结束后再恢复;或者制定join某个时间,当到时间后,不管子线程有没有执行网,父线程都会恢复

如何保证线程安全

- 同步锁
- 使用原子类(atomic concurrent class):该类位于java.util.concurrent.atomic包中,这些类保证在多线程环境下,当某个线程执行atomic的方法时不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个线程执行。Atomic类在软件层面上是非阻塞的,它的原子性其实是在硬件层面上借助相关的指令来保证的。使用Atomic类时不再需要人为添加synchronized关键字来保证同步性
- 实现并发锁
- 使用volatile关键字
- 使用不变类
- 使用线程安全类

同步方法和同步块,哪个是更好的选择?简述两种同步方式的区别

同步块是更好的选择,它可以指定线程需要获取哪个对象的同步锁才能执行对应的方法,不局限于某个具体的同步方法,灵活性较高
- 同步方法:通过将某个类的方法声明为同步方法,来保证同一时间只有一个线程能够获得该类实例对象的同步锁,来执行这个同步方法,与此同时,其他线程都无法访问这个同步方法以及同步块,但可以访问这个对象中的非同步部分;同步方法的锁是当前对象的锁
- 同步块:当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁,其他原理跟同步方法一致

- 两者的差异:
1. 灵活性:同步块不但可以指定获取当前对象的锁才能访问同步代码(synchronized(this)),还可以指定需要获取其他对象的锁才能访问(synchronized(otherObject));
2. 效率:同步的范围越多,越影响程序执行效率,因此用同步代码块可以尽量缩小影响范围,有时候只需要将一个方法中该同步的地方同步了就行了,比如运算

什么是ThreadLocal?

- 为了保证对象的全局变量的线程安全,当不想使用同步时,可以选择使用ThreadLocal类
- 单个线程使用ThreadLocal对象的get()set()方法去获取他们的默认值或者在线程内部改变他们的值
- 实现方式:当多个线程需要访问同一个共享变量时,将该变量保存到ThreadLocal对象的ThreadLocalMap映射表中保存为副本,key为保存该变量的线程实例本身,这样就能实现每个线程在使用该共享变量时不受其他线程影响

什么是Thread Group?

ThreadGroup API提供了两个功能:
- 获取线程组中处于活跃状态线程的列表
- 为线程设置未捕获异常处理器Uncaught exception handler(此功能在JDK1.5后已经被Thread类的setUncaughtExceptionHandler(UncaughtExceptionHandler eh)方法取代)

说说UncaughtExceptionHandler接口

- 当单线程的程序发生一个未捕获的异常时我们可以采用try....catch进行异常的捕获,但是在多线程环境中,线程抛出的异常是不能用try....catch捕获的,这样就有可能导致一些问题的出现,比如异常的时候无法回收一些系统资源,或者没有关闭的链接
- 使用方法:实现UncaughtExceptionHandler接口,重写uncaughtException方法,定制异常捕获后需要进行的操作

class ExceptionHandler implements UncaughtExceptionHandler
{  @Override  public void uncaughtException(Thread t, Throwable e)  {  System.out.println("==Exception: "+e.getMessage());  }
} 

- 启用该实现:

Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
Thread thread = new Thread(new Task());
thread.start();  

什么是Java线程转储(Thread Dump),如何得到它?

- 线程转储是一个JVM活动线程的列表,它对于分析系统瓶颈和死锁非常有用。有很多方法可以获取线程转储——使用Profiler,Kill -3命令,jstack工具等等

什么是死锁(DeadLock)?如何分析和避免死锁?写一个简单的demo描述什么是死锁

两个线程A和B,如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。JVM中,当一组JAVA线程发生死锁时,这两个线程就永远无法使用了。
例子:
- x对象和y对象是线程t1和t2的共享资源,t1需要获取y对象的同步锁执行完y的同步方法后才能释放x对象的同步锁,而t2需要获取x对象的同步锁执行完x的同步方法才能释放y对象的同步锁,因此造成了死锁:

class X{public synchronized void doFirst(Y y){System.out.println("当前运行:"+Thread.currentThread.getName()+"的doFirst()方法");Thread.sleep(1000);y.doSecond();}public synchronized void doSecond(){System.out.println("当前运行:"+Thread.currentThread.getName()+"的doSecond()方法");}
}class Y{public synchronized void doFirst(X x){System.out.println("当前运行:"+Thread.currentThread.getName()+"的doFirst()方法");Thread.sleep(1000);x.doSecond();}public synchronized void doSecond(){System.out.println("当前运行:"+Thread.currentThread.getName()+"的doSecond()方法");}
}public class Run implements Runnable{public int flag;static X x = new X(), Y y = new Y();Run run1 = new Run();Run run2 = new Run();run1.flag = 1;run2.flag = 0;Thread t1 = new Thread(run1);Thread t2 = new Thread(run2);t1.start();t2.start();public void run(){if(flag == 1){x.doFirst(y);}if(flag == 0){y.doFirst(x);}}
}

- 如何分析死锁:分析死锁需要查看Java应用程序的线程转储,找出那些状态为Blocked的线程和他们等待的资源。每个资源都有一个唯一的id,用这个id我们可以找出哪些线程已经拥有了它的对象锁:
1. 控制台输入jps获得当前JVM进程的pid
2. 输入jstack以及进程pid,打印当前进程堆栈,就可以发现哪些线程处于死锁状态及其等待的同步锁对象id

- 避免死锁:
1. 尽量让线程每次至多获得一个锁
2. 设计程序时尽量减小嵌套加锁的情况
3. 利用Lock功能代替synchronized来获取锁:object.lock.tryLock(),当获取到object对象的锁后才返回true,以此执行同步操作,最后使用object.lock.unLock()方法来手动释放同步锁

什么是线程池?如何创建一个Java线程池?

- 线程池管理了一组工作线程,便于对线程进行统一分配、调优和监控,同时还包括了一个用于放置等待执行的任务队列
- 使用线程池有如下好处:
1. 降低资源消耗:重复利用已经创建的线程降低线程创建和销毁造成的消耗
2. 提高相应速度:任务到达时不需要等到线程创建就能立即执行
3. 提高线程的可管理性
- 使用使用JDK自带的ExecutorService接口及其实现来进行线程池操作:
1. 通过ThreadPoolExecutor来创建线程池

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler);

2. 通过execute()方法或者submit()方法来执行任务,均传入Runnable对象,不同之处在于submit()可返回任务执行的结果

3. 通过shutdown()和shutdownNow()来关闭线程池

简述volatile关键字的作用及其原理

- 相比于synchronized,volatile是一个轻量级锁,使用volatile不会引起上下文切换和调度,提高了程序执行效率
- 通过volatile关键字修饰某个变量,可以保证所有使用这个变量的线程看到的变量值都是一致的,如果某线程对该变量进行了修改,那么其他线程可以立马看到修改结果,保证了线程可见性
- 实现原理简介:
并发编程有三个基本概念:原子性、可见性、有序性
1. 原子性:类似于数据库事务操作的原子性,某项操作执行的过程中要么成功要么失败,中途不能被其他因素(其他线程)打扰,volatile无法保证程序执行的原子性,仅能通过synchronized等同步方式来实现
2. 可见性:多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值,volatile就能保证可见性,对应到操作系统内存模型中,当一个线程修改共享变量后他会立即被更新到主内存,其他线程读取该共享变量时会直接读取主内存中的最新数据
3. 有序性:程序执行的顺序按照代码的先后顺序进行执行;JVM内存模型中,为了提高程序执行效率会对程序进行重排序,涉及volatile修饰变量的操作将不会进行重排序,以此保证了程序执行的有序性,让每个线程获取的变量都是正确的值

JVM底层的volatile机制是采用内存屏障来实现的

多线程面试题整理(1)相关推荐

  1. BATJ都爱问的Java多线程面试题整理

    今天给大家总结一下,面试中出镜率很高的几个多线程面试题,希望对大家学习和面试都能有所帮助.备注:文中的代码自己实现一遍的话效果会更佳哦! 一.面试中关于 synchronized 关键字的 5 连击 ...

  2. 【持续更新】java多线程面试题整理

    1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速.比如,如果一个线程完 ...

  3. futuretask java 并发请求_Java面试题整理一(侧重多线程并发)

    1..是否可以在static环境中访问非static变量? 答:static变量在Java中是属于类的,它在所有的实例中的值是一样的.当类被Java虚拟机载入的时候,会对static变量进行初始化.如 ...

  4. 计算机组成算术流水线,计算机组成系统结构试题整理.doc

    计算机组成系统结构试题整理 选择题(50分,每题2分,正确答案可能不只一个,可单选或复选) (CPU周期.机器周期)是内存读取一条指令字的最短时间. (多线程.多核)技术体现了计算机并行处理中的空间并 ...

  5. 史上最全 Java 多线程面试题及答案

    这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题. 这些多线程的问题,有些来源于各大网站.有些来源于自己的思考.可能有些问题网上有.可能有些问题对应的答案也有.也可能有些各位网友也 ...

  6. 2010 .NET面试题整理之基础篇

    2010 .NET面试题整理之基础篇 zhuan 开篇语:对于已有工作经验的朋友,也许面试题已显得不怎么重要,但是如果你应聘的还仅仅是个普通的程序员,相信在很多的公司都还是会先拿出一套面试题,可能对整 ...

  7. android笔试题整理

    笔试题整理 今天接到消息,说下个星期三.会陆陆续续的有公司来学校找暑假实习生.还没准备好啊,这就来啦?麻蛋 我慌的要死啊~ 1.Math.round(11.5)等于多少(). Math.round(- ...

  8. Java经典面试题整理及答案详解(八)

    简介: Java经典面试题第八节来啦!本节面试题包含了进程.线程.Object类.虚拟内存等相关内容,希望大家多多练习,早日拿下心仪offer- 了解更多: Java经典面试题整理及答案详解(一) J ...

  9. 金九银十,史上最强 Java 面试题整理。

    以下会重新整理所有 Java 系列面试题答案.及各大互联网公司的面试经验,会从以下几个方面汇总,本文会长期更新. Java 面试篇 史上最全 Java 面试题,带全部答案 史上最全 69 道 Spri ...

最新文章

  1. 【Spring】spring基于注解的声明式事务控制
  2. Hey!你的 CSDN 年度报告已出炉,请查收~
  3. TMG 2010 建立站对站***隧道
  4. EXCEL2013保存时提示Be careful!Parts of your document may include personal information...
  5. 使用商业模式的九大模块去分析京东的商业模式_商业模式—筑基篇(1)
  6. hdu4807枚举费用流
  7. Win:如何查看自己的电脑是否通过代理服务器进行上网
  8. 『ACM-算法-数据结构』信息竞赛进阶指南--树状数组 (模板)
  9. python股票接口_Python 从 sina 股票数据接口读取数据,并保存到 MySQL 数据库
  10. Entity Framework 复杂类型
  11. Java仓储物流项目_基于jsp的物流仓库管理系统-JavaEE实现物流仓库管理系统 - java项目源码...
  12. LLVM之clang
  13. 投资20亿元,又一个云手机基地诞生
  14. PS制作gif表情包
  15. C语言实现输出九九乘法表
  16. 安全专业委员会发言_安全专业委员会发言稿
  17. 红米k30至尊纪念版和红米k30i哪个好
  18. 【带着canvas去流浪(12)】用Three.js制作简易的MARVEL片头动画(上)
  19. 浏览器指纹技术与浏览器指纹破解办法
  20. 拉格朗日乘数法和KKT条件的直观解释

热门文章

  1. java短信接口源码_java免费短信接口开发源码
  2. 在Windows中搭建iPhone开发环境
  3. 编译器警告c4996
  4. 10分钟实现个人博客布置说说留言功能,Artitalk.js插件使用
  5. matlab保存读取矩阵
  6. 数据库对象(同义词)
  7. CPU 中控制器的作用
  8. 【Python全栈开发从入门到实战】持续更新中......
  9. PhotoShop制作彩色纸屑(转)
  10. 超快速实现 svg 转 png,jpg等格式