JAVA面试常见问题之进程和线程篇

1、线程和进程的概念、并行和并发的概念

  • 进程:计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
  • 线程:有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

            

 

  • 并行:多个线程可以同时执行,每一个时间段,可以有多个线程同时执行。

          

  • 并发多个线程同时竞争一个位置,竞争到的才可以执行,每一个时间段只有一个线程在执行。

         

 2、创建线程的方式及实现

       创建线程的三种方式及实现代码:

  1. 继承Thread类创建
public class MyThread extends Thread{//继承Thread类
  public void run(){
  //重写run方法
  }
}

public class Main {
  public static void main(String[] args){
    new MyThread().start();//创建并启动线程
  }
}

         2. 实现Runnable接口创建

public class MyThread2 implements Runnable {//实现Runnable接口
  public void run(){
  //重写run方法
  }
}

public class Main {
  public static void main(String[] args){
    //创建并启动线程
    MyThread2 myThread=new MyThread2();
    Thread thread=new Thread(myThread);
    thread().start();
    //或者    new Thread(new MyThread2()).start();
  }
}

          3. 使用Callable和Futura创建

 3、进程间通信的方式

 进程间的通信(IPC)常用方式管道(无名管道和命名管道)、消息队列、信号量、共享存储、套接字(Socket)及Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

  • 管道
    • 无名管道管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。它是半双工的,具有固定的读端和写端。只能用于亲缘关系间的进程之间的通信。
    • FIFO:也称命名管道,它是一种文件类型。可以在无关的进程之间交换数据。有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
  • 消息队列是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。特点
    • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级
    • 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
    • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
  • 信号量:是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。特点
    • 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存
    • 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
    • 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
    • 支持信号量组。
  • 共享存储:指两个或多个进程共享一个给定的存储区。特点
    • 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
    • 因为多个进程可以同时操作,所以需要进行同步。
    • 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
  • 总结
    • 无名管道:速度慢,容量有限,只有父子进程能通讯
    • FIFO:任何进程间都能通讯,但速度慢
    • 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
    • 信号量:不能传递复杂消息,只能用来同步
    • 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

4、说说 CountDownLatch、CyclicBarrier 原理和区别

   原理: 参考:https://blog.csdn.net/wantflydacheng/article/details/81664035

    区别

 5、说说 Semaphore 工作原理,举例说明

       Semaphore主要用于控制当前活动线程数目,就如同停车场系统一般,而Semaphore则相当于看守的人,用于控制总共允许停车的停车位的个数,而对于每辆车来说就如同一个线程,线程需要通过acquire()方法获取许可,而release()释放许可。如果许可数达到最大活动数,那么调用acquire()之后,便进入等待队列,等待已获得许可的线程释放许可,从而使得多线程能够合理的运行

6、说说 Exchanger 原理

       Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。因此使用Exchanger的重点是成对的线程使用exchange()方法,当有一对线程达到了同步点,就会进行交换数据。因此该工具类的线程对象是成对的。
       Exchanger类提供了两个方法,String exchange(V x):用于交换,启动交换并等待另一个线程调用exchange;String exchange(V x,long timeout,TimeUnit unit):用于交换,启动交换并等待另一个线程调用exchange,并且设置最大等待时间,当等待时间超过timeout便停止等待。
       参考:https://blog.csdn.net/carson0408/article/details/79477280

7、ThreadLocal 原理分析,ThreadLocal为什么会出现OOM,出现的深层次原理

  • ThreadLocal是本地线程变量,用于保存某个线程的共享变量。方法包含get(),set(),remove(),initialValue()
  • 不正当的使用ThreadLocal时候会出现OOM,线程池的一个线程使用完ThreadLocal对象之后,再也不用,由于线程池中的线程不会退出,线程池中的线程的存在,同时ThreadLocal变量也会存在,占用内存!造成OOM溢出!
  • 出现OOM的深层次原理 参考:https://mp.weixin.qq.com/s/xqb1kUVtD82JuvlqWGV18w   第二部分  由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。使用线程池,线程不会被销毁,ThreadLocalMap也不会销毁,随着线程数量也来越多,ThreadLocalMap也会越来越多,占得内存越来越大,从而导致出现内存溢出。

 

8、讲讲线程池的实现原理

一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队  列中有任务了就取出来继续执行。

几个重要参数意义:

  • corePoolSize: 规定线程池有几个线程(worker)在运行。
  • maximumPoolSize: 当workQueue满了,不能添加任务的时候,这个参数才会生效。规定线程池最多只能有多少个线程(worker)在执行。
  • keepAliveTime: 超出corePoolSize大小的那些线程的生存时间,这些线程如果长时间没有执行任务并且超过了keepAliveTime设定的时间,就会消亡。
  • unit: 生存时间对于的单位
  • workQueue: 存放任务的队列
  • threadFactory: 创建线程的工厂
  • handler: 当workQueue已经满了,并且线程池线程数已经达到maximumPoolSize,将执行拒绝策略。

9、线程池的几种实现方式

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

10、线程的生命周期,状态是如何转移的

生命周期新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态

状态转移

1. 新建状态,当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值

2. 就绪状态,当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行

3. 运行状态,如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态

4. 阻塞状态,当处于运行状态的线程失去所占用资源之后,便进入阻塞状态

5. 在线程的生命周期当中,线程的各种状态的转换过程

参考:https://www.cnblogs.com/sunddenly/p/4106562.html    

 

 

 

     另有线程相关面试题:https://mp.weixin.qq.com/s/rW0H5oez1IfMOtaPBwWGoA(可能和上面的有重复)

 


           

 

posted @ 2019-01-03 13:57  品书读茶  阅读(476)  评论(0编辑  收藏  举报