线程
线程(Thread)是程序运行的执行单元,依托于进程存在。一个进程中可以包含多个线程,多线程可以共享一块内存空间和一组系统资源,因此线程之间的切换更加节省资源、更加轻量化,因而也被称为轻量级的进程。
进程
进程(Processes)是程序的一次动态执行,是系统进行资源分配和调度的基本单位,是操作系统运行的基础,通常每一个进程都拥有自己独立的内存空间和系统资源。简单来说,进程可以被当做是一个正在运行的程序。
简单来说
一个进程包含多个线程
线程的使用
线程的创建,分为以下三种方式:
- 继承 Thread 类,重写 run 方法
- 实现 Runnable 接口,实现 run 方法
- 实现 Callable 接口,实现 call 方法
1.继承 Thread 类
class ThreadTest {
public static void main(String[] args) throws Exception {
MyThread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread");
}
}
2.实现 Runnable 接口
class ThreadTest {
public static void main(String[] args) throws Exception {
MyRunnable runnable = new MyRunnable();
runnable.run();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable");
}
}
3.实现 Callable 接口
class ThreadTest {
public static void main(String[] args) throws Exception {
MyCallable callable = new MyCallable();
Object result = callable.call();
System.out.println(result);
}
}
class MyCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("Callable");
return "Success";
}
}
可以看出,Callable 的调用是可以有返回值的,它弥补了之前调用线程没有返回值的情况。
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
比如,当线程 A 持有独占锁 a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 A、B 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
线程池
把一个或多个线程通过统一的方式进行调度和重复使用的技术,避免了因为线程过多而带来使用上的开销。
- 可重复使用已有线程,避免对象创建、消亡和过度切换的性能开销。
- 避免创建大量同类线程所导致的资源过度竞争和内存溢出的问题。
- 支持更多功能,比如延迟任务线程池(newScheduledThreadPool)和缓存线程池(newCachedThreadPool)等。
创建线程池有两种方式:ThreadPoolExecutor 和 Executors,其中 Executors 又可以创建 6 种不同的线程池类型。
1.ThreadPoolExecutor 的使用
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(100));
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
// 执行线程池
System.out.println("Hello, Java.");
}
});
ThreadPoolExecutor 构造方法有以下四个,如下图所示:
其中最后一个构造方法有 7 个构造参数,包含了前三个方法的构造参数,这 7 个参数名称如下所示:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//...
}
2.ThreadPoolExecutor线程池的工作原理
当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心数量就会新建线程池进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时候任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过了最大线程数,就会执行拒绝执行策略。
ThreadPoolExecutor 是创建线程池最传统和最推荐使用的方式,创建时要设置线程池的核心线程数和最大线程数还有任务队列集合,如果任务量大于队列的最大长度,线程池会先判断当前线程数量是否已经到达最大线程数,如果没有达到最大线程数就新建线程来执行任务,如果已经达到最大线程数,就会执行拒绝策略(拒绝策略可自行定义)。线程池可通过 submit() 来调用执行,从而获得线程执行的结果,也可以通过 shutdown() 来终止线程池。
3.Executors 可以创建以下六种线程池。
- FixedThreadPool(n):创建一个数量固定的线程池,超出的任务会在队列中等待空闲的线程,可用于控制程序的最大并发数。
- CachedThreadPool():短时间内处理大量工作的线程池,会根据任务数量产生对应的线程,并试图缓存线程以便重复使用,如果限制 60 秒没被使用,则会被移除缓存。
- SingleThreadExecutor():创建一个单线程线程池。
ScheduledThreadPool(n):创建一个数量固定的线程池,支持执行定时性或周期性任务。 - SingleThreadScheduledExecutor():此线程池就是单线程的 newScheduledThreadPool。
- WorkStealingPool(n):Java 8 新增创建线程池的方法,创建时如果不设置任何参数,则以当前机器处理器个数作为线程个数,此线程池会并行处理任务,不能保证执行顺序。
《阿里巴巴 Java 开发手册》中对于线程池的创建有这样的规定,内容如下:
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的读者更加明确线程池的运行规则,规避资源耗尽的风险。