四种基本实现方式
1、继承Thread类
2、实现Runnable接口
3、实现Callable接口
4、创建线程池
实现Runnable和实现Callable接口的方式基本相同,不过前者线程执行体run()方法无返回值,而后者执行call()方法有返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:
1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。
2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。
4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。
5、前三种的线程如果创建关闭频繁会消耗系统资源影响性能,而使用线程池可以不用线程的时候放回线程池,用的时候再从线程池取,项目开发中主要使用线程池
注:在前三种中一般推荐采用实现接口的方式来创建多线程
一、extends Thread类
通过继承Thread类来创建并启动多线程的一般步骤如下
1】定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
2】创建Thread子类的实例,也就是创建了线程对象
3】启动线程,即调用线程的start()方法
package com.example.worktest.async.thread.callable.service;
import org.springframework.stereotype.Service;
@Service
public class ThreadTest extends Thread{
@Override
public void run(){
System.out.println("继承Thread类的线程");
}
}
@GetMapping("/test1")
public String threadTest() {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
return Thread.currentThread().getName();
}
运行后的执行结果:
二、实现Runnable接口
通过实现Runnable接口创建并启动线程一般步骤如下:
1】定义Runnable接口的实现类,重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
3】通过调用线程对象的start()方法来启动线程
package com.example.worktest.async.thread.callable.service;
import org.springframework.stereotype.Service;
@Service
public class RunTest implements Runnable{
public void run(){
System.out.println("runnable___"+Thread.currentThread().getName());
}
}
@GetMapping("/test2")
public String runnableTest() {
RunTest runTest = new RunTest();
Thread thread = new Thread(runTest);
Thread thread1 = new Thread(runTest,"自己命名的runnable线程yyyy");
thread.start();
thread1.start();
return Thread.currentThread().getName();
}
三、实现Callable接口
3.1 Callable和Runnable接口的不同点
1】Runnable接口是run()方法,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
2】call()方法可以有返回值
3】call()方法可以声明抛出异常
4】Callable能接受一个泛型,然后在call方法中返回一个这个类型的值。而Runnable的run方法没有返回值(下面以String为例)
3.2 实现步骤
1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
2】创建一个 FutureTask ,它将在运行时执行给定的 Callable ,该FutureTask对象封装了Callable对象的call()方法的返回值
3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
4】调用FutureTask/Future(线程池时使用) 对象的get()方法来获得子线程执行结束后的返回值
<< get方法建议与isDone方法同用!!不然get方法在线程执行完之前会一直阻塞!!>>
@Component
public class CallTest implements Callable<String>{
@Override
public String call() throws Exception{
return "这是callable的call方法";
}
}
@GetMapping("/test3")
public void callableTest(){
try{
CallTest callTest = new CallTest();
FutureTask<String> futureTask = new FutureTask<String>(callTest);
Thread thread3 = new Thread(futureTask);
thread3.start();
while (true){
if(futureTask.isDone()){
System.out.println(futureTask.get());
break;
}
Thread.sleep(2000);
System.out.println("线程还未执行完,sleep2000ms后再尝试");
}
}catch (Exception e) {
e.printStackTrace();
}
}
执行结果:
3.3 FutureTask
线程Thread的构造方法:
三个构造器:无参构造器、一个参数构造器和两个参数构造器。但是就没有我们Callable作为参数的构造器。那么,我们想要获取到线程,通过callable怎么获取呢 ?
我们先来看一下FutureTask的实现:
public class FutureTask<V> implements RunnableFuture<V>
FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
FutureTask提供了2个构造器:
public FutureTask(Callable callable) { }
public FutureTask(Runnable runnable, V result) { }
事实上,FutureTask是Future接口的一个唯一实现类。
四、线程池
4.0 线程池的好处
在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。
减少资源的开销
减少了每次创建线程、销毁线程的开销。
提高响应速度 ,每次请求到来时,由于线程的创建已经完成,故可以直接执行任务,因此提高了响应速度。
提高线程的可管理性 ,线程是一种稀缺资源,若不加以限制,不仅会占用大量资源,而且会影响系统的稳定性。 因此,线程池可以对线程的创建与停止、线程数量等等因素加以控制,使得线程在一种可控的范围内运行,不仅能保证系统稳定运行,而且方便性能调优。
那么,我们应该如何创建一个线程池那?Java中已经提供了创建线程池的一个类:Executor
而我们创建时,一般使用它的子类:ThreadPoolExecutor
4.1 线程池不要使用Executors构造!!!
Executors 大大的简化了我们创建各种类型线程池的方式,为什么还不让使用呢?
其实,只要你打开看看它的静态方法参数就会明白了
传入的workQueue 是一个边界为 Integer.MAX_VALUE 队列,我们也可以变相的称之为无界队列了,因为边界太大了,这么大的等待队列也是非常消耗内存的
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
另外该 ThreadPoolExecutor方法使用的是默认拒绝策略(直接拒绝),但并不是所有业务场景都适合使用这个策略,当很重要的请求过来直接选择拒绝显然是不合适的
总的来说,使用 Executors 创建的线程池太过于理想化,