并发与并行
问:什么是并发、什么是并行?
答:并发(Concurrent):是一种构造程序的方式,把任务分解为一个个独立运行的小任务。通信是协调这些小任务的手段
并行(Parallel):以分组的方式,同时执行每一组并发任务。
两者的区别:并发是同时处理(dealing)很多的事情,并行是同时做(doing)很多的事情。
问:并发与并行的区别?
答:我认为并发是任务处理过程中面临的问题,将任务进行合理的分解,采用并行的方式解决问题。
线程与进程
问:什么是多线程?
答:如果现在同时有多个任务,则所有的系统的资源是共享的,被所有线程所公用,但是程序处理需要CPU,传统单核CPU来说同一个时间段会有多个程序执行,但是在同一个时间点上只能存在一个程序运行,也就是说所有的程序都要抢占CPU资源。但是当CPU已经发展到多核的状态了,在一个电脑上可能会存在多个CPU,这个时候就可以非常清楚的发现多线程操作间是如何进行并发的执行的。
问:多线程实现?
答:Java多线程实现方式主要有四种:1.继承Thread类;2.实现Runnable接口;3.实现Callable接口通过FutureTask包装器来创建Thread线程;4.使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。
问:线程的五种状态?
答:New,Runnable,Running,Blocked,Dead
1. 新建( new ):新创建了一个线程对象。
2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start () 方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。 3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行 程序代码。
4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。
问:阻塞的情况分三种:
答:(一). 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。
(二). 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占 用,则 JVM 会把该线程放入锁池( lock pool )中。
(三). 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。当 sleep ()状态超时、 join () 等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。 5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该 线程结束生命周期。死亡的线程不可再次复生。
问:进程与线程是什么?有什么区别与联系?
答:进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程:进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
进程和线程的主要差别在于它们是不同的操作系统资源管理方式,多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些,线程都是在进程的基础上并发同时运行。
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
线程池
问:自己设计线程池、submit() 和 execute()、线程池原理?
答:一个线程池的基本要素:
1.线程池一般需要一个线程管理类: ThreadPoolManager,其作用有:
1)提供创建一定数量的线程的方法。主线程调用该方法,从而创建线程。创建的线程执行自己的例程,线程的例程阻塞在任务抓取上。
2)提供对任务队列的操作的方法。主线程调用初始化任务队列的方法,然后在有任务的时候,调用提供的任务添加方法,将任务添入等待队列。当主线程调用任务的添加方法时,会触发等待的线程,从而使得阻塞的线程被唤醒,其抓取任务,并执行任务。
2.线程池需要一个任务队列: List<Task>,其作用有:
提供任务的增删方法。而且该任务队列需要进行排他处理,防止多个工作线程对该任务队列进行同时的抓取操作或者主线程的加入与工作线程的抓取的并发操作。
3.线程池需要一个类似信号量的通知机制:wait -notify:
工作线程调用wait阻塞在任务抓取上。主线程添加任务后,调用notify触发阻塞的线程。
4.线程池需要一个线程类:WorkThread,其作用有:
提供线程的例程。创建线程WorkThread后,需要抓取任务,并执行任务。这是线程的例程。
5.线程池需要一个任务类:Task,其作用有:提供线程抓取并执行的任务目标。
submit()方法,可以提供Future < T > 类型的返回值。
executor()方法,无返回值。
excute方法会抛出异常。
sumbit方法不会抛出异常。除非你调用Future.get()。
excute入参Runnable
submit入参可以为Callable
提交一个任务到线程池中,线程池的处理流程如下:
1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
问:为什么不允许使用 Executors 创建线程池?
答:在阿里巴巴java开发手册中明确指出,不允许使用Executors创建线程池,避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。java中BlockingQueue主要有两种实现,分别是ArrayBlockingQueue和LinkedBlockingQueue。ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。而LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE.
查看new SingleExecutor时的源码可以发现,在创建LinkedBlockingQueue时,并未指定容量。此时,LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出的问题。