Java ExecutorService四种线程池的简单使用

  我们都知道创建一个线程可以继承Thread类或者实现Runnable接口,实际Thread类就是实现了Runnable接口。

  到今天才明白后端线程的作用:我们可以开启线程去执行一些比较耗时的操作,类似于前台的ajax异步操作,比如说用户上传一个大的文件,我们可以获取到文件之后开启一个线程去操作该文件,但是可以提前将结果返回去,如果同步处理有可能太耗时,影响系统可用性。

1、new Thread的弊端

原生的开启线程执行异步任务的方式:

new Thread(new Runnable() {

    @Override
    public void run() {
        // TODO Auto-generated method stub
    }
}).start();

 

 

弊端如下:

  • 每次new Thread新建对象性能差。
  • 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
  •  缺乏更多功能,如定时执行、定期执行、线程中断。

相比new Thread,Java提供的四种线程池的好处在于:

  •  重用存在的线程,减少对象创建、消亡的开销,性能佳。
  • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
  •  提供定时执行、定期执行、单线程、并发数控制等功能。

 

2.Java线程池的使用

Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

 

 可以用ExecutorService接受返回值,ExecutorService是继承Executor的一个接口,ThreadPoolExecutor继承自AbstractExecutorService,AbstractExecutorService实现ExecutorService接口。一般也用作一个类的静态成员变量,所有实例共用一个ExecutorService对象。

Executor接口只有一个方法:

package java.util.concurrent;

public abstract interface Executor {
    public abstract void execute(Runnable paramRunnable);
}

 

ExecutorService的方法如下:重要的方法:

  execute(Runnable)只执行任务,无返回值,继承Executor的方法,此方法不能传Callable实例,只接受Runnable实例;

  submit(xxx),执行任务,且返回一个future对象,此对象可以接受线程执行完成的返回值,接受的参数可以是Runnable和Callable接口对象;

  shutdown()关闭线程池。

package java.util.concurrent;
import java.util.List;
import java.util.Collection;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;

public interface ExecutorService extends Executor {

    void shutdown();

    List<Runnable> shutdownNow();

    boolean isShutdown();

    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

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

    <T> Future<T> submit(Runnable task, T result);

    Future<?> submit(Runnable task);

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

 

 

 

 

MyThread线程类:继承Thread并且重写run方法,run方法中间隔一秒打印一次线程名字

package cn.qlq.threadTest;

/**
 * 原生的线程类Thread的使用方法
 * 
 * @author Administrator
 *
 */
public class MyThread extends Thread {
    /**
     * 更改线程名字
     * 
     * @param threadName
     */
    public MyThread(String threadName) {
        this.setName(threadName);
    }

    @Override
    public void run() {
        for(int i=0;i<5;i++){
            System.out.println(getName()+"-------"+i);
            try {
                Thread.sleep(1*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

 

1.FixedThreadPool的用法

  创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

创建方法:

    /**
     * 参数是初始化线程池子的大小
     */
    private static final ExecutorService batchTaskPool = Executors.newFixedThreadPool(2);

 

 

查看源码:(使用了阻塞队列,超过池子容量的线程会在队列中等待)

 

 

测试代码:

package cn.qlq.threadTest;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolTest {
    
    /**
     * 参数是初始化线程池子的大小
     */
    private static final ExecutorService batchTaskPool = Executors.newFixedThreadPool(2);
    
    public static void main(String[] args) {
        for(int i = 0;i < 3;i++){
            batchTaskPool.execute(new MyThread("mt"+i));
        }
    }
    
}

 

结果:

mt1-------0
mt0-------0
mt0-------1
mt1-------1
mt1-------2
mt0-------2
mt1-------3
mt0-------3
mt1-------4
mt0-------4
mt2-------0
mt2-------1
mt2-------2
mt2-------3
mt2-------4

 

 解释:

  池子容量大小是2,所以mt0和mt1可以加入到线程池子中,mt2只是暂时的加到等待队列,等mt0或者mt1执行完成之后从队列移除之后mt2就有机会执行。。。。。。。。

 

定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()

 

2.CachedThreadPool

  创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

创建方法:

private static final ExecutorService batchTaskPool = Executors.newCachedThreadPool();

 

 

查看源码:(使用了同步队列)

 

 

测试代码:

package cn.qlq.threadTest;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolTest {
    
    private static final ExecutorService batchTaskPool = Executors.newCachedThreadPool();
    
    public static void main(String[] args) {
        for(int i = 0;i < 3;i++){
            batchTaskPool.execute(new MyThread("mt"+i));
        }
    }
    
}

 

结果:

mt0-------0
mt1-------0
mt2-------0
mt0-------1
mt1-------1
mt2-------1
mt0-------2
mt1-------2
mt2-------2
mt1-------3
mt0-------3
mt2-------3
mt1-------4
mt0-------4
mt2-------4

 

3.SingleThreadExecutor用法

   创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。类似于单线程执行的效果一样。

创建方法:

    private static final ExecutorService batchTaskPool = Executors.newSingleThreadExecutor();

 

查看源码;使用的阻塞队列

 

 

测试代码:

package cn.qlq.threadTest;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorTest {
    
    private static final ExecutorService batchTaskPool = Executors.newSingleThreadExecutor();
    
    public static void main(String[] args) {
        for(int i = 0;i < 3;i++){
            batchTaskPool.execute(new MyThread("mt"+i));
        }
    }
    
}

 

结果:

mt0-------0
mt0-------1
mt0-------2
mt0-------3
mt0-------4
mt1-------0
mt1-------1
mt1-------2
mt1-------3
mt1-------4
mt2-------0
mt2-------1
mt2-------2
mt2-------3
mt2-------4

 

 4.ScheduledThreadPool用法------可以实现任务调度功能

   创建一个定长线程池(会指定容量初始化大小),支持定时及周期性任务执行。可以实现一次性的执行延迟任务,也可以实现周期性的执行任务。

创建方法:

    private static final ScheduledExecutorService batchTaskPool = Executors.newScheduledThreadPool(2);

 

查看源码:(使用了延迟队列)

 

 

 

 

 测试代码:

package cn.qlq.threadTest;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolTest {

    private static final ScheduledExecutorService batchTaskPool = Executors.newScheduledThreadPool(2);

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            //第一次执行是在3s后执行(延迟任务)
            batchTaskPool.schedule(new MyThread("my" + i), 3, TimeUnit.SECONDS);
            //第一个参数是需要执行的任务,第二个参数是第一次的延迟时间,第三个参数是两次执行的时间间隔,第四个参数是时间的单位
            batchTaskPool.scheduleAtFixedRate(new MyThread("my" + i), 3,7, TimeUnit.SECONDS);
            //第一个参数是需要执行的任务,第二个参数是第一次的延迟时间,第三个参数是两次执行的时间间隔,第四个参数是时间的单位
            batchTaskPool.scheduleWithFixedDelay(new MyThread("my" + i), 3,5, TimeUnit.SECONDS);
        }
    }

}

 

schedule是一次性的任务,可以指定延迟的时间。
scheduleAtFixedRate已固定的频率来执行某项计划(任务)
scheduleWithFixedDelay相对固定的延迟后,执行某项计划 (这个就是第一个任务执行完5s后再次执行,一般用这个方法任务调度)

 

 关于二者的区别:

  scheduleAtFixedRate :这个是按照固定的时间来执行,简单来说:到点执行
  scheduleWithFixedDelay:这个呢,是等上一个任务结束后,在等固定的时间,然后执行。简单来说:执行完上一个任务后再执行

 

举例子

scheduledThreadPool.scheduleAtFixedRate(new TaskTest("执行调度任务3"),0, 1, TimeUnit.SECONDS);  //这个就是每隔1秒,开启一个新线程
scheduledThreadPool.scheduleWithFixedDelay(new TaskTest("第四个"),0, 3, TimeUnit.SECONDS); //这个就是上一个任务执行完,3秒后开启一个新线程

 

 

 当然实现任务调度还可以采用quartz框架来实现,更加的灵活。参考:https://www.cnblogs.com/qlqwjy/p/8723358.html

 

 

 

例如我系统中使用的一个ExcutorService的例子:

/**
 * 同步钉钉组织结构和人员的Action
 * 
 * @author Administrator
 *
 */
@Namespace("/sync")
public class SyncGroupAndUserAndBaseInfoAction extends DMSActionSupport {

    /**
     * serialID
     */
    private static final long serialVersionUID = 3526083465788431949L;
    
    private static final ExecutorService batchTaskPool = Executors.newFixedThreadPool(2);

    private static Logger logger = LoggerFactory.getLogger(SyncGroupAndUserAndBaseInfoAction.class);

    @Autowired
    private GroupAndUserService groupService;

    @Autowired
    private BaseInfoService baseInfoService;

    /**
     * 同步基本信息的数据
     * 
     * @return
     */
    @Action(value = "syncGroupAndUser")
    public String syncGroupAndUser() {
        long startTime = System.currentTimeMillis();
        logger.info("manual sync groups and users!");

        String accessToken = FetchDataUtils.getAccessToken();
        if (StringUtils.isBlank(accessToken)) {
            setPreJs("accessToken is null!");
            return "js";
        }

        String groupStr = FetchDataUtils.getGroupStr(accessToken);
        if (StringUtils.isBlank(groupStr)) {
            setPreJs("groupStr is null");
            return "js";
        }
        
        
        Set<String> dingGroupIds = FetchDataUtils.getGroupIds(groupStr);// 钉钉同步回来的组
        //新开一个线程去获取钉钉用户和组织
        batchDisposeDingGroupAndUser(dingGroupIds,groupStr,accessToken);
        

        Map<String,Object> response = new HashMap<String,Object>();
        response.put("success", true);
        response.put("message", "success sync datas!");
        setPreJs(APIUtils.getJsonResultFromMap(response));
        
        long endTime = System.currentTimeMillis();
        logger.info("同步钉钉组织结构和用户完成-----用时:{}ms",(endTime-startTime));
        return "js";
    }

    private void batchDisposeDingGroupAndUser(final Set<String> dingGroupIds, final String groupStr,final String accessToken) {
        Runnable run = new Runnable() {
            @Override
            public void run() {
                groupService.batchDisposeGroups(groupStr, dingGroupIds);
                groupService.fetchAndDisposeUsers(accessToken, dingGroupIds);                
            }
        };
        batchTaskPool.execute(run);
    }
    
}

注意:

  batchDisposeDingGroupAndUser()方法的形参必须声明为final,否则编译错误。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值