目录
1. 线程和进程
1.1 进程
本质是一个运行的程序,是系统资源分配的基本单位,每一个进程都有一个独立的内存地址空间:(包括代码空间,数据空间),每一个进程内存空间,数据空间都是私有的,不能被其他的进程访问:进程与进程之间相互隔离的。
1.2 线程
线程是为了让CPU执行更高效提出的。
- 进程中的所有资源都被线程共享。
- 线程是CPU分配的基本单位。
- 线程之间操作共享资源进行通信。
- 线程比进程通信更简单、高效。
1)上下文切换
问题1: 现在的机器可以运行远大于CPU核心数的线程运行,操作资源是如何分配的?
CPU采用时间分片的模式,将CPU的时间片段轮流分配给多个线程执行;分配给每个线程的时间大概是几十毫秒ms,线程在CPU时间片执行,如果当前线程没有执行结束,CPU时间分片已经结束,此线程就会被挂起,下一个时间分片将会分给下一个线程,上一个线程等待唤醒。
问题2: 线程没有执行完毕,线程别挂起,等待下次被唤醒,继续完成任务!系统是怎么知道线程之前运行到哪里?从哪里开始执行呢??
CPU包含
- 寄存器(存储数据状态信息)
- 程序计数器:存储CPU正在执行的指令,以及执行下一条指令的位置。
CPU在执行任务时候,都必须依赖寄存器,程序计数器;这些东西就是CPU切换的开销,称之为上下文切换
2)线程的调度
- 抢占式调度
- 协同式调度
问题1:操作系统如何共享CPU时间分片,如何分配CPU执行权限?线程何时分到时间分片?线程分配多长时间?如何给重要的线程多分配一点时间,次要的少分配点时间呢?
(1)抢占式调度
并发线程下,所有线程会抢占时间分片,获取执行权限。有些线程执行时间长,造成线程堵塞等待,等待CPU资源。
JVM线程调度:抢占式调度模式
(2)协同式调用
一个线程执行完成后主动通知系统切换到另一个线程执行;(同步堵塞)
致命缺点:一个线程堵塞,则会导致整个进程堵塞,一个线程异常,则会导致整个进程崩溃
3)并行和并发
- 并发:一段时间内,多个线程轮流分配时间分片,抢占式执行。
- 并行:同一时刻,多个线程同时执行。
2. 多线程实践
2.1 多线程的实现方式
-
继承Thread类,实现多线程
public static void main(String[] args) { System.out.println("thread.........start..........."); /* 1. 测试Thread实现的多线程 */ // 创建多线程对象 ThreadA threadA = new ThreadA(); // 开启线程 threadA.start(); System.out.println("thread.........end............."); } public static class ThreadA extends Thread { /** * run方法是线程执行主体,多线程任务在run方法中执行。 */ @Override public void run() { log.info("继承Thread实现方式....."); // 业务代码执行 int i = 100 / 3; log.info("业务代码执行的结果为:{},线程的名称:{},线程的ID:{}", i, this.getName(), this.getId()); } }
总结:多线程线程的执行,是在主线程执行完毕后再执行的。(体现了异步的效果)
-
实现Runnable接口,实现多线程
(1)普通构建方式
public static void main(String[] args) { System.out.println("thread.........start..........."); /* 测试实现Runnable接口,实现多线程 */ // 创建多线程对象 Thread02 thread02 = new Thread02(); // 构建多线程对象 Thread thread = new Thread(thread02); // 开启线程 thread.start(); System.out.println("thread.........end............."); } public static class Thread02 implements Runnable { /** * run方法是线程执行主体,多线程任务在run方法中执行。 */ @Override public void run() { log.info("实现Runnable接口的实现方式....."); // 业务代码执行 int i = 100 / 3; log.info("业务代码执行的结果为:{}", i); } }
(2)匿名内部类实现方式
public static void main(String[] args) { System.out.println("thread.........start..........."); /* 测试实现Runnable接口,实现多线程 */ // 创建多线程对象 Runnable runnable = new Runnable() { @Override public void run() { log.info("实现Runnable接口的实现方式....."); // 业务代码执行 int i = 100 / 3; log.info("业务代码执行的结果为:{}", i); } }; // 构建多线程对象 Thread thread = new Thread(runnable); // 开启线程 thread.start(); System.out.println("thread.........end............."); }
(3)lambda表达式方式(推荐使用,但需要了解下lambda表达式实现原理)
public static void main(String[] args) { System.out.println("thread.........start..........."); /* 测试实现Runnable接口,实现多线程 */ // lambda创建多线程对象,直接传入Thread构造参数中,并开启线程 new Thread(() -> { log.info("实现Runnable接口的实现方式....."); // 业务代码执行 int i = 100 / 3; log.info("业务代码执行的结果为:{}", i); }).start(); System.out.println("thread.........end............."); }
lambda表达式的特点:
1. @FunctionalInterface:表示可以使用lambda表达式编程,此注解相当于一个标识
2. 接口如果只有一个方法(必须满足),即使没有上面的注解,也可以使用lambda表达式;程序会在后台自动识别。lambda表达式写作形式:
方法括号(有参写,无参不写) -> {业务执行方法体}
-
Callable + FutureTask实现多线程
jdk1.5后: 添加Callable接口,实现多线程,相较于Thread和Runnable接口而言,Callable有返回值。
@FunctionalInterface
:表示支持lambda表达式
public interface Callable<V>
:- 具有泛型的接口,只有一个call方法,call方法就是多线程执行业务主体。
- 方法有返回值,返回值类型就是指定的泛型类型。
疑问: 由于多线程执行必须和Thread有关系,Callable和Thread有什么关系呢?
-
因为
Runnable
的实现类FutureTask
即创建了Runnable
接口的构造函数,也创建了Callable
接口的构造函数 。 -
同时
Thread
类需要的是一个对Runnable
接口的实现类,而RunnableFuture
是继承于Runnable
的,FutureTask
是RunnableFuture
实现类,所以通过FutureTask
构造的多线程对象可以直接传入Thread
使用。。
(1)普通实现方式
public static void main(String[] args) { System.out.println("thread.........start..........."); // 创建多线程对象 Thread03 thread03 = new Thread03(); // 创建FutureTask对象,将thread03对象传递到构造函数中 FutureTask<Integer> task = new FutureTask<>(thread03); // 创建多线程对象 Thread thread = new Thread(task); // 开启线程执行 thread.start(); try { // 等待子线程执行结束后,获取返回结果,该代码为同步阻塞,必须等待异步线程执行结束,且返回结果后,才能继续往下执行。 Integer integer = task.get(); System.out.println("子程序运行结果为:" + integer); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } System.out.println("thread.........end............."); } public static class Thread03 implements Callable<Integer> { /** * 业务执行主体 * @return 返回值 * @throws Exception 异常捕获 */ @Override public Integer call() throws Exception { log.info("实现Callable接口的实现方式....."); // 业务代码执行 int i = 100 / 3; log.info("业务代码执行的结果为:{}", i); return i; } }
(2)匿名内部类实现方式
public static void main(String[] args) { System.out.println("thread.........start..........."); // 创建多线程对象 Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { log.info("实现Callable接口的实现方式....."); // 业务代码执行 int i = 100 / 3; log.info("业务代码执行的结果为:{}", i); return i; } }; // 创建FutureTask对象,将Callable接口对象传递到构造函数中 FutureTask<Integer> task = new FutureTask<>(callable); // 创建多线程对象 Thread thread = new Thread(task); // 开启线程执行 thread.start(); try { // 等待子线程执行结束后,获取返回结果,该代码为同步阻塞,必须等待异步线程执行结束,且返回结果后,才能继续往下执行。 Integer integer = task.get(); System.out.println("子程序运行结果为:" + integer); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } System.out.println("thread.........end............."); }
(3)lambda表达式实现方式
public static void main(String[] args) { System.out.println("thread.........start..........."); // 创建FutureTask对象,通过lambda表达式实现Callable接口 FutureTask<Integer> task = new FutureTask<>(() -> { log.info("实现Callable接口的实现方式....."); // 业务代码执行 int i = 100 / 3; log.info("业务代码执行的结果为:{}", i); return i; }); // 创建多线程对象 Thread thread = new Thread(task); // 开启线程执行 thread.start(); try { // 等待子线程执行结束后,获取返回结果,该代码为同步阻塞,必须等待异步线程执行结束,且返回结果后,才能继续往下执行。 Integer integer = task.get(); System.out.println("子程序运行结果为:" + integer); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e)