ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java中的多线程使用方式

2021-03-17 21:01:20  阅读:93  来源: 互联网

标签:java Thread 方式 创建 线程 executor new 多线程 public


文章目录

1 场景

java中默认情况下,程序都是同步执行的。即在一个线程中执行,程序未执行完,不返回请求。

如下场景,均需要创建执行程序外额外的线程:

  • 有些场景,需要立即返回请求结果,核心程序在后台异步执行,此次情况需要使用异步线程;
  • 处理数据较多时,需要启用多线程执行,提高执行速度

2 直接创建线程

java中可借助如下类,直接创建线程:ThreadRunnableCallable

此种方式,不建议使用。频繁地创建线程,会占用大量的资源和时间,会大大降低系统的性能

2.1 Thread创建线程

(1)类继承Thread类

public class ThreadClass extends Thread{
    private String param;

    public ThreadClass(String param){
        this.param=param;
    }

    @Override
    public void run() {
        // 多线程方法
    }
}

启动线程,代码如下:

new ThreadClass("my param").start();

(2)使用匿名类

new Thread(){
    @Override
    public void run() {
        // 多线程方法
    }
}.start();

2.2 Runnable创建线程

(1)类实现Runnable接口

public class RunnableClass implements Runnable {
    private String param;

    public RunnableClass(String param) {
        this.param = param;
    }

    @Override
    public void run() {
        // 多线程方法
    }
}

启动线程,代码如下:

RunnableClass runnableClass = new RunnableClass("thread param");
new Thread(runnableClass).start();

(2)使用匿名类

new Thread(new Runnable(){

    @Override
    public void run() {
		// 多线程方法
    }
}).start();

2.3 Callable创建线程

实现Callable接口创建的线程类,和Thread和Runnable的区别是:

  • 可以返回内容
  • 可以抛出异常

(1)类实现Callable接口

借助FutureTask执行线程,并获取返回结果

public class CallableClass implements Callable<String> {
    private String param;

    public CallableClass(String param) {
        this.param = param;
    }

    @Override
    public String call() throws Exception {
        // 自定义抛出异常,可在主线程中调用get方法时捕获
        // throw new Exception("123");

        // 线程返回值
        return null;
    }
}

启动线程,并获取结果,代码如下:

FutureTask<String> futureTask = new FutureTask<>(new CallableClass("param value"));
Thread myThread = new Thread(futureTask);
myThread.start();
try {
    // 获取异步线程的返回值(调用此方法,会阻塞当前主线程)
    System.out.println(futureTask.get());
} catch (InterruptedException e) {
    // 线程被中断时抛出的异常
    e.printStackTrace();
} catch (ExecutionException e) {
    // Callable类中抛出的异常
    e.printStackTrace();
}

(2)使用匿名类

FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
    @Override
    public String call() throws Exception {
        // 线程返回值
        return null;
    }
});

3 自定义线程池

3.1 关于线程池

(1)为什么使用线程池?

程序中不允许直接创建线程使用,建议使用线程池。避险new的线程太多,程序中线程数量不可控,导致程序出问题。

(2)创建几个线程池?

一个应有中,可以创建多个线程池,根据实际业务情况服务器情况,创建对应的线程池,并调整成符合实际情况参数配置

建议相同场景的业务,使用同一个线程池,并进行文档记录

3.1 工具类创建

不建议此种方式创建线程池。

jdk中的工具类java.util.concurrent.Executors,是java提供的创建线程池的工具类。

此工具类强烈建议不要用,主要是因为此工具类创建的线程池,要么是线程池的排队队列无界队列(如Executors.newFixedThreadPool),要么是线程池的最大线程数为无界队列(如Executors.newCachedThreadPool)。

线程池中的最大线数排队队列数,如果为无界队列,线程池将会不可控,系统将会出现严重的问题。

说明:
此处的无界队列,指的是Integer.MAX_VALUE,并不是绝对的无限制。

不建议此种方式创建线程池,此处也不再讲解。

3.2 自定义创建

3.2.1 创建说明

创建线程池,有很多种方式,此处借助于ThreadPoolExecutor来创建线程池。

建议使用ThreadPoolExecutor最完整的构造函数创建线程池:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,  TimeUnit unit,  BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

参数注释如下:

corePoolSize – the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set

maximumPoolSize – the maximum number of threads to allow in the pool

keepAliveTime – when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.

unit – the time unit for the keepAliveTime argument

workQueue – the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method.

threadFactory – the factory to use when the executor creates a new thread

handler – the handler to use when execution is blocked because the thread bounds and queue capacities are reached

参数补充说明:

(1)当前线程数小于corePoolSize,有新提交任务,将创建新的线程。即使当前线程池中有空闲线程

(2)当前线程数等于corePoolSize,有新提交任务,会将新任务放到队列workQueue中,只有在workQueue满了后且“maximumPoolSize >corePoolSize”,才会扩充当前线程数

(3)提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理

(4)当前线程数量大于corePoolSize,且线程空闲时间大于keepAliveTime后,空闲线程将被关闭。

(5)设置allowCoreThreadTimeOut为true时,线程数量不超过corePoolSize,空闲时间超过keepAliveTime后也会被关闭

3.2.2 创建线程池

(1)创建依赖类

/**
  * 自定义线程工厂(主要用于设置线程名称等属性)
  */
public class MyThreadFactory implements ThreadFactory {
    private String threadName;
    private volatile AtomicInteger count = new AtomicInteger(0);
    public MyThreadFactory(String threadName) {
        this.threadName = threadName;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        // 设置线程名
        String name = this.getClass().getSimpleName() + "_" + threadName + "_" + count.getAndAdd(1);
        thread.setName(name);
        return thread;
    }
}

/**
  * 自定义拒绝策略(排队队列满了后,会执行此策略)
  * 默认拒绝策略AbortPolicy(拒绝任务,抛出异常)
  */
public class MyRejectedExecutionHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        自定义拒绝策略 ---------------
        // (1)抛出异常,不执行任务
        /*throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + executor.toString());*/
        // (2)写入队列更改为阻塞方法
        try {
            // “非阻塞方法”offer改成“阻塞方法”put
            executor.getQueue().put(r);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
         额外操作--------------------
        // (1)记录拒绝数量,进行日志记录及预警(根据此提示调整线程池配置或优化代码速度)
        // (2)加入补偿线程池去执行
        // (3)再次加入到线程池尝试执行
        //-----------------------------
    }
}

(2)创建线程池

// 线程排队队列(队列大小为100)
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
// 线程工厂
ThreadFactory threadFactory = new MyThreadFactory("testThread");
// 拒绝策略
RejectedExecutionHandler rejectedExecutionHandler = new MyRejectedExecutionHandler();
// 创建线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 50, 15, TimeUnit.SECONDS, workQueue, threadFactory, rejectedExecutionHandler);

3.2.3 使用线程池

(1)调用Runnable

poolExecutor.execute(new Runnable() {
    @Override
    public void run() {
        // 输出内容:MyThreadFactory_testThread_0
        System.out.println(Thread.currentThread().getName());
    }
});

(2)调用callable

 Future<String> future = poolExecutor.submit(new Callable<String>() {
     @Override
     public String call() throws Exception {
         return Thread.currentThread().getName();
     }
 });
try {
    // 输出内容:MyThreadFactory_testThread_0
    System.out.println(future.get());
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

3.2.6 监视线程池

线程池的状态属性,可以作为检测线程池状态使用。

属性说明
taskCount返回已计划执行的任务的大致总数。线程的状态可能只在近似值期间动态地返回,因为任务的状态可能只在近似值期间发生变化。
completedTaskCount返回已完成执行的任务的大致总数。由于任务和线程的状态在计算过程中可能会动态变化,因此返回的值只是一个近似值,但在连续调用期间不会减少。
largestPoolSize返回池中同时存在的最大线程数。
poolSize返回池中同时存在的最大线程数。
activeCount返回正在积极执行任务的大约线程数。

可以启动一个单独的线程,将线程池对象作为属性传递进去,根据实际情况,定时检测线程池的相关属性,并进行记录。

此线程也可作为被检测线程池的一个线程任务使用。

3.2.5 关闭线程池

线程池不用时,可以进行关闭,实际项目中关闭线程池的情况很少,此节仅做一个记录。

可以调用shutdownshutdownNow关闭线程池。

(1)关闭机制

调用线程的interrupt方法来关闭线程。如线程无法响应,调用interrupt方法无法关闭线程,则线程池一直无法关闭

(2)shutdown和shutdownNow区别

shutdown:拒绝新任务,正在执行的任务,和队列中的任务都会执行

shutdownNow:拒绝新任务、停止正在执行的任务、停止队列中任务

调用任何一个方法,isShutdown返回结果都为true

所有任务都关闭后,线程池才真的被关闭,线程池真被关闭后isTerminaed 返回为true

4 spring线程池

如果java中集成了spring环境,建议使用spring创建线程池。

4.1 maven依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

4.2 定义线程池

/**
  * 通用线程池
  * @return org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
  */
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    // 核心线程数(默认为1)
    executor.setCorePoolSize(5);
    // 最大线程数(默认为:Integer.MAX_VALUE)
    executor.setMaxPoolSize(50);
    // 队列最大长度(默认为:Integer.MAX_VALUE)
    executor.setQueueCapacity(1000);
    // 线程池内线程允许的空闲时间/秒(默认60秒)
    executor.setKeepAliveSeconds(10);
    // 线程池拒绝策略(默认AbortPolicy:拒绝任务,抛出异常)
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());

    // 设置线程名前缀(用于定制线程名)
    executor.setThreadNamePrefix("common thread");
    // 设置线程分组名(用于定制分组信息)
    //executor.setThreadGroupName("common thread group");
    // 设置创建线程的优先级(默认为Thread.NORM_PRIORITY)
    //executor.setThreadPriority(Thread.NORM_PRIORITY);
    // 是否允许核心线程数超时(默认为false,设置为true后,即使队列未满,也可实现线程的动态增加和减少)
    //executor.setAllowCoreThreadTimeOut(true);

    return executor;
}

4.3 调用线程池

(1)无返回值

threadPoolTaskExecutor.execute(new Runnable() {
    @Override
    public void run() {
        // 输出:common thread1
        System.out.println(Thread.currentThread().getName());
    }
});

(2)有返回值

Future<String> future = threadPoolTaskExecutor.submit(new Callable<String>() {
    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName();
    }
});
try {
    // 输出:common thread1
    System.out.println(future.get());
} catch (InterruptedException e) {
    // 线程被中断时抛出的异常
    e.printStackTrace();
} catch (ExecutionException e) {
    // Callable类中抛出的异常
    e.printStackTrace();
}

4.4 注解调用

调用spring中的线程池异步执行方法,也可通过spring中的注解@Async来实现,此注解可以加到方法上或者类上(加类上后,类上面的所有代理方法,均为异步线程方法)。

4.4.1 开启注解

(1)springBoot开启注解

通过在启动类上,加上@EnableAsync注解来开启@Async的异步线程方法。

(2)springMvc开启注解

sprignMvc通过加上如下配置,来开启@Async的异步线程方法:

<!-- executor为默认的spring线程池 -->
<task:annotation-driven executor="threadPoolTaskExecutor"/>

4.4.2 使用异步方法
@Async
public void testAsyncMethod() {
    // 输出:common thread1
    System.out.println(Thread.currentThread().getName());
}

4.4.3 多个线程池的情况

spring中也可以定义多个线程池,每个线程池的类型均为ThreadPoolTaskExecutor,每个线程池的name不同

@Async注解不加参数时,使用的为默认的线程池,如需要指定线程池,可根据bean的name来指定,如下:

(1)定义非默认线程池

@Bean
public ThreadPoolTaskExecutor otherThreadPoolTaskExecutor() {
    ......
}

(2)调用非默认线程池

@Async(value = "otherThreadPoolTaskExecutor")
public void testAsyncMethod() {
    System.out.println(Thread.currentThread().getName());
}

注解的value注释说明:

指定的异步操作的限定符值。

可用于确定在执行异步操作时要使用的目标执行器,它与特定执行器或TaskExecutor bean定义的限定符值(或bean名称)匹配。

当在类级别@Async注释上指定时,指示给定的执行器应用于类中的所有方法。方法级使用Async#value总是覆盖类级别设置的任何值。

标签:java,Thread,方式,创建,线程,executor,new,多线程,public
来源: https://blog.csdn.net/weixin_55891090/article/details/114852341

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有