多线程
线程与进程
进程:
- 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
进程:
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程。
- 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
线程调度
线程调度是指Java虚拟机按照特定机制为多个线程分配CPU的使用权。
分时调度:
- 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度:
- 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。 处于运行状态的线程会一直运行,直至它不得不放弃CPU。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高。
放弃CPU原因
- java虚拟机让当前线程暂时放弃CPU,转到就绪状态,使其它线程获得运行机会。
- 当前线程因为某些原因而进入阻塞状态 。
- 线程结束运行 。
注意:
线程的调度不是跨平台的,它 不仅仅取决于java虚拟机,还依赖于操作系统。在某些操作系统中,只要运行中的线程没有遇到阻塞,就不会放弃CPU;在某些操作系统中,即使线程没有遇到阻塞,也会运行一段时间后放弃CPU,给其它线程运行的机会。
让一个线程给另一线程运行机会:
-
调整各个线程的优先级
-
让处于运行状态的线程调用Thread.sleep()方法
-
让处于运行状态的线程调用Thread.yield()方法
-
让处于运行状态的线程调用另一个线程的join()方法
-
线程切换:不是所有的线程切换都需要进入内核模式
同步与异步
- 同步:排队执行,效率低安全。
- 异步:同时执行,效率高,数据不安全。
区别:
- 同步,是所有的操作都做完,才返回给用户结果。即写完数据库之后,再响应用户,用户体验不好。
- 异步,不用等所有操作都做完,就响应用户请求。即先响应用户请求,然后慢慢去写数据库,用户体验较好。为了避免短时间大量的数据库操作,就使用缓存机制,也就是消息队列。先将数据放入消息队列,然后再慢慢写入数据库。
并发与并行
三种解释:
- 并发:指两个或者多个事件在同一时间段内发生。
- 并行:指两个或者多个事件在同一时刻发生。
- 并行是在不同实体上的多个事件。
- 并发是在同一实体上的多个事件。
- 并行是在多台处理器上同时处理多个任务。如 hadoop 分布式集群。
- 并发是在一台处理器上“同时”处理多个任务。
并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)。
当有多个线程在操作时,如果系统只有一个 CPU,则它根本不可能真正同时进行一个以上的线程,它只能把 CPU 运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态.这种方式我们称之为并发(Concurrent)。
当系统有一个以上 CPU 时,则线程的操作有可能非并发。当一个 CPU 执行一个线程时,另一个 CPU 可以执行另一个线程,两个线程互不抢占 CPU 资源,可以同时进行,这种方式我们称之为并行(Parallel)。
Runnable 与 Callable
接口定义
//Callable接口
public interface Callable<V> {
V call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}
Callable使用步骤
- 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> { @Override public <T> call() throws Exception { return T; } }
- 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
- 通过Thread,启动线程
new Thread(future).start();
Runnable 与 Callable的相同点
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
Runnable 与 Callable的不同点
- Runnable没有返回值;Callable可以返回执行结果
- Callable接口的call()允许抛出异常;Runnable的run()不能抛出
Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
线程池Executors
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
好处
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性
分类:四种线程池 . ExecutorService
-
缓存线程池
/** * 缓存线程池. * (长度无限制) * 执行流程: * 1. 判断线程池是否存在空闲线程 * 2. 存在则使用 * 3. 不存在,则创建线程 并放入线程池, 然后使用 */ ExecutorService service = Executors.newCachedThreadPool(); //向线程池中 加入 新的任务 service.execute(new Runnable() { @Override public void run () { System.out.println("线程的名称:" + Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run () { System.out.println("线程的名称:" + Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run () { System.out.println("线程的名称:" + Thread.currentThread().getName()); } });
-
定长线程池
/** * 定长线程池. * (长度是指定的数值) * 执行流程: * 1. 判断线程池是否存在空闲线程 * 2. 存在则使用 * 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 * 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 */ ExecutorService service = Executors.newFixedThreadPool(2); service.execute(new Runnable() { @Override public void run () { System.out.println("线程的名称:" + Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run () { System.out.println("线程的名称:" + Thread.currentThread().getName()); } });
-
单线程线程池
效果与定长线程池 创建时传入数值1 效果一致. /** * 单线程线程池. * 执行流程: * 1. 判断线程池 的那个线程 是否空闲 * 2. 空闲则使用 * 4. 不空闲,则等待 池中的单个线程空闲后 使用 */ ExecutorService service = Executors.newSingleThreadExecutor(); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } });
-
周期性任务定长线程池
public static void main(String[] args) { /** * 周期任务 定长线程池. * 执行流程: * 1. 判断线程池是否存在空闲线程 * 2. 存在则使用 * 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 * 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 * * 周期性任务执行时: * 定时执行, 当某个时机触发时, 自动执行某任务 . */ ScheduledExecutorService service = Executors.newScheduledThreadPool(2); /** * 定时执行 * 参数1. runnable类型的任务 * 参数2. 时长数字 * 参数3. 时长数字的单位 */ /*service.schedule(new Runnable() { @Override public void run() { System.out.println("俩人相视一笑~ 嘿嘿嘿"); } },5,TimeUnit.SECONDS); */ /** * 周期执行 * 参数1. runnable类型的任务 * 参数2. 时长数字(延迟执行的时长) * 参数3. 周期时长(每次执行的间隔时间) * 参数4. 时长数字的单位 */ service.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("俩人相视一笑~ 嘿嘿嘿"); } }, 5, 2, TimeUnit.SECONDS); }