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的一个接口。一般也用作一个类的静态成员变量,所有实例共用一个ExecutorService对象。

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,否则编译错误。

猜你喜欢

转载自www.cnblogs.com/qlqwjy/p/9470414.html