java线程和线程池
Posted 盖丽男
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java线程和线程池相关的知识,希望对你有一定的参考价值。
标题
线程
首先明确一下,我们最经常听到的一句话,一个进程可以对应多个线程,一个线程只能属于一个进程。在JAVA里,JVM 中的线程与操作系统的线程是一对一的关系,所以在 JVM 中每创建一个线程就需要调用操作系统提供的 API 创建线程,赋予资源,并且销毁线程同样也需要系统调用。
为什么我们需要多线程呢?
就现在的操作系统来说,天然的就是多进程/线程处理任务,最常见的,我们的电脑,可以处理word的时候听音乐,看电视等等,并不是看起来同一时刻只能做一件事。
至于多线程/进程的原因
- 随着技术发展,cpu的运行速度越来越高,而内存,硬盘的运行速度跟不上cpu的发展,所以为了提高cpu的使用效率,简单来说就是为了让cpu不闲着,所以将任务包装成进程/线程排队等待cpu处理,这个时候,需要多线程/进程。
- 不仅是cpu运行速度,还有cpu的数量,双核甚至多核的cpu,可以并行处理任务,这种时候,也需要多进程/线程的存在。
总的来说,就是为了提高效率。
线程的生命周期/状态
这个地方可以直接用一张图来说明
要注意的是,java的线程状态,和通用的线程状态,并不是严格一一对应的。
开启一个新线程
java中,常用的开启新线程的方式有三种。
- 继承Thread类
public class NewTread extends Thread{
@Override
public void run() {
System.out.println("====== A new thread ======");
}
}
- 实现Runnable接口
public class NewTread implements Runnable{
@Override
public void run() {
System.out.println("====== A new thread ======");
}
}
- 直接在函数体使用
//一种
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("====== A new thread ======");
}
});
thread.start();
//另一种
Thread thread1=new Thread(()->{
System.out.println("====== A new thread ======");
});
thread1.start();;
}
守护线程
守护线程用到的情况应该很少,
守护线程(Daemon)。这种线程的优先级很低,通常来说,当同一个应用程序里没有其他的线程运行的时候,守护线程才运行。当程序中唯一运行的的线程是守护线程时,并且守护线程执行结束后,JVM也就结束了这个程序。
因为这种特性,守护线程通常被用来作为同一程序中普通线程(用户线程)的服务提供者。它们通常是无线循环的,以等待服务请求或者执行线程的任务。它们不能做重要工作,因为我们不可能知道守护线程什么时候获取CPU时钟,并且,在没有其他线程运行时,守护线程随时可以结束。典型应用就是JAVA
GC。
看一下守护线程的创建:
public static void main(String[] args) {
Thread thread1=new Thread(()->{
System.out.println("====== A new Daemon thread ======");
});
thread1.setDaemon(true);
thread1.start();;
}
取得线程的返回值
最常用的方法,就是主线程等待或者join
public static void main(String[] args) throws InterruptedException {
Dog dog=new Dog();
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("====== A new thread ======");
dog.setName("汪汪");
}
});
//第一种情况,等待
//thread.start();
//while (dog.getName() == null){
// Thread.sleep(1000);
//}
//第二种情况:join
thread.join();
thread.start();
System.out.println(dog.toString());
}
FutureTask
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable1 = new Callable<String>() {
@Override
public String call() throws Exception {
String result="假设这里是耗时操作";
return result;
}
};
FutureTask<String> futureTask = new FutureTask<>(callable1);
new Thread(futureTask).start();
String info = futureTask.get();
}
缺点
多线程这样好,难道就没有缺点吗?那肯定是有的
- 因为线程之间,是共享进程的内存空间的,所以会有临界值的存在,当多个进程操作同一块内存空间,处理不当就会出问题,所以写出正确的多线程代码是一个问题
- 线程/进程的频繁切换,也是要耗费资源和时间的,所以如果线程设置不当,可能不仅不能提升效率,还会造成反效果。
线程池
当我们拥有了多线程,为了提高“管理水平”,就有了线程池来管理线程,线程池可以维护一组线程,避免频繁的创建/销毁线程,也算是提高效率。
为什么我们需要线程池呢?
线程池主要解决以下几个问题:
- 减小创建/销毁线程的开销
- 对线程进行统一管理,比如线程数量
线程池的生命周期/状态
线程的生命周期控制的是线程的执行或者不执行,线程池的生命周期控制的是线程池接收不接收新任务。
这里抄一张图过来:
RUNNING:能接受新任务,并处理阻塞队列中的任务
SHUTDOWN:不接受新任务,但是可以处理阻塞队列中的任务
STOP:不接受新任务,并且不处理阻塞队列中的任务,并且还打断正在运行任务的线程,就是直接撂担子不干了!
TIDYING:所有任务都终止,并且工作线程也为0,处于关闭之前的状态
TERMINATED:已关闭。
创建一个线程池
我们都知道,java默认内置了4种默认的线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
自定义
因为后面4种其实最后都用的是这个,所以先说自定义的情况。我们都知道,自定义线程池的时候,会有7个参数可以传,是这样的:
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
他们分别是:
- corePoolSize:核心线程数,线程池中始终存活的线程数。
- maximumPoolSize: 最大线程数,线程池中允许的最大线程数。
- keepAliveTime: 存活时间,线程没有任务执行时最多保持多久时间会终止。
- unit: 单位,参数keepAliveTime的时间单位,7种可选。
- workQueue: 一个阻塞队列,用来存储等待执行的任务,均为线程安全,7种可选。
- threadFactory: 线程工厂,主要用来创建线程,默及正常优先级、非守护线程。
- handler:拒绝策略,拒绝处理任务时的策略,4种可选,默认为AbortPolicy。
简单来说,如果想自己创建一个线程池,只要按照需求传入这些参数就可以,当然有一些参数不是必填的,有的会有默认值,java默认写了几个重载的方法,自己用的时候挑选一下就可以了。
newCachedThreadPool
先来看一下newCachedThreadPool的创建都有哪些参数:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可以看到,newCachedThreadPool是一个核心线程数为0,最大线程数无上限(其实是2^31-1),核心线程数之外的空闲线程回收时间是60s,然后用的是SynchronousQueue 队列,这种队列,一个offer,必须对应一个poll。
也就是说,对于newCachedThreadPool,只要有任务进来,就一定会创建或者复用一个线程执行它。所以假设任务多,而且处理任务时间比较长,newCachedThreadPool是非常不适合的,会一直创建新线程。
newCachedThreadPool 适用场景:处理任务速度 > 提交任务速度,耗时少的任务(避免无限新增线程)。
newSingleThreadPool
来看下代码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看到,newSingleThreadPool的核心线程数和最大线程数都是1,使用LinkedBlockingQueue队列,可以认为这是个无界的队列,可以一直往里面提交任务。
也就是说,对于newSingleThreadPool来说,只会有一个线程执行任务,执行完一个执行下一个排队最久的。
newCachedThreadPool 适用场景:任务需要排队执行的情况。
newFixedThreadPool
代码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到,newFixedThreadPool的核心线程数需要自己设置,
最大线程数=核心线程数,同样使用了LinkedBlockingQueue。
也就是说,newFixedThreadPool可以维护固定数目的线程,多余的任务就去排队了。
也就是说,
newFixedThreadPool 适用场景:任务数一定,执行时间比较长的情况。
newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
可以看到,newScheduledThreadPool比较特殊,它返回了一个ScheduledExecutorService,它的核心线程数需要用户设置,最大线程数无上限,同时超出核心线程数的空闲线程会在10s之后被销毁。newS,cheduledThreadPool比较特殊的一点在于,他可以延时执行,通过提交任务时的参数可以实现,比如:
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
ExecutorService workStealingPool = Executors.newWorkStealingPool();
for (int i = 0; i < 20; i++) {
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
}, 10, TimeUnit.SECONDS);
}
}
在例子中,所有被提交的任务都会在提交之后的10s后执行。
具体的newScheduledThreadPool可以看下这个文章,写的挺详细的:ScheduledThreadPoolExecutor
引用
java创建一个守护线程_JAVA 并发编程之守护线程的创建与运行
以上是关于java线程和线程池的主要内容,如果未能解决你的问题,请参考以下文章
newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段