线程
什么是线程
线程是调度CPU的最小单位。线程模型分为内核级模型(KLT)和用户级模型(ULT)。JVM使用的是内核级模型(KLT)。Java线程和OS线程是保持1:1的映射关系, 也就是说有一个java线程也会在操作系统里有对应的线程。
线程的生命状态
java线程有多种生命状态 :
- NEW 初始化状态/新建
- RUNNABLE 运行状态
- BLOCKED 阻塞
- WAITING 等待
- TIME_WAITING 超时等待
- TERMINATED 终结
线程的创建
可以通过构建一个Thread的子类来定义一个线程,然后构建一个子类的对象,并调用start方法。不要直接调用run方法,run方法只会执行同一个线程中的任务,而不会启动新线程。调用start方法,会创建一个执行run的新线程。
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("创建了一个线程!!");
}
}
public class ThreadMain {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
我们可以看到Thread类也是实现了Runnable接口,那么我们是不是也可以通过实现Runnable接口创建线程呢?
当然是可以的,具体代码示例展示如下:
public class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("实现runnable接口创建线程!!");
}
}
public class ThreadMain {
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2();
Thread thread = new Thread(myThread2);
thread.start();
}
}
除此之外还可以通过实现定义Callable接口,重写call()方法。FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。
public class MyThread3 implements Callable {
@Override
public Object call() throws Exception {
System.out.println("执行一个线程!!!");
return Thread.currentThread().getName();
}
}
public class ThreadMain {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread3 myThread3 = new MyThread3();
FutureTask futureTask = new FutureTask(myThread3);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println("结果为:"+ futureTask.get());
}
}
总结
1、由于java是单继承多实现,所以继承Thread对象的类无法在继承其他类。
2、通过实现Runnable、Callable接口创建线程的方式,线程类还可以继承其他类;适合多个线程处理同份资源,将cpu、代码、数据分开;
3、Runnable规定必须实现run方法,且无法抛出异常。Callable规定实现call方法,能够抛出异常。Callable执行完任务后,可以返回结果。
线程池
通过对线程的介绍,我们可以看到,若是对每一个任务都创建一个线程,那么将会频繁的消耗系统资源。面对这样的问题,Java提供了线程池来解决这个问题。
什么是线程池?
顾名思义,线程池就是一个线程缓存,线程是稀缺资源,如果被无限制创建,不仅会消耗系统资源,还会降低系统稳定性。因此java种提供线程池对线程统一分配、调优和监控。
什么时候使用线程池?
· 单个任务处理执行时间较短
· 需要处理的任务数量很大
线程池的优势?
重用已存在的线程,减少线程的创建、消亡的开销,从而提高性能。 提高响应速度,当任务到达时,无需创建线程就能立即执行. 提高线程的可管理性。
线程池的实现
Executors创建线程池存在4种方法:
- newFixedThreadPool(int nThreads) — 创建固定数目线程的线程池
- newCachedThreadPool() – 创建一个缓存线程池
- newSingleThreadExecutor() – 创建一个单线程化的Executor
- newScheduledThreadPool(int corePoolSize) – 创建一个定时及周期性的执行的线程池
但是不建议使用Executors创建线程池
是因为使用Executors创建线程池存在OOM(OutOfMemoryError堆内存不存)的风险。原因:不设置容量,底层是一个无边界的阻塞队列,默认长度未Integer.MAX_VALUE。对于一个无边界队列来说,是可以不断地向队列种加入任务的,任务过多就可能导致OOM。
如何正确创建线程池?
避免使用Executors创建线程池,主要是避免使用其中的默认实现。推荐使用ThreadPoolExecutor类的构造方法自己创建线程池,在创建的同时,给BlockingQueue指定容量就可以了
下面介绍一下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
每个参数代表的含义如下:
- corePool
线程池中的核心线程数 ,当提交一个任务时,线程池创建一个新的线程执行任务,直到当前线程数等于corePoolSize;
如果当前线程数为corePoolSize,继续提交的任务将被保存到阻塞队列中,等待执行。
如果执行了线程的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
- maximumPoolSize
线程池中允许的最大线程数。
如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSizw
- keepAliveTime
线程池维护线程所容许的空闲时间。
当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程数外的线程不会立即被销毁,而是会等待,直到等待时间超过了keepAliveTime;
- unit
keepAliveTime的单位
- workQueue
用来保存等待执行任务的阻塞队列,且任务必须实现Runnable接口。
JDK中提供了如下阻塞队列:
ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务。
- LinkedBlockingQueue:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常高于ArrayBlockingQueue;···
- threadFactory
他是ThreadFactory类型的变量,用来创建线程。默认使用Executors.defaultThreadFactory()来创建线程。
使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级,并且是非守护线程,同时也设置了线程的名称。
- handler
线程池的饱和(拒绝策略)策略,当阻塞队列满了,且没有空闲的工作线程,如过想继续提交任务,必须采取一种策略处理该任务。
示例代码如下:
public class MyThreadPoolExecutor {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,100,1000,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 20; i++) {
// 创建任务对象
Runnable task = () -> System.out.println(Thread.currentThread().getName() + ":任务执行啦");
threadPoolExecutor.execute(task);
}
// 关闭线程池
threadPoolExecutor.shutdown();
}
}
线程池的执行流程?
- 如果workerCount(工作线程)<corePoolSize,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount(工作线程)>=corePoolSize,且线程池内的阻塞队列未满,则将任务添加到阻塞队列中。
- 如果workerCount(工作线程)>=corePoolSize && workerCount(工作线程) < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
- 如果 workerCount(工作线程) >= maximumPoolSize,且线程池内的阻塞队列已满,则根据拒绝策略来处理该任务,默认处理方式是抛异常。
如有错误感谢指正