参考链接
进程
- 进程:具有独立功能的程序关于某个数据集合上的一次运行活动
- 进程有自己的 独立地址空间
- 每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段
- 资源分配 的基本单位,它是程序执行时的一个
实例
- 资源:CPU、内存等
- 程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行
- 每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵
线程
-
线程:
-
CPU调度和分派的基本单位, 程序执行的
最小单位
,它是进程的一个执行流 -
共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多
- 每个线程有自己的堆栈和局部变量
-
线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据
-
线程是在同一时间需要完成多项任务的时候实现的
进程 & 线程
-
一个进程可以由很多个线程组成
-
线程间共享进程的所有资源
-
【基本区别】
- 线程是处理器调度的基本单位,但是进程不是
-
【地址空间】
-
进程有自己的独立地址空间
- 每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段
-
线程是共享进程中的数据的,使用相同的地址空间
-
-
【资源拥有】
-
同一进程内的线程共享本进程的资源如内存、I/O、cpu 等
-
进程之间的资源是独立的
-
-
【执行过程】
- 每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口
- 但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
-
【性能】
- 进程切换时,消耗的资源大,效率高
- 涉及到频繁的切换时,使用线程要好于进程
- 如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
-
线程之间的 【通信】更方便
- 同一进程下的线程共享全局变量、静态变量等数据
- 进程之间的通信需要以通信的方式(IPC)进行
-
【健壮性】
- 一个进程崩溃后,在保护模式下不会对其他进程产生影响
- 但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮
多线程
处理多线程(独占)
- mutex(互斥锁) 是 semaphore的一种特殊情况(n=1时)
- 也就是说,完全可以用后者替代前者
- 但是,因为mutex 较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计
JAVA多线程实现的三种方式
-
继承Thread类实现多线程
public class MyThread extends Thread { public void run() { System.out.println("MyThread.run()"); } }
MyThread myThread1 = new MyThread(); myThread2.start();
-
实现Runnable接口方式实现多线程
public class MyThread implements Runnable { public void run() { System.out.println("MyThread.run()"); } public static void main(String[] args) { MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start(); } }
public class Thread implements Runnable { private Runnable target; public void run() { if (target != null) { target.run(); } } ... }
-
前面两种可以归结为一类
- 无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果
-
通过Callable和FutureTask创建线程
public class CallableFutureTest implements Callable<Integer>{ private int i; @Override public Integer call(){ for (i = 0; i < 2; i++) { System.out.println("接口创建线程: " + Thread.currentThread().getName() + " : " + i); } return i; } }
public static void main(String[] args) { System.out.println("main线程:" +Thread.currentThread().getName()); long begin = System.currentTimeMillis(); ExecutorService executorService = Executors.newCachedThreadPool(); //创建了实例 CallableFutureTest callableFutureTest = new CallableFutureTest(); //使用FutureTask类包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值 FutureTask<Integer> futureTask = new FutureTask<>(callableFutureTest); //使用FutureTask对象作为Thread对象的target创建并通过线程对象的start()方法启动线程 executorService.submit(futureTask); try { System.out.println("futureTask1: "+ futureTask1.get(); } catch (InterruptedException e) { e.printStackTrace(); }finally { executorService.shutdown(); } System.out.println("executor pool: " + executorService.isShutdown()); System.out.println("time: " + (System.currentTimeMillis() - begin)); } }
-
通过线程池创建线程
-
后面两种可以归结成一类
- 有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放在Object对象中
三种实现方式的区别
-
返回值
- 继承Thread类,run()方法没有返回值
- 实现Runnable接口和Callable接口方式基本相同
- 但Callable接口定义的call()方法具有返回值,可以声明抛出异常
-
继承
- 继承Thread类,不能再继承其他类
- 线程类实现Runnable和Callable接口可以继承其他类
-
访问当前线程
- 继承Thread类,需访问当前线程,无须用Thread.currentThread()方法,直接使用this即可获得当前线程
- 线程类实现Runnable和Callable接口
- 若访问当前线程,必须使用Thread.currentThread()方法
- 多个线程共享同一个target对象,适合多个相同线程来处理同一份资源的情况,从而将CPU、代码和数据分开,形成清晰的模型,较好地体现面向对象思想
JAVA多线程使用场景
参考链接
- 高并发
- 系统接受实现多用户多请求的高并发时,通过多线程来实现
- 线程后台处理大任务
- 一个程序是线性执行的,如果程序执行到要花大量时间处理的任务时,那主程序就得等待其执行完才能继续执行下面的。那用户就不得不等待它执行完
- 这时候可以开线程把花大量时间处理的任务放在线程处理,这样线程在后台处理时,主程序也可以继续执行下去,用户就不需要等待。线程执行完后执行回调函数。
- 大任务
- 大任务处理起来比较耗时,这时候可以起到多个线程并行加快处理(例如:分片上传)
多进程 & 多线程
-
多进程和多线程编程对于代码的并发执行,提升代码效率和缩短运行时间至关重要
-
多进程的程序要比多线程的程序健壮
- 一个线程死掉就等于整个进程死掉
-
【健壮性】多进程要比多线程健壮
- 一个进程崩溃后,在保护模式下不会对其他进程产生影响
- 一个线程崩溃整个进程都死掉
-
【并发性】
- 进程之间可以并发执行
- 同一个进程的多个线程之间也可并发执行
-
【作用】
- 多进程作用是提高CPU的使用率,而不是提高执行速度
- 多线程作用是提高应用程序的使用率,而不是提高执行速率
多CPU,多核,多进程,多线程
1、单CPU中进程只能是并发,多CPU计算机中进程可以并行。
2、单CPU单核中线程只能并发,单CPU多核中线程可以并行。