【并发技术10】线程并发库的使用

简介: 【并发技术10】线程并发库的使用

1. 线程池的概念


在 java5 之后,就有了线程池的功能了,在介绍线程池之前,先来简单看一下线程池的概念。假设我开了家咨询公司,那么每天会有很多人过来咨询问题,如果我一个个接待的话,必然有很多人要排队,这样效率就很差,我想解决这个问题,现在我雇几个客服,来了一个咨询的,我就分配一个客服去接待他,再来一个,我再分配个客服去接待……如果第一个客服接待完了,我就让她接待下一个咨询者,这样我雇的这些客服可以循环利用。这些客服就好比不同的线程,那么装这些线程的容器就称为线程池。


2. Executors的使用


Executors 工具类是用来创建线程池的,这个线程池可以指定线程个数,也可以不指定,也可以指定定时器的线程池,它有如下常用的方法:

方法名 作用
newFixedThreadPool(int nThreads) 创建固定数量的线程池
newCachedThreadPool() 创建缓存的线程池
newSingleThreadExecutor() 创建单个线程
newScheduledThreadPool(int corePoolSize) 创建定时器线程池


2.1 固定数量的线程池


先来看下 Executors 工具类的使用:

public class ThreadPool {
//线程池的概念与Executors类的使用
    public static void main(String[] args) {
        //固定线程池:创建固定线程数去执行线程的任务,这里创建三个线程
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        for (int i = 1; i <= 10; i++) {//向池子里扔10个任务
            final int task = i;
            threadPool.execute(new Runnable() {//execute方法表示向池子中扔任务,任务即一个Runnable
                @Override
                public void run() {
                    for (int j = 1; j <= 5; j++) { 
                        try {
                            Thread.sleep(20);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + " looping of " + j + " for task of " + task);
                    }
                }
            });
        }
        System.out.println("all of 10 tasks have committed!");
        threadPool.shutdown(); //执行完任务后关闭
//        threadPool.shutdownNow(); //立即关闭
    }
}

从代码中可以看出,有了 Executors 工具类,我们创建固定数量的线程数就方便了,这些线程都去做同样的任务。 threadPool.execute 表示从池子里取出一个线程去执行任务,上面定义了三个线程,所以每次会取三个任务去让线程执行,其他任务等待,执行完后,再从池子里取三个任务执行,执行完,再取三个任务,最后一个任务三个线程有一个会抢到执行。所以定义了线程数量的话,每次会执行该数量的任务,因为一个线程一个任务,执行完再执行其他任务。

因为这个执行结果有点多,就不贴结果了,反正每次三个任务一执行,直到执行完10个任务为止。


2.2 缓存线程池


所谓缓存线程池,指的是线程数量不固定,一个任务来了,我开启一个线程为其服务,两个任务我就开启两个,N个任务我就开启N个线程为其服务。如果现在只剩1个任务了,那么一段时间后,就把多余的线程给干掉,保留一个线程为其服务。所以可以改写一下上面的代码:

public class ThreadPool {
//线程池的概念与Executors类的使用
    public static void main(String[] args) {
        //缓存的线程池
        //自动根据任务数量来设定线程数去服务,多了就增加线程数,少了就减少线程数
        //这貌似跟一般情况相同,因为一般也是一个线程执行一个任务,但是这里的好处是:如果有个线程死了,它又会产生一个新的来执行任务
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 1; i <= 10; i++) {//扔5个任务
            final int task = i;
            threadPool.execute(new Runnable() {//向池子中扔任务,任务即一个Runnable
                @Override
                public void run() {
                    for (int j = 1; j <= 5; j++) {
                        try {
                            Thread.sleep(20);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + " looping of " + j + " for task of " + task);
                    }
                }
            });
        }
        System.out.println("all of 10 tasks have committed!");
        threadPool.shutdown(); //执行完任务后关闭
    }
}

使用缓存线程池的时候,会自动根据任务数量来产生线程数,即线程跟着任务走。运行结果也不贴了,有点多。

那么创建单个线程池 newSingleThreadExecutor() 就不写了,把上面那个方法改掉就行了,就只有一个线程去执行10个任务了,但是这跟我们平常直接 new 一个线程还有啥区别呢?它还有个好处就是,如果线程死了,它会自动再生一个,而我们自己 new 的就不会了。如果线程死了还要重新产生一个,也就是说要保证有一个线程在执行任务的话,那么 newSingleThreadExecutor() 是个很好的选择。


3. 线程池启动定时器


我们可以用静态方法 newScheduledThreadPool(intcorePoolSize) 来定义一个定时器线程池,可以指定线程个数。然后再调用 schedule 方法,传进去一个 Runnable 和定时时长即可,见代码:

public class ThreadPool {
    public static void main(String[] args) {
        Executors.newScheduledThreadPool(3).schedule(new Runnable() {           
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " bombing");
            }
        }, 2, TimeUnit.SECONDS);        
    }
}

定义了三个线程,会有一个率先抢到任务执行在2秒后执行。这只是创建了一个任务,如果我们要使用这个线程池去执行多个任务咋办呢?schedule 中只能传入一个 Runnable,也就是说只能传入一个任务,解决办法跟上面那些程序一样,先拿到创建的线程池,再循环多次执行 schedule,每次都传进去一个任务即可:

public class ThreadPool {
    public static void main(String[] args) {
        //拿到定时器线程池
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);
        for(int i = 1; i <= 5; i ++) { //执行5次任务
            threadPool.schedule(new Runnable() {                
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " bombing");
                }
            }, 2, TimeUnit.SECONDS);
        }   
    }
}

因为线程池中只有3个线程,但是有5个任务,所以会先执行3个任务,剩下两个任务,随机2个线程执行,看下结果:

pool-1-thread-3 bombing

pool-1-thread-2 bombing

pool-1-thread-1 bombing

pool-1-thread-2 bombing

pool-1-thread-3 bombing

如果我想5秒后执行一个任务,然后每个2秒执行一次该怎么办呢?我们可以调用另一个方法 scheduleAtFixedRate,这个方法中传进去一个 Runnable,一个定时时间和每次重复执行的时间间隔,如下:

public class ThreadPool {
    public static void main(String[] args) {
        Executors.newScheduledThreadPool(3).scheduleAtFixedRate(new Runnable() {            
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " bombing");
            }
        }, 5, 2, TimeUnit.SECONDS);     
    }
}

这样就可以5秒后执行,并且以后每隔2秒执行一次了。这个方法有个瑕疵,就是无法设置指定时间点执行,官方JDK提供的解决办法是 data.getTime()-System.currentTimeMillis() 来获取相对时间,然后放到上面方法的第二个参数即可。


相关文章
|
1月前
|
数据采集 Java API
Jsoup库能处理多线程下载吗?
Jsoup库能处理多线程下载吗?
|
3月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
284 0
|
2月前
|
安全
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
245 59
|
2月前
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
54 6
|
2月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
78 6
|
2月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
2月前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
3月前
|
Java
【编程进阶知识】揭秘Java多线程:并发与顺序编程的奥秘
本文介绍了Java多线程编程的基础,通过对比顺序执行和并发执行的方式,展示了如何使用`run`方法和`start`方法来控制线程的执行模式。文章通过具体示例详细解析了两者的异同及应用场景,帮助读者更好地理解和运用多线程技术。
44 1
|
3月前
|
网络协议 安全 Java
难懂,误点!将多线程技术应用于Python的异步事件循环
难懂,误点!将多线程技术应用于Python的异步事件循环
105 0
|
4月前
|
网络协议 C语言
C语言 网络编程(十四)并发的TCP服务端-以线程完成功能
这段代码实现了一个基于TCP协议的多线程服务器和客户端程序,服务器端通过为每个客户端创建独立的线程来处理并发请求,解决了粘包问题并支持不定长数据传输。服务器监听在IP地址`172.17.140.183`的`8080`端口上,接收客户端发来的数据,并将接收到的消息添加“-回传”后返回给客户端。客户端则可以循环输入并发送数据,同时接收服务器回传的信息。当输入“exit”时,客户端会结束与服务器的通信并关闭连接。