Java Spring多线程
- 开启一个线程
- 1 继承java.lang.Thread类
- 2 实现java.lang.Runnable接口
- 3 实现Callable接口
- 4 实现线程池ThreadPoolExecutor
- Java线程池
- Executors 的类型
- Future与线程池
开启一个线程
https://blog.csdn.net/qq_44715943/article/details/116714584
1 继承java.lang.Thread类
- 编写一个类,直接 继承 java.lang.Thread,重写 run方法,run方法为这个线程要做的事情。
- 创建线程对象, new继承线程的类。
- 启动线程,调用线程对象的 start() 方法。
public class ThreadTest02 {
public static void main(String[] args) {
MyThread t = new MyThread();
// 启动线程
//t.run(); // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程。)
t.start();
// 这里的代码还是运行在主线程中。
for(int i = 0; i < 1000; i++){
System.out.println("主线程--->" + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
// 编写程序,这段程序运行在分支线程中(分支栈)。
for(int i = 0; i < 1000; i++){
System.out.println("分支线程--->" + i);
}
}
}
注意:
t.run()
不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方式就是单线程。)t.start()
方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。启动成功的线程会自动调用run方法。 run方法在分支栈的栈底部(压栈)main方法在主栈的栈底部,run和main是平级的。
2 实现java.lang.Runnable接口
- 编写一个类,实现 java.lang.Runnable 接口,实现run方法,run方法为这个线程要做的事情(同1)。
- 创建线程对象,new线程类传入可运行的类/接口。
- 启动线程呢,调用线程对象的 start() 方法(同1)。
public class ThreadTest03 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
for(int i = 0; i < 100; i++){
System.out.println("主线程--->" + i);
}
}
}
// 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
class MyRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("分支线程--->" + i);
}
}
}
优势:
- 实现的方式没有类的单继承性的局限性:一个类实现了接口,它还可以去继承其它的类,更灵活。
- 实现的方式更适合来处理多个线程有共享数据的情况
3 实现Callable接口
- 创建一个实现Callable的实现类
- 实现call方法,将此线程需要执行的操作声明在call()中
- 创建Callable接口实现类的对象
- 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
- 获取Callable中call方法的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1.创建一个实现Callable的实现类
class NumThread implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
//把100以内的偶数相加
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
优势:
- call()可以有返回值的
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- Callable是支持泛型的
4 实现线程池ThreadPoolExecutor
- 创建好实现了Runnable接口的类或实现Callable的实现类
- 实现run或call方法
- 创建线程池
- 调用线程池的execute方法执行某个线程,参数是之前实现Runnable或Callable接口的对象
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
class NumberThread implements Runnable {
@Override
public void run() {
//遍历100以内的偶数
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable {
@Override
public void run() {
//遍历100以内的奇数
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//自定义线程池的属性
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2. 执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适用于Runnable
service.execute(new NumberThread1());//适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3. 关闭连接池
service.shutdown();
}
}
Java线程池
如果我们不采用线程池,为每一个请求都创建一个线程的话:
- 管理线程的生命周期开销非常高。管理这些线程的生命周期会明显增加 CPU 的执行时间,会消耗大量计算资源。
- 线程间上下文切换造成大量资源浪费。
- 程序稳定性会受到影响。我们知道,创建线程的数量存在一个限制,这个限制将随着平台的不同而不同,并且受多个因素制约,包括jvm的启动参数、Thread构造函数中请求的栈大小,以及底层操作的限制等。如果超过了这个限制,那么很可能抛出OutOfMemoryError异常,这对于运行中的应用来说是非常危险的。
Java自JDK1.5 提供了自己的多线程框架,称为 Executor
框架.
- Executor:java线程池框架的最上层父接口,在Executor这个接口中只有一个execute方法,该方法的作用是向线程池提交任务并执行。
- ThreadPoolExecutor:这是Java线程池最核心的一个类,该类继承自AbstractExecutorService,主要功能是创建线程池,给任务分配线程资源,执行任务。
- ScheduledExecutorSerivce 和 ScheduledThreadPoolExecutor 提供了另一种线程池:
延迟执行和周期性执行
的线程池。 - Executors:这是一个静态工厂类,该类定义了一系列静态工厂方法,通过这些工厂方法可以
返回各种不同的线程池
。 - ExecutorService:该接口继承自Executor接口,添加了shutdown、shutdownAll、submit、invokeAll等一系列对线程的操作方法,该接口比较重要,在使用线程池框架的时候,经常用到该接口。
- AbstractExecutorService:这是一个抽象类,实现ExecuotrService接口
Executors 的类型
SingleThreadExecutor
: 此线程池 Executor 只有一个线程。它用于以顺序方式的形式执行任务。如果此线程在执行任务时因异常而挂掉,则会创建一个新线程来替换此线程,后续任务将在新线程中执行。
ExecutorService executorService = Executors.newSingleThreadExecutor()
FixedThreadPool(n)
: 它是一个拥有固定数量线程的线程池。提交给 Executor 的任务由固定的 n 个线程执行,如果有更多的任务,它们存储在 LinkedBlockingQueue 里。这个数字 n 通常跟底层处理器支持的线程总数有关。
ExecutorService executorService = Executors.newFixedThreadPool(4);
如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1 如果是IO密集型任务,参考值可以设置为2*NCPU
-
CachedThreadPool
: 该线程池主要用于执行大量短期并行任务的场景。与固定线程池不同,此线程池的线程数不受限制。如果所有的线程都在忙于执行任务并且又有新的任务到来了,这个线程池将创建一个新的线程并将其提交到 Executor。只要其中一个线程变为空闲,它就会执行新的任务。 如果一个线程有 60 秒的时间都是空闲的,它们将被结束生命周期并从缓存中删除。但是,如果管理得不合理,或者任务不是很短的,则线程池将包含大量的活动线程。这可能导致资源紊乱并因此导致性能下降。
ExecutorService executorService = Executors.newCachedThreadPool();
ScheduledExecutor
:当我们有一个需要定期运行的任务或者我们希望延迟某个任务时,就会使用此类型的 executor。
ScheduledExecutorService scheduledExecService = Executors.newScheduledThreadPool(1);
scheduledExecService.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
scheduledExecService.scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)
Future与线程池
由于提交给Executor 的任务是异步的,需要有一个对象来接收Executor 的处理结果:
Future<String> result = executorService.submit(callableTask);
调用者可以继续执行主程序,当需要提交任务的结果时,他可以在这个 Future对象上调用.get() 方法来获取。如果任务完成,结果将立即返回给调用者,否则调用者将被阻塞,直到 Executor 完成此操作的执行并计算出结果。
如果调用者不能无限期地等待任务执行的结果,那么这个等待时间也可以设置为定时地。可以通过 Future.get(long timeout,TimeUnit unit) 方法实现,如果在规定的时间范围内没有返回结果,则抛出 TimeoutException。调用者可以处理此异常并继续执行该程序。
如果在执行任务时出现异常,则对 get 方法的调用将抛出一个ExecutionException。对于 Future.get()方法返回的结果,一个重要的事情是,只有提交的任务实现了java.util.concurrent.Callable接口时才返回 Future。如果任务实现了Runnable接口,那么一旦任务完成,对 .get() 方法的调用将返回 null。
另一点是 Future.cancel(boolean mayInterruptIfRunning) 方法。此方法用于取消已提交任务的执行。如果任务已在执行,则 Executor 将尝试在mayInterruptIfRunning 标志为 true 时中断任务执行。
创建Callable任务:
public class Task implements Callable<String> {
private String message;
public Task(String message) {
this.message = message;
}
@Override
public String call() throws Exception {
return "Hello " + message + "!";
}
}
异步执行任务:
public class ExecutorExample {
public static void main(String[] args) {
// 1. 实例化了 Task 类
Task task = new Task("World");
// 2. 创建了一个具有4个线程数的 FixedThreadPool Executors
// 3. 提交给 Executors 执行
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 4. 结果由 Future 对象返回
Future<String> result = executorService.submit(task);
try {
System.out.println(result.get());
} catch (InterruptedException | ExecutionException e) {
System.out.println("Error occured while executing the submitted task");
e.printStackTrace();
}
// 调用 executorService 对象上的 shutdown 来终止所有线程并将资源返回给 OS
executorService.shutdown();
}
}
shutdown()
方法等待 Executor 完成当前提交的任务。 但是,如果要求是立即关闭 Executor 而不等待,那么我们可以使用 shutdownNow()
方法。
或者创建Runnable任务:
public class Task implements Runnable{
private String message;
public Task(String message) {
this.message = message;
}
public void run() {
// 由于Runnable任务没有返回值,不能得到任务执行结果。需要在任务内完成所有操作(打印)
System.out.println("Hello " + message + "!");
}
}