ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java面试题之Java并发

2022-02-25 20:03:36  阅读:163  来源: 互联网

标签:状态 面试题 volatile java Runnable 线程 Java 执行 方法


1. 线程和进程有什么区别?

根本区别 :进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位 资源开销 :每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计 数器( PC ),线程之间切换的开销小。 包含关系 如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的; 线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。 内存分配 同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死 掉。所以多进程要比多线程健壮。 执行过程 每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必 须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。

2.创建线程的三种方式的对比?

1)采用实现Runnable、Callable接口的方式创建多线程。 优势是: 线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。 在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的 情况,从而可以将CPU 、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。 劣势是: 编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread() 方法。 2)使用继承Thread类的方式创建多线程 优势是: 编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。 劣势是: 线程类已经继承了 Thread 类,所以不能再继承其他父类。 3)Runnable和Callable的区别
  • Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
  • Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
  • Call方法可以抛出异常,run方法不可以。
  • 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

3.线程的状态流转?

   线程的生命周期及五种基本状态:

Java 线程具有五中基本状态 1)新建状态(New) 当线程对象对创建后,即进入了新建状态,如: Thread t = new MyThread(); 2)就绪状态(Runnable) 当调用线程对象的 start() 方法( t.start(); ),线程即进入就绪状态。处于 就绪状态的线程,只是说明此线程已经做好了准备,随时等待 CPU 调度执行,并不是说执行了 t.start() 此 线程立即就会执行; 3)运行状态(Running) :当 CPU 开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进 入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行, 首先必须处于就绪状态中; 4)阻塞状态(Blocked) 处于运行状态中的线程由于某种原因,暂时放弃对 CPU 的使用权,停止执 行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU 调用以进入到运行状态。根据阻 塞产生的原因不同,阻塞状态又可以分为三种:
  1. 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
  2. 同步阻塞 — 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
  3. 其他阻塞 — 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5)死亡状态(Dead) :线程执行完了或者因异常退出了 run() 方法,该线程结束生命周期。

4.什么是线程死锁?如何避免死锁?

死锁 : 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

 

死锁必须具备以下四个条件:
  1. 互斥条件:该资源任意一个时刻只由一个线程占用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释 放资源。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何避免线程死锁? 只要破坏产生死锁的四个条件中的其中一个就可以了
  • 破坏互斥条件

这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)

  • 破坏请求与保持条件

一次性申请所有的资源。

  • 破坏不剥夺条件

占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。

  • 破坏循环等待条件

靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

  • 锁排序法:(必须回答出来的点)

指定获取锁的顺序,比如某个线程只有获得A锁和B锁,才能对某资源进行操作,在多线程条件下如何避免死锁? 通过指定锁的获取顺序,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可以避 免死锁。这通常被认为是解决死锁很好的一种方法。

  • 使用显式锁中的ReentrantLock.try(long,TimeUnit)来申请锁

5.为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法

  • new 一个 Thread,线程进入了新建状态; 调用start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,(调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间 片后就可以开始运行了。)这是真正的多线程工作。
  • 直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程 中执行它,所以这并不是多线程工作。 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。

6. Thread类中的yield方法有什么作用?

        Yield 方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法 ,而且只保证当前线程放弃CPU 占用而不能保证使其它线程一定能占用 CPU ,执行 yield() 的线程有可能在 进入到暂停状态后马上又被执行。

7.谈谈volatile的使用及其原理?

volatile 的两层语义 :           1 、 volatile 保证变量对所有线程的可见性:当 volatile 变量被修改,新值对所有线程会立即更新。或者理 解为多线程环境下使用volatile 修饰的变量的值一定是最新的。          2 、 jdk1.5 以后 volatile 完全避免了指令重排优化,实现了有序性。 volatile 的原理 :           获取 JIT (即时 Java 编译器,把字节码解释为机器语言发送给处理器)的汇编代码,发现 volatile 多加了 lock addl指令,这个操作相当于一个内存屏障,使得 lock 指令后的指令不能重排序到内存屏障前的位置。这也是为什么JDK1.5 以后可以使用双锁检测实现单例模式。           lock 前缀的另一层意义是使得本线程工作内存中的 volatile 变量值立即写入到主内存中,并且使得其他线 程共享的该volatile 变量无效化,这样其他线程必须重新从主内存中读取变量值。 具体原理见这篇文章: https://www.javazhiyin.com/61019.html

8.synchronized 关键字和 volatile 关键字的区别?

  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。
  • volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。实际开发中使用 synchronized 关键字的场景还是更多一些。
  • 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
  • volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。

9.sleep() 方法和 wait() 方法区别和共同点?

区别
  • sleep方法:是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进入可运行状态,等待CPU的到来。睡眠不释放锁(如果有的话)。
  • wait方法:是Object的方法,必须与synchronized关键字一起使用,线程进入阻塞状态,当notify 或者notifyall被调用后,会解除阻塞。但是,只有重新占用互斥锁之后才会进入可运行状态。睡眠 时,会释放互斥锁。
  • sleep 方法没有释放锁,而 wait 方法释放了锁 。
  • sleep 通常被用于暂停执行Wait 通常被用于线程间交互/通信
  • sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏 醒。wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法
相同
  • 两者都可以暂停线程的执行。

10 常见的对比

1.Runnable vs Callable

  • Callable仅在 Java 1.5 中引入,目的就是为了来处理Runnable不支持的用例。Callable 接口可以返回结果或抛出检查异常
  • Runnable 接口不会返回结果或抛出检查异常,
  • 如果任务不需要返回结果或抛出异常推荐使用 Runnable接口,这样代码看起来会更加简洁
  • 工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转(Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule))

2.execute() vs submit()

  • execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
  • submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功(可以通过 Future 的 get()方法来获取返回值,get()方法会 阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程 一段时间后立即返回,这时候有可能任务没有执行完。)

3.shutdown()VSshutdownNow()

  • shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队 列里的任务得执行完毕。
  • shutdownNow() :关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并 停止处理排队的任务并返回正在等待执行的 List。 shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线 程,所以无法响应中断的任务可能永远无法终

4.isTerminated() VS isShutdown()

  • isShutDown 当调用 shutdown() 方法后返回为 true。
  • isTerminated 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true

在此祝大家面试成功!!!

标签:状态,面试题,volatile,java,Runnable,线程,Java,执行,方法
来源: https://blog.csdn.net/weixin_46685039/article/details/123139754

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有