线程
首先明确一下,我们最经常听到的一句话,一个进程可以对应多个线程,一个线程只能属于一个进程。在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 并发编程之守护线程的创建与运行
如果你是 JDK 设计者,如何设计线程池?我跟面试官大战了三十个回合

