java并发编程基础面试总结02

接上一篇java并发编程知识总结01,继续总结一下,上一篇主要涉及了一些基本的概念,这一篇对基本概念会做一些延伸。

12.   什么是上下文切换?

          多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线 程使用。为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并进行轮转的方式。

        当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于 一次上下文切换。 概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。 上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。

       所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。 Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文 切换和模式切换的时间消耗非常少。

13. 如何在 Windows 和 Linux 上查找哪个线程cpu利用率最高?

     windows上面用任务管理器看,linux下可以用 top 这个工具看。

    找出cpu耗用厉害的进程pid, 终端执行top命令,然后按下shift+p (shift+m是找出消耗内存 最高)查找出cpu利用最厉害的pid号,根据上面第一步拿到的pid号,执行命令:top -H -p pid 。然后按下shift+p,查找出cpu利用率最厉害的 线程号,比如top -H -p 1328 将获取到的线程号转换成16进制,去百度转换一下就行 。使用jstack工具将进程信息打印输出,jstack pid号 > /tmp/t.dat,比如jstack 31365 > /tmp/t.dat 编辑/tmp/t.dat文件,查找线程号对应的信息。

14. 什么是线程死锁?

       死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的 一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或称系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。

      多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。 比如,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

15. 形成死锁的四个必要条件是什么 ?

  • 互斥条件:在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,就只能等 待,直至占有资源的进程用毕释放。
  • 占有且等待条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进 程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
  • 不可抢占条件:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过 来。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(比如一个进程集合,A在 等B,B在等C,C在等A)

16. 如何避免线程死锁?

  •  避免一个线程同时获得多个锁
  •  避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
  •  尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制

17. 创建线程的四种方式?

     继承 Thread 类;

public class MyThread extends Thread {
@Override
public void run() {
    System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
}

    实现 Runnable 接口;

public class MyRunnable implements Runnable {
@Override
public void run() {
    System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
}

    实现 Callable 接口;

public class MyCallable implements Callable<Integer> {
@Override
public Integer call() {
     System.out.println(Thread.currentThread().getName() + " call()方法执行中...");
     return 1;
}

    使用匿名内部类方式 

public class CreateRunnable {
public static void main(String[] args) {
//创建多线程创建开始
Thread thread = new Thread(new Runnable() {
   public void run() {
      for (int i = 0; i < 10; i++) {
      System.out.println("i:" + i);
      }
   }
  });
   thread.start();
}
}

18. 说一下 runnable 和 callable 有什么区别 相同点?

   都是接口,都可以编写多线程程序,都采用Thread.start()启动线程

  主要区别: Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、 FutureTask配合可以用来获取异步执行的结果 Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出 异常,可以获取异常信息 注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到, 此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

19. 线程的 run()和 start()有什么区别?

      每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,run()方法称为线程 体。通过调用Thread类的start()方法来启动一个线程。 start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。 start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待run方法体代码执 行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此 Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度 其它线程。 run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实 就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下 面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用 start()方法而不是run()方法。

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

     这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会 答不上来! new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪 状态,当分配到 时间片 后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线 程中执行它,所以这并不是多线程工作。 总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法 调用,还是在主线程里执行。

21. 什么是 Callable 和 Future?

    Callable 接口类似于 Runnable,从名字就可以看出来了,但是 Runnable 不会返回结果,并且无 法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,可以返回值,这个返回值 可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。 Future 接口表示异步任务,是一个可能还没有完成的异步任务的结果。所以说 Callable用于产生 结果,Future 用于获取结果。

22. 什么是 FutureTask?

     FutureTask 表示一个异步运算的任务。FutureTask 里面可以传入一个 Callable 的具体实现类,可 以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。只有当运算 完成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。一个 FutureTask 对象可以对调 用了 Callable 和 Runnable 的对象进行包装,由于 FutureTask 也是Runnable 接口的实现类,所 以 FutureTask 也可以放入线程池中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值