多线程
一、 什么是多线程?
多线程是一种编程特性,它允许同时执行多个线程完成任务。线程是程序中一个执行路径,每个线程都可以同时运行代码,使得
多核CPU的性能被榨干。
生活上多线程是什么,一个办公室有很多的员工同时工作,每个人都处理工作,一个人正在写前端页面,一个人正在对接支付接口,还有一个人正在
测试写好接口随便写着产品报告
什么是进程
进程就是运行在操作系统上程序,也可以说是正在执行的实例,就是正在运行程序。比如QQ微信等软件
什么是线程
线程算是进程的实例,它是程序中的执行路径,每一个JAVA程序至少有三个线程--main主线程,Gc回收机制的线程,异常处理的线程
当一个Java程序启动时,JVM会创建主线程,并且在该线程中调用程序的main()方法。
单线程
生活上来说:
就是你有一个洗衣机,但是你有三批衣服需要清洗,
你不能直接把三批衣服直接放入洗衣机去洗,你只能一批一批的放入洗衣机去清洗,单线程简单来说只能做一件事情,完成这一件事情在去做下一件事情.
编程上来说:
什么是单程线程,规定一个程序或者一个应用只能执行一次任务,前面的任务没有完成不能继续完
成节接下来的任务,任务完成后,线程会进入等待状态,
单线程的特点是,被调用的方法执行完毕后当前方法才可能完成,前一个方法完成后才进行下一个方法。这是一种顺序调用。
多线程
生活上来说:
在一个快餐店里,多名员工并行工作,以提高效率。例如,
一人负责点单,一人负责烹饪,另一人负责打包。这样,即使是多个客户同时到来,快餐店也能同时处
理多个订单,大大提高服务速度和效率。
编程上来说:
一个网络服务器,要同时处理多个用户的请求的时候会使用多线程来处理,当用户请求到达了的时候,它就会开辟一个新线程来处理该请求,
这就允许服务器可以处理多个请求,
当程序同时用多个线程调用不同方法时,并不是像单线程一样的顺序调用,而是启动独立的线程之后立刻返回,各线程运行从run()方法开始,当run()结束后线程运行结束。
注意:真正的多线程指的是多CPU(即多核),如果只有单个CPU时,多线程是模拟实现的,在同一个时间点上,CPU只做一件事情。会产生二个非常的概念就是并行和并发
多线程的并行与并发
——————————————————
并行(parallel)是同一时间动手做(doing)多件事情的能力;**
从生活上来说
就拿做饭来举例 厨师只负责炒菜,杂工就负责切菜,服务员就负责上菜,他们几个互不干拢,在同一时间做了多件事情这就是并行
(想当于多核cpu同一时间应对多件事情)
从编程上
网络服务器要同时处理多个用户的请求,当用户的请求发送到服务器的时候,服务器就生成一条线程专门为同一类型请求的
用户进行处理,如果请求服务不一致则创建新一条线程
并发(concurrent)是同一时间应对(dealing with)多件事情的能力)
从生活上来说:
你家只有一台洗衣机,但是你有好几堆衣服需要清洗,洗衣机只能塞下一堆,你等上一堆完成,再放下一堆,这就是并发必须等待上一件完成.
(想当于1个cpu同一时间应对多件事情)
从编程上来讲
在软件开发中,一个典型的并发应用是一个网络服务器,它需要处理来自成百上千个客户端的并发请求。服
务器可能会采用多线程或异步编程技术来同时处理这些请求。
例如,一个线程可以处理数据库查询,同时另一个线程处理文件读写操作,而第三个线程处理网络数据的发送和接收。
线程创建
1、继承java.lang.Thread类
// 继承 Thread 类
public class Runner extends Thread() {
// 重写 run 方法
@Override
public void run() {
System.out.println("继承了Thread,重写了线程的run方法");
}
}
// 定义一个 Test 类进行测试
public class Test{
public static void main(String[] args){
// 第二:创建线程对象(父类引用指向子类对象)
// 也可以 Runner runner = new Runner();
// Runner t = new Runner() ;
Thread t=new Runner();
// 第三:启动线程
t.start();
}
}
为什么这里使用的方法是Start 不是 run 方法呢,因为 Start方法被调用之后会创建新线程,然而run方法只是普通的方法调用罢了.
2、实现接口java.lang.Runnable
public class Rummble implements Runnable() {
public void run(){
System.out.println("继承了Thread,重写了线程的run方法");
}
}
public class Test() {
public static void main(String[] args) {
// 创建Runner对象
Rummble r = new Rummble();
// 创建线程
Thread thread = new Thread(r);
// 调用线程
thread.start();
}
}
两种实现对比,实现Runnable接口有以下好处
1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效的分离,较好地体现了面向对象的设计思想;
2)可以避免由于Java的单继承特性带来的局限;
3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码(run方法体)来自同一个类的实例(实现Runnable接口的对象)时,即称它们共享相同代码。
4)Thread类实际上是Runnable接口的实现类
3 实现 callable 类接口
第一步:实现 Callable 接口 并重写call方法
// 指定泛型
// 自定为String
public class MyCallble implements Callable<String> {
@verride
public String call() {
System.out.println("实现了Callable接口,重写了call方法,并返回了一个参数");
return "程序梦爆款";
}
第二步:
第三:创建FutureTask对象
Future接口的唯一实现类
同时也实现了Runnable接口
能够接收 Callable 类型的参数,用来处理有返回结果的情况
get():获取值
第四:创建线程对象
第五:启动线程
public static void main(String ags[]) {
// 创建 Callable 对象
MyCallable myCallable = new MyCallable();
// 创建 FutureTask 对象 这里的泛型必须是MyCallable 的泛型
FutureTask<String> futureTask = new FutureTask<>(myCallable);
// 创建线程
Thread thread = new Thread(futureTask);
// 启动线程
thread.start();
try {
// 获取线程执行结果
String result = futureTask.get();
System.out.println(result);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
执行结果:
4. Callable接口与Runnable接口的对比
Callable接口与Runnable接口类似,实现Callable接口的类和实现Runnable接口的类都是可被其它线程执
行的任务。二者区别如下:
- Callable接口定义的方法是call 而 Runnable 接口的定义的方法是 run方法
- call方法可以抛出报错,而run方法不行
- Callable接口执行完任务后可以返回值因为运行Callable任务可拿到一个Future对象可以返回值,而Runnable接口做不到,
- Runnable 接口不支持泛型 而Callable 接口支持泛型
- Callable 接口返回值,需要借助Future对象的get方法
1. Future 接口
Future接口是用来表示异步计算接口的接口,它提供一系列方法、用来检索计算是否完成、等待计算完成、
检索结果等功能,
2. FutureTask 实现类
FutureTask是JAVA中一个类,它实现了Futute接口 和 Ruunable 接口,可以允许你在创建一个线程
并执行一个任务
主要特点:
- 异步执行任务:可以使用FutureTask在一个线程中执行一个耗时的任务,而不会阻塞主线程。
- 2.获取任务结果:可以在需要的时候获取任务的执行结果,通过get()方法获取,如果任务还没有执行完成,get()方法将会阻塞直到任务完成。
- 取消任务:可以通过cancel()方法取消任务的执行,取消之后,get()方法将会返回CancellationException。
- 判断任务状态:可以通过isDone()、isCancelled()等方法判断任务的执行状态。
线程池
- 什么是线程池
线程池就是一种管理和重复使用的机制,用来有效处理更多任务,它里面有事先创建好线程,使用这些线程重复处理不同的任务,不用去创建新的线程
- 好处是什么
好处:减少了线程创建和销毁的开销;提高了性能和资源利用率;更好地控制了并发度;提高线程的可管理性。
怎么使用线程池
1 .实现Callable接口+重写call()方法
// 实现了Callable接口
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1024;
}
}
2.创建测试类,使用线程池执行线程任务
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建线程池并创建10条线程
ExecutorService exec = Executors.newFixedThreadPool(10);
// 创建 Future 对象 提交任务给线程池并返回 Future 对象
// 这里是使用了线程池里面的线程去执行这个任务的
Future<Integer> future = exec.submit(new MyCallable());
// 输出结果 通过 future对象的get方法拿到值
System.out.println(future.get());
// 关闭线程池
exec.shutdown();
}
}
3.得到输出结果
-
线程池API
-
FixedThreadPool: 该线程池包含固定数量的线程,当有新的任务提交时,如果当前线程池中有空闲线程,则立即执行任务,否则任务将被放入队列中等待执行。
-
CachedThreadPool: 该线程池会根据需要创建新的线程,但如果有空闲线程可用,则会重用它们。当线程处于空闲状态一段时间后,它们会被回收。
-
SingleThreadPool: 只包含单个工作线程的线程池,所有任务按照先进先出的顺序执行。
-
ScheduledThreadPool: 用于执行定时任务和周期性任务,可以指定延迟时间或固定的周期来执行任务。
线程API
- Start(); 启动线程方法
启动一个新的线程,在新的线程中运行run方法的代码;
不能直接调用run方法;
start方法只能调用一次;
start方法只是让线程进行就绪状态,不一定马上运行线程体,需要CPU分配时间片;
1.1 实现 Runnable 接口 并重写Run方法
public class My implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println("子线程" + i);
}
}
}
1.2 创建测试类,使用start()方法
public class Main {
public static void main(String[] args) {
My my = new My();
// 创建线程
Thread thead = new Thread(my);
thead.start();
//主线程与子线程同时运行循环(并发)
for (int i = 0; i < 100; i++) {
System.out.println("主线程:" + i);
}
}
}
1.3 结果截图
会发现我们的子线程和主线程是交替执行,因为这就出现了并发这个问题,二个线程分别抢占CPU的时间片来回交替执行
未写完等待更新