1.什么是线程池
线程池(Thread Pool)是一种并发编程中常用的技术,用于管理和重用线程。它由线程池管理器、工作队列和线程池线程组成。
线程池的基本概念是,在应用程序启动时创建一定数量的线程,并将它们保存在线程池中。当需要执行任务时,从线程池中获取一个空闲的线程,将任务分配给该线程执行。当任务执行完毕后,线程将返回到线程池,可以被其他任务复用。
线程池是一种多线程处理模式,它允许多个线程在程序中被重用,而不是在需要的时候创建新的线程,使用完后销毁。线程池的主要目的是提高资源利用率和系统的响应速度。其运行机制就是,通过线程的复用,任务队列和池的管理来进行运行。
线程复用:通过预先创建一定数量的线程并将其保存在池中,避免了频繁创建和销毁线程的开销。当有任务需要执行时,从线程池中取出一个空闲线程来执行任务,任务完成后线程回到池中等待下一个任务。
任务队列:当所有线程都在忙碌时,新的任务会被放入队列中等待空闲线程来处理。
池的管理:线程池会自动管理线程的生命周期,包括线程的创建、执行和销毁,确保系统资源的高效利用。
其优势主要有:
提高性能:减少了线程创建和销毁的开销,因为创建和销毁线程是昂贵的操作。
控制并发量:通过限制线程池中的最大线程数量,可以避免过多的线程导致系统资源耗尽。
提高资源利用率:通过线程复用,提高CPU和内存的使用效率。
便于管理:通过线程池,统一管理和调度线程,简化了并发程序的开发。
当然,线程池也存在着其局限性:
1.需要进行线程池配置,而线程池的配置,决定着线程池的性能和效果的好坏。他需要你根据具体的场景进行动态的更改配置去进行优化,从而保证其性能和效果实现最大化。
2.死锁:如果一个任务在执行过程中需要等待另一个任务完成,而后者又需要第一个任务完成某些操作,这样的循环依赖会导致死锁。
3.资源泄漏:如果线程池没有正确关闭(调用shutdown或shutdownNow方法),可能会导致线程池中的线程一直存在,造成资源泄漏
4.任务饥饿:如果线程池的任务队列中的任务被长时间占用,新的任务可能无法及时得到处理。这种情况被称为“任务饥饿”,可能会导致系统响应缓慢。
5. 资源消耗:线程池在创建时会初始化一组线程,这些线程会一直占用系统资源(如内存和CPU),即使它们处于空闲状态。这可能会在高负载系统中导致资源浪费
2.线程池的创建和销毁
1. 使用 Executors 工具类
1.1Executors类提供了一些静态工厂方法来方便地创建几种常用的线程池:
//创建一个固定大小的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
1.2:提交任务:使用execute()
或submit()
方法将任务提交给线程池。
executor.execute(new MyRunnable()); // 提交Runnable任务
Future<String> future = executor.submit(new MyCallable()); // 提交Callable任务,并返回Future对象
1.3:销毁任务(关闭线程):
executor.shutdown();
2.使用 ThreadPoolExecutor 类
int corePoolSize = 5;//线程核心,不能少于0
int maximumPoolSize = 10;//最大线程的数量
long keepAliveTime = 1;//非核心线程的空闲存活时间设置为1分钟。非核心线程是超过corePoolSize创建的线程
TimeUnit unit = TimeUnit.MINUTES;//设置存活时间的单位,这里是分钟。
创建一个有界的工作队列,容量为100。当线程池中的线程都在忙碌时,新的任务会被放入这个队列中等待执行。
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
//创建一个ThreadPoolExecutor实例
ThreadPoolExecutor customThreadPool = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
3.线程和进程的区别
进程(Process):进程就像是马路上的一辆辆汽车。每一辆汽车都是一个独立的个体。进程是系统进行资源分配和调度的基本单位。当你启动一个程序(比如打开一个浏览器或者一个文本编辑器),系统就会为这个程序分配一定的资源(如内存空间),并创建一个进程来运行这个程序。
线程(Thread):线程就像是汽车里的司机。一个进程可以包含多个线程,这些线程共享进程的资源(一辆汽车里可以有多个司机,每个司机都负责驾驶汽车的一部分路程。同样地,一个进程中的多个线程可以并行执行不同的任务,共享进程的内存空间,但各自有独立的执行路径。
4.synchronized
synchronized 是 Java 语言中的一个关键字,它用于实现线程间的同步(synchronization),即控制多个线程对共享资源的访问。当一个线程访问某个对象的某个 synchronized(同步)代码块或方法时,其他线程对该对象的所有其他 synchronized 代码块或方法的访问将被阻塞,直到该线程完成其工作并退出 synchronized 块或方法。这样可以防止多个线程同时访问和修改同一共享资源,从而避免了数据不一致的问题。synchronized 锁时,我们实际上是指由 synchronized 关键字创建的一个内置锁(也称为监视器锁或互斥锁)。在Java对象中,每一个对象都有一个内置锁,当你有一个共享资源,所有的线程都要使用这个共享资源的时候,就会造成数据不一致或线程安全问题。但是你给这个共享资源加上一个锁(synchronized 锁)时,线程要去使用这个资源就要获取这个锁才能去使用共享资源,其他的线程因为没有锁就无法使用,他必须要等待这个锁被释放才行。
5.将业务和线程实现脱离
在编程中,意味着将处理业务逻辑的代码与创建和管理线程的代码分离开来。这种做法有助于提高代码的可读性、可维护性和可扩展性。将业务逻辑(即应用程序的实际功能和数据处理部分)与线程管理(即控制和管理线程的执行和调度的部分)进行解耦或分离。这种分离的目的在于提高代码的可维护性、可扩展性和并发性能。在Java中,可以通过实现Runnable
接口或继承Thread
类来定义业务逻辑,并将这些任务提交给线程池(如ThreadPoolExecutor
)来执行。线程池负责线程的创建、调度和管理,而业务逻辑代码则只需要关注于实现具体的业务功能。这种分离使得代码更加清晰、易于维护,并且能够更好地利用系统资源提高并发性能