线程创建的方法

面试面到过好多次,感觉要是深聊可以聊好久

1.继承Thread类创建线程

1】定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。

2】创建Thread子类的实例,也就是创建了线程对象

3】启动线程,即调用线程的start()方法

//1、创建一个类继承Thread类  
public class MyThread  extends Thread {  
    //2、重写run()实现业务需求  
    @Override  
    public void run() {  
        System.out.println(Thread.currentThread().getName() + ":我是子线程");  
    }  
  
    public static void main(String[] args) {  
        //3、创建子类对象通过start()方法运行线程  
        new MyThread().start();  
        System.out.println(Thread.currentThread().getName() + ":我是主线程");  
    }  
}

2.实现Runnable接口

可以通过编写实现类,匿名内部类和lambda方式

1】实现Runnable接口,重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体

2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象

3】启动线程,即调用线程的start()方法

//1、实现Runnable接口
public class MyThread implements Runnable {  
    //2、重写run方法  
    @Override  
    public void run() {  
        System.out.println(Thread.currentThread().getName() + ">>>>我是子线程");  
    }  
  
    public static void main(String[] args) {  
        System.out.println(Thread.currentThread().getName() + ">>>>>>>>我是主线程");  
        //3、通过Thread构造函数创建线程  
        new Thread(new MyThread()).start();  
        //通过匿名内部类创建  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                System.out.println(Thread.currentThread().getName() + ">>>>我是子线程");  
            }  
        }).start();  
  
        //通过lamdar表达式创建  
        new Thread(() -> System.out.println(Thread.currentThread().getName() + ">>>>我是子线程")).start();  
  
    }  
}

3.实现Callable接口

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

call()方法可以有返回值
call()方法可以声明抛出异常

这里先介绍一下 Future 接口和 FutureTask

在 Java 中,Future 类只是一个泛型接口,位于 java.util.concurrent 包下,其中定义了 5 个方法,主要包括下面这 4 个功能:

  • 取消任务;
  • 判断任务是否被取消;
  • 判断任务是否已经执行完成;
  • 获取任务执行结果。
// V 代表了Future执行的任务返回值的类型
public interface Future<V> {
    // 取消任务执行
    // 成功取消返回 true,否则返回 false
    boolean cancel(boolean mayInterruptIfRunning);
    // 判断任务是否被取消
    boolean isCancelled();
    // 判断任务是否已经执行完成
    boolean isDone();
    // 获取任务执行结果
    V get() throws InterruptedException, ExecutionException;
    // 指定时间内没有返回计算结果就抛出 TimeOutException 异常
    V get(long timeout, TimeUnit unit)

        throws InterruptedException, ExecutionException, TimeoutExceptio

}

简单理解就是:我有一个任务,提交给了 Future 来处理。任务执行期间我自己可以去做任何想做的事情。并且,在这期间我还可以取消任务以及获取任务的执行状态。一段时间之后,我就可以 Future 那里直接取出任务执行结果。

FutureTask 提供了 Future 接口的基本实现,常用来封装 CallableRunnable,具有取消任务、查看任务是否执行完成以及获取任务执行结果的方法。ExecutorService.submit() 方法返回的其实就是 Future 的实现类 FutureTask

<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);

FutureTask 不光实现了 Future接口,还实现了Runnable 接口,因此可以作为任务直接被线程执行。

在这里插入图片描述

FutureTask 有两个构造函数,可传入 Callable 或者 Runnable 对象。实际上,传入 Runnable 对象也会在方法内部转换为Callable 对象。

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;
}
public FutureTask(Runnable runnable, V result) {
    // 通过适配器RunnableAdapter来将Runnable对象runnable转换成Callable对象
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;
}

FutureTask相当于对Callable 进行了封装,管理着任务执行的情况,存储了 Callablecall 方法的任务执行结果。

介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

同样可以通过编写实现类,匿名内部类和lambda方式

1】实现Callable接口,并实现call()方法。

2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

public class MyThread {  
	  
    public static void main(String[] args) throws ExecutionException, InterruptedException {  
        FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {  
            @Override  
            public Integer call() throws Exception {  
                Thread.sleep(1000);  
                System.out.println("子线程结束");  
                return 1;  
            }  
        });  
        new Thread(futureTask).start();  
        System.out.println(Thread.currentThread().getName() + ":我是主线程");  
        //主线程会等待子线程结束才执行后面的语句
        System.out.println(futureTask.get()+"子线程返回");  
        System.out.println("主线程结束");  
    }  
}

4.线程池

4.1Executor框架

Executor 框架是 Java5 之后引进的,在 Java 5 之后,通过 Executor 来启动线程比使用 Threadstart 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。

this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用,调用尚未构造完全的对象的方法可能引发令人疑惑的错误。

Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让并发编程变得更加简单。

Executor 框架结构主要由三大部分组成:

1、任务(Runnable /Callable)

执行任务需要实现的 Runnable 接口Callable接口Runnable 接口Callable 接口 实现类都可以被 ThreadPoolExecutorScheduledThreadPoolExecutor 执行。

2、任务的执行(Executor)

如下图所示,包括任务执行机制的核心接口 Executor ,以及继承自 Executor 接口的 ExecutorService 接口。ThreadPoolExecutorScheduledThreadPoolExecutor 这两个关键类实现了 ExecutorService 接口。

在这里插入图片描述

这里提了很多底层的类关系,但是,实际上我们需要更多关注的是 ThreadPoolExecutor 这个类,这个类在我们实际使用线程池的过程中,使用频率还是非常高的。

ScheduledThreadPoolExecutor 实际上是继承了 ThreadPoolExecutor 并实现了 ScheduledExecutorService ,而 ScheduledExecutorService 又实现了 ExecutorService,正如我们上面给出的类关系图显示的一样。

3、异步计算的结果(Future)

Future 接口以及 Future 接口的实现类 FutureTask 类都可以代表异步计算的结果。

当我们把 Runnable接口Callable 接口 的实现类提交给 ThreadPoolExecutorScheduledThreadPoolExecutor 执行。(调用 submit() 方法时会返回一个 FutureTask 对象)

Executor 框架的使用示意图

在这里插入图片描述

  1. 主线程首先要创建实现 Runnable 或者 Callable 接口的任务对象。
  2. 把创建完成的实现 Runnable/Callable接口的 对象直接交给 ExecutorService 执行: ExecutorService.execute(Runnable command))或者也可以把 Runnable 对象或Callable 对象提交给 ExecutorService 执行(ExecutorService.submit(Runnable task)ExecutorService.submit(Callable <T> task))。
  3. 如果执行 ExecutorService.submit(…)ExecutorService 将返回一个实现Future接口的对象(我们刚刚也提到过了执行 execute()方法和 submit()方法的区别,submit()会返回一个 FutureTask 对象)。由于 FutureTask 实现了 Runnable,我们也可以创建 FutureTask,然后直接交给 ExecutorService 执行。
  4. 最后,主线程可以执行 FutureTask.get()方法来等待任务执行完成。主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。
public class CallableDemo{  
    public static void main(String[] args){  
        ExecutorService executorService = Executors.newCachedThreadPool();  
  
        //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中  
        Future<String> future = executorService.submit(new Callable<String>() {  
            @Override  
            public String call() throws Exception {  
                System.out.println("子线程");  
                Thread.sleep(1000);  
                return "1";  
            }  
        });  
  
        try{  
            while(!future.isDone());//Future返回如果没有完成,则一直循环等待,直到Future返回完成  
            System.out.println(future.get());     //打印各个线程(任务)执行的结果  
        }catch(InterruptedException | ExecutionException e){  
            e.printStackTrace();  
        } finally{  
            //启动一次顺序关闭,执行以前提交的任务,但不接受新任务  
            executorService.shutdown();  
        }    
    }  
}

4.2ThreadPoolExecutor 类

线程池实现类 ThreadPoolExecutorExecutor 框架最核心的类。

《阿里巴巴 Java 开发手册》强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

Executors 返回线程池对象的弊端如下:

  • FixedThreadPoolSingleThreadExecutor : 使用的是无界的 LinkedBlockingQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
  • CachedThreadPool :使用的是同步队列 SynchronousQueue, 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
  • ScheduledThreadPoolSingleThreadScheduledExecutor : 使用的无界的延迟阻塞队列DelayedWorkQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。

具体参数解释:

ThreadPoolExecutor 3 个最重要的参数:

  • corePoolSize : 任务队列未达到队列容量时,最大可以同时运行的线程数量。
  • maximumPoolSize : 任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
  • workQueue: 新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

ThreadPoolExecutor其他常见参数 :

  • keepAliveTime:线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁。
  • unit : keepAliveTime 参数的时间单位。
  • threadFactory :executor 创建新线程的时候会用到。
  • handler :饱和策略。
public class ThreadPoolExecutorDemo {  
  
    private static final int CORE_POOL_SIZE = 5;  
    private static final int MAX_POOL_SIZE = 10;  
    private static final int QUEUE_CAPACITY = 100;  
    private static final Long KEEP_ALIVE_TIME = 1L;  
    public static void main(String[] args) {  
  
        //使用阿里巴巴推荐的创建线程池的方式  
        //通过ThreadPoolExecutor构造函数自定义参数创建  
        ThreadPoolExecutor executor = new ThreadPoolExecutor(  
                CORE_POOL_SIZE,  
                MAX_POOL_SIZE,  
                KEEP_ALIVE_TIME,  
                TimeUnit.SECONDS,  
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),  
                new ThreadPoolExecutor.CallerRunsPolicy());  
  
        for (int i = 0; i < 10; i++) {  
            //执行Runnable  
            executor.execute(new Runnable() {  
                @Override                public void run() {  
                    System.out.println("我是子线程");  
                }  
            });  
        }        //终止线程池  
        executor.shutdown();  
        while (!executor.isTerminated()) {}  
        System.out.println("Finished all threads");  
    }  
}

5.Spring @Async

spring自带的异步注解
通过注解将指定的方法标记为异步线程

@Async
public void thread(){
	System.out.println(Thread.currentThread().getName()+"我是子线程");
}

注:参考了JavaGuide

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ponymate

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值