基础概念
程序是为完成特定任务、用某种语言编写的一组指令的集合。指一段静态的代码,是一个静态的概念
进程是具有一定独立功能程序的运行过程,是操作系统进行资源分配和调度的一个最小的独立单位,重点在系统调度和单独的单位
每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止
线程是进程中的一个独立执行线索,是进程中的一个实体,是CPU调度和分派的基本单位
同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器PC,线程切换开销小。(线程是cpu调度的最小单位)
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止
并行与并发
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时,而是轮流执行
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时
主线程
创建一个进程时,它的第一个线程称为主线程,它由系统自动生成
它是产生其他子线程的线程
通常它是最后完成执行,因为它执行各种关闭动作。注意这里不绝对
进程中线程之间的关系
线程不像进程,一个进程中的线程之间是没有父子之分的,都是平级关系
线程都是一样的, 退出了一个不会影响另外一个。但是所谓的主线程main可以在执行完处理逻辑后调用System.exit(0)。exit会让整个进程over终止,那所有线程自然都会退出
进程和线程的关系
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(主线程)
资源分配给进程,同一进程的所有线程共享该进程的所有资源
线程在执行过程中需要协作同步
CPU分给线程,即真正在处理机上运行的是线程
线程是指进程内的一个执行单元,也是进程内的可调度实体,两者都是动态的概念
线程和进程的区别
调度:线程是CPU调度和分配的基本单位,进程是系统资源分配和调度的基本单位
并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行,一个进程至少有一个线程(单进程单线程),一个线程必须隶属于某个进程
拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源
进程的基本操作
启动方法
ProcessBuilder pb=new ProcessBuilder("cmd","/c","ipconfig/all");
Process p=pb.start();
String cmd="cmd "+"/c "+"ipconfig/all";
Process process=Runtime.getRuntime().exec(cmd);
输入输出操作
获取进程的输出数据p.getInputStream()
获取进程的报错信息p.getErrorStream():InputStream
向进程传输数据p.getOutputStream():OutputStream
面试
进程的三大特征
独立性
动态性
并发性
僵尸进程
僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程
是对系统资源的浪费,必须解决
孤儿进程
孤儿进程是一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程
孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。---没有什么危害
多线程
概述
一个应用程序可以包含多个线程,每个线程执行特定的任务,并可与其他线程并发执行
多线程使系统的空转时间最少,提高CPU利用率,多线程编程环境用方便的模型隐藏CPU在任务间切换的细节
吞吐量,充分利用cpu资源,减少CPU空转时间
伸缩性,通过CPU核数来提升性能
线程
线程是比进程更小的执行单位
线程不能独立存在,必须存在于进程中
各线程间共享进程空间的数据,并利用这些共享单元来实现数据交换、实时通信与必要的同步操作
一个线程有它自己的入口和出口,以及一个顺序执行的序列
在许多情况中可以显式地使用线程以提高程序的性能、响应速度或组织。要用到多线程的主要是需要处理大量的IO操作时或处理的情况需要花大量的时间等,比如:读写文件、视频图像的采集、处理、显示、保存等
并发操作,避免阻塞和更有效利用资源。典型的例子有:在长时间工作的程序中使用工作线程避免界面失去响应。在网络下载程序中,使用多个线程提高对网络的使用效率,更快下载文件
对于http协议的了解,实现分段读取响应数据
下载工具的实现可以采用多线程、多个输入流和RandomAccessFile
并行,线程是处理器调度的最小单位。如果你的计算机配置了多个处理器或者内核,那么可以同时利用多个处理器同时计算,加快问题解决的速度
多线程机制会提高程序的运行效率吗
不一定,如果针对密集型计算的应用使用单线程避免多线程中的切换反而会提高代码的运行效率
资源限制的挑战,在并发编程时需要考虑到资源上的限制。如果受制于资源,整体程序的速度肯定会慢下来
多次运行会发现多个线程的执行顺序不一定相同,这就是多线程运行过程的不可重现性(执行的顺序不能保证),但是运行结果应该是一致的
使用多线程并不能增加CPU的处理能力,也不一定会提升CPU的吞吐量
基于Internet的应用有必要使用多线程
创建线程的4种方式
继承Thread,然后覆盖定义run()方法
最大的限制实际上就是Java的单根继承体系,这种方式基本不用
实现Runnable接口,直接new的Runnable对象并不是子线程
首先定义接口中的run()方法
MyRunnable mr=new MyRunnable();构建Runnable对象
Thread t=new Thread(mr);构建线程对象
t.start()启动线程
注意run方法没有返回值,所以如果需要保存线程的执行结果,需要编程自行解决(属性)
使用Callable和Future接口创建线程
MyCallable mc=new MyCallable();构建Callable对象
FutureTask ft=new FutureTask(mc); 构建实现了Future和Runnable接口的对象
Thread t=new Thread(ft); 创建线程对象
t.start();
call()方法可以返回执行结果,并允许抛出异常
引入Future接口的目的在于调用get方法阻塞当前线程的执行,直到指定的线程对象执行完毕并返回结果
一般用于需要线程返回数据的场景下
使用线程池创建线程,实际并没有编码创建线程,只是向线程池提交工作任务(Runnable和Callable),由线程池负责创建,并启动
线程池
在具体开发中直接使用ThreadPoolExecutor进行自定义参数创建,不建议使用Java通过Executors工具类提供的线程池的实现
从JDK1.5开始,Java API提供了Executor框架可以创建不同的线程池
可缓存线程池ExecutorService es=Executors.newCachedThreadPool();
new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
固定大小线程池ExecutorService es=Executors.newFixedThreadPool(5);
new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
单线程的线程池ExecutorService es=Executors.newSingleThreadExecutor();
new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
定期任务线程池
ScheduledExecutorService es=Executors.newScheduledThreadPool(5);
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
并行执行的连接池ExecutorService es=Executors.newWorkStealingPool();
new ForkJoinPool (Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
线程池的好处
重用存在的线程,减少对象创建、消亡的开销,性能佳
可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
提供定时执行、定期执行、单线程、并发数控制等功能
线程池的工作原理
1、线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则执行第二步。
2、线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里进行等待。如果工作队列满了,则执行第三步
3、线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务
线程池的相关配置参数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue)
corePoolSize线程池的核心大小
maximumPoolSize线程池中允许的最大线程数
workQueue阻塞任务队列
long keepAliveTime,TimeUnit unit用于定义空闲线程的存活的时长周期
常见问题总结
一般创建线程对象时不建议使用extends Thread方法? 单根继承
一般没有返回结果时建议使用Runnable接口,如果有返回值一般建议使用Callable接口
如果使用线程比较频繁建议使用线程池