进程、线程、多线程
- 一、 进程、线程、多线程 的关系
- 1. 进程、线程多、线程
- 2. 线程的调度
- 二、 线程的实现方式
- 1. 继承 Thread
- 2. 实现 Runable
- 3. 实现 Callable
- 4. start 、 run
- 三、 线程池
- 1. 简介
- 2. 创建线程池
- 3. 4种封装好的 线程池
- 4. 线程计算数
- 四、 总结(重点)
一、 进程、线程、多线程 的关系
1. 进程、线程多、线程
- 进程 : 进程是程序的一次执行过程或是正在运行的一个程序,进程是资源分配的基本单位,是正在执行的应用程序。
- 线程 : 线程是操作系统能够进行运算调度的最小单位,线程是存在进程中的,是进程的实际运作单位,进程内最少存在一个线程。
- 多线程 : 顾名思义,多个线程
这里也需要知道一下 并发&并行
- 并发 : 多个CPU 同时执行多个任务
- 并行 : 一个CPU(采用时间片轮转的方式)同时执行多个线程
线程的生命周期: 新建 → 就绪 → 运行 → 阻塞(等待/超时等待两个状态) → 销毁
2. 线程的调度
线程的调度: Jvm 中会按照特定的机制为程序中的每个线程分配CPU的使用权,这种机制被称为线程的调度。
线程的调度有两种模型, 分时调度模型与抢占式调度模型
- 分时调度模型 :让所有的线程轮流获得CPI的使用权,并且平均分配每个线程占用的CPU 的时间片
- 抢占式调度模型(默认) : 优先级高的线程优先占用CPU的使用权, 优先级相同的线程,随机选择一个线程占用CPU的使用权。 Java默认采用抢占式调度模型,但在某种特定情况下会改变这种模型,由线程自己来控制CPU的调度
线程的优先级:在线程中有优先级的机制,线程的优先级用1~10之间的整数来表示, 数字越大则优先级越高。除了数字,Thread 类中还提供了 三个静态常量表示线程的优先级,分别是:
- MAX_PRIORITY 最高优先级 值 10
- NORM_PRIORITY 普通优先级 值 5 线程默认就是普通优先级
- MIN_PRIORITY 最低优先级 值 1
线程的优先级不是固定不变的,Thread类 提供了 查看/设置优先级的方法 getPriority()、setPriority(int newPriority) newPriority 接收1-10之间的参数
demo 请查看 目录 2.1 继承 Thread
注意: 优先级高的线程只是获得CPU执行的机会大,不代表就一定比优先级低的先执行。就是万事别说死 哈哈
二、 线程的实现方式
1. 继承 Thread
这里需要注意的是 Thread 也是实现了 Runnable 重写了run()方法
class ThreadTest extends Thread {
@Override
public void run() {
System.out.println("ThreadTest 线程名:" + Thread.currentThread().getName() + " 优先级:" + Thread.currentThread().getPriority());
}
}
public static void main(String[] args) {
//继承Thread 的线程
ThreadTest threadTest1 = new ThreadTest();
ThreadTest threadTest2 = new ThreadTest();
//设置优先级
threadTest1.setPriority(1);
threadTest2.setPriority(10);
//运行
threadTest1.start();
threadTest2.start();
}
2. 实现 Runable
class RunnableTest implements Runnable {
public void run() {
System.out.println("RunableTest 线程名:" + Thread.currentThread().getName() + " 优先级:" + Thread.currentThread().getPriority());
}
}
public static void main(String[] args) {
//实现Runnable 的线程
Thread thread = new Thread(new RunnableTest());
thread.start();
}
3. 实现 Callable
class CallableTest implements Callable {
public Object call() throws Exception {
System.out.println("CallableTest 线程名:" + Thread.currentThread().getName() + " 优先级:" + Thread.currentThread().getPriority());
return "CallableTest 返回值";
}
}
public static void main(String[] args) {
//实现Callable 的线程 需要使用 FutureTask 来接受返回值
FutureTask futureTask = new FutureTask(new CallableTest());
Thread callableThread = new Thread(futureTask);
callableThread.setName("callableThread");
callableThread.start();
try {
System.out.println("线程返回值打印: "+futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
4. start 、 run
- start(): 线程的start()方法是真正创建一个线程,线程创建完成获得CPU的使用权 会执行run()方法。
- run(): 只是一个类的普通方法,直接调用run()方法,程序依然只有一个线程,不会创建线程。
三、 线程池
1. 简介
背景: 由于线程的创建与销毁非常消耗资源,所以产生了线程池, 连接池也是这么个概念。
线程池的好处:减低资源消耗、提高响应速度、提高线程的可管理性。
2. 创建线程池
ThreadPoolExecutor 线程池 再初始化的过程中需要 以下的参数
参数名 | 类型 | 描述 |
corePoolSize | int | 核心线程数 |
maximumPoolSize | int | 最大线程数 |
keepAliveTime | long | 超过核心线程数的 线程的等待任务时间,超过时间就会销毁 |
unit | TimeUnit | 时间类型 |
workQueue | BlockingQueue | 工作队列 |
threadFactory | ThreadFactory | 线程创建工厂 默认DefaultThreadFactory |
handler | RejectedExecutionHandler | 拒绝策略处理器 默认AbortPolicy |
核心参数: 核心线程数 最大线程数 存活时间 时间类型 工作队列 线程创建工厂 拒绝策略
当前线程数> 核心线程数 工作队列没满 会存入工作队列
当前线程数> 核心线程数+队列数 创建新的线程
当前线程数> 工作队列+最大线程数 执行拒绝策略
BlockingQueue 双缓存队列。内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作,在保证并发安全的同事,提高了队列的存活效率。
- ArrayBlockingQueue : 规定大小的BlockingQueue, 其构造必须制定大小,其所含的对象是FIFO顺序排序的。
- LinkedBlockingQueue :大小不固定的BlockingQueue,其构造是指定大小,生成的BlockingQueue有大小限制,不指定 其大小有 Integer.MAX_VALUE来决定。其所含的对象是FIFO顺序排序的
- PriorityQueue : 类似于LinkedBlockingQueue ,但是其所含对象的排序不是FIFO,而是一句对象的自然顺序或者构造函数Comparator决定。
- SynchronizedQueue :特殊的BlockingQueue ,对其的操作必须是放和取交替完成。只能存在一个对象 , 再存的时候必须取出一个。
异常拒绝策略处理器
- new ThreadPoolExecutor.AbortPolicy() :默认使用 丢弃任务并抛出异常
- new ThreadPoolExecutor.DiscardPolicy():丢弃任务 不抛异常
- new ThreadPoolExecutor.DiscardOldestPolicy():丢弃队列最前边的任务,然后重新提交被拒绝的任务
- new ThreadPoolExecutor.CallerRunsPolicy():有调用线程 处理该任务
3. 4种封装好的 线程池
线程池创建 | 描述 |
Executors.newCachedThreadPool | 创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收线程,则新建线程。线程池为无限大,当无空闲线程被占用就会创建新线程 |
Executors.newFixedThreadPool | 创建一个定长线程池,可控制线程最大并发数,超过的线程会在队列中等待。线程池中的线程数量不会超过指定的最大线程数. |
Executros.newScheduledThreadPool | 创建一个定长线程池,支持定时及周期性执行任务 |
Executors.newSingleThreadExecutor | 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有的任务按照指定顺序执行。 |
4. 线程计算数
最大线程数的计算公式 N是CPU 核心数
- IO密集型应用 2N+1
- CPU密集型应用 N+1
假设我的服务器是4核的,且一般进行大数据运算,cpu消耗较大,那么线程池数量设置为5为最优
四、 总结(重点)
通过以上对线程、线程的了解,我们可以总结出 一个程序的运行会生成一个进程,一个进程中最少有一个线程存在,线程是实际的运作单位。
通过对并发、并行的了解,我们也可以看出:
- 在多线程情况下,单核CPU中多个线程是通过线程调度轮流执行的,并不是真正意义上的同一时间段执行多个线程,只不过时间很短我们无法感知,所以单核CPU同一时间段运行的线程只有一个。
- 多个CPU 执行是并行,比如说两个CPU,两个CPU中的线程是可以同一时间段执行的,分别在两个CPU中的两个线程不存在抢占CPU资源的情况,所以可以同时进行,不过这种情况叫做并行。
线程池的作用:是帮我们解决重复创建线程,线程的创建会消耗系统资源。
线程池参数的作用: 创建线程池我们首先需要知道线程创建工厂用来创建线程, 然后确定核心线程数(核心线程会一直存在),当前需要工作线程超过核心线程数时这个时候我们就需要工作队列来工作任务等待排队,当工作队列中满了的话 需要创建额外的工作线程来工作,这个时候是需要我们设置最大线程数来限制的,队列满跟工作线程已经是最大的情况下这个时候我们就需要指定拒绝策略。当核心线程数之外的线程都空闲了,这个时候我们就需要知道核心线程数之外的线程的存活时间到达存活时间销毁线程只留核心线程。