java学习笔记——线程池

1. 为什么有线程池?
我们知道,当程序接到一个任务后就会创建一个线程去执行这个任务。此时JVM会为线程分配内存并初始化成员变量。任务完成后就销毁。那么,来一个任务就创建一个线程,之后继续销毁?这样是不是太浪费资源了。线程可是非常宝贵的资源啊。所以我们可不可以在线程执行完后将其保存起来,等以后有相似的任务后,再run呢?就好比运沙车,没沙运的时候将车放入车库?所以线程池的概念就出来了。数据库连接池也是相应的道理。数据库连接也是宝贵的资源啊(_
2.线程池的原理
既然是一个存放线程的池子,那么,线程池的工作要务肯定就是控制管理运行的线程啦。既然要探讨原理,我们肯定得从源码说起了。我们先来介绍一下四个常见的线程池

  1. 四个基本线程池
    创建单个线程:Executors.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

我们可以看出,Executors.newSingleThreadExecutor()返回的是一个只有一个线程的线程池,在线程死亡(发生异常)之后重新启动一个线程替代之前的继续工作下去。
创建缓存线程:Executors.newCachedThreadPool()

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

创建固定数量的线程池:Executors.newFixedThreadPool()

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

创建定期执行,定时线程:Executors.newScheduledThreadPool()

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

通过观察四个线程池的创建代码,我们可以发现,线程池的创建都是基于一个类:ThreadPoolExecutor,只是传入的参数各异。我们通过源码来看一下这个类是怎样创建线程池的。
其中一个构造函数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize:线程池核心线程数量
maximumPoolSize:线程池所允许的最大线程数量
keepAliveTime:指定了空闲线程的存活时间
unit:时间的单位(秒、毫秒)
workQueue:任务队列,等待线程执行
handler :线程池的拒绝策略(当线程数量到达允许的最大值时如何拒绝任务)
线程池工作过程
通过上面源码,我们可以大致看出线程池工作大致分为:创建线程,添加任务,运行线程执行任务。
1.我们可以看出,任务队列workQueue是作为一个参数传进去的,既,线程池刚刚创建的时候,是不会马上执行任务的。
2.线程池通过execute()方法来添加一个任务。通过下面源码分析,我们可以知道,添加一个任务后,线程池会进行以下判断
当当前线程数量<corePoolSize时,线程池会去创建一个线程来执行这个任务。
当线程数量>corePoolSize时,会将这个任务添加到workQueue中。
如果队列满了,但是<maximumPoolSize时,线程池还是会创建非核心线程来执行任务。
但当线程数量到达极限,队列也满的时候,就会抛出RejectExecutionException异常

final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

3.当一个线程执行完一个任务后,会去workQueue阻塞队列中获取下一个任务执行。
4.当线程的空闲时间keepAliveTime抵达之后,如果这时线程数量>corePoolSize,线程池会销毁这些空闲线程。
拒绝策略
当线程数量都在运行,且无法创建新线程的时候,线程池无法为新任务服务,阻塞队列也放不下了。这时,就要采用合理的拒绝策略来拒绝执行这些新任务。
JDK 内置的拒绝策略如下:

  1. AbortPolicy : 直接抛出异常,阻止系统正常运行。
  2. CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
  3. DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
  4. DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。
    以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际需要,完全可以自己扩展 RejectedExecutionHandler 接口。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
文件上传是Web开发中常见的功能之一,Java中也提供了多种方式来实现文件上传。其中,一种常用的方式是通过Apache的commons-fileupload组件来实现文件上传。 以下是实现文件上传的步骤: 1.在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency> ``` 2.在前端页面中添加文件上传表单: ```html <form method="post" enctype="multipart/form-data" action="upload"> <input type="file" name="file"> <input type="submit" value="Upload"> </form> ``` 3.在后台Java代码中处理上传文件: ```java // 创建一个DiskFileItemFactory对象,用于解析上传的文件 DiskFileItemFactory factory = new DiskFileItemFactory(); // 设置缓冲区大小,如果上传的文件大于缓冲区大小,则先将文件保存到临时文件中,再进行处理 factory.setSizeThreshold(1024 * 1024); // 创建一个ServletFileUpload对象,用于解析上传的文件 ServletFileUpload upload = new ServletFileUpload(factory); // 设置上传文件的大小限制,这里设置为10MB upload.setFileSizeMax(10 * 1024 * 1024); // 解析上传的文件,得到一个FileItem的List集合 List<FileItem> items = upload.parseRequest(request); // 遍历FileItem的List集合,处理上传的文件 for (FileItem item : items) { // 判断当前FileItem是否为上传的文件 if (!item.isFormField()) { // 获取上传文件的文件名 String fileName = item.getName(); // 创建一个File对象,用于保存上传的文件 File file = new File("D:/uploads/" + fileName); // 将上传的文件保存到指定的目录中 item.write(file); } } ``` 以上代码中,首先创建了一个DiskFileItemFactory对象,用于解析上传的文件。然后设置了缓冲区大小和上传文件的大小限制。接着创建一个ServletFileUpload对象,用于解析上传的文件。最后遍历FileItem的List集合,判断当前FileItem是否为上传的文件,如果是,则获取文件名,创建一个File对象,将上传的文件保存到指定的目录中。 4.文件上传完成后,可以给用户一个提示信息,例如: ```java response.getWriter().write("File uploaded successfully!"); ``` 以上就是使用Apache的commons-fileupload组件实现文件上传的步骤。需要注意的是,文件上传可能会带来安全隐患,因此在处理上传的文件时,需要进行严格的校验和过滤。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值