【Java并发学习】之线程的创建
前言
Java并发一直是学习中的一个难点,之前虽然也有接触一部分的内容,不过由于当时对于并发的认识不足,所以学习的效果不是很好,乘着最近有时间,重新拾起这一部分内容,重新学习一下,并且将学习过程的笔记整理出来,本小节主要学习的内容是线程的创建
线程的相关概念
谈到并发,不可避免的会涉及到进程、线程相关的概念问题,关于这部分,有比较详细的定义,这里只是做个简单的总结与梳理
进程
进程简单的可以理解为是程序的一次执行,是动态的一个概念,进程是现在计算机体系结构中资源分配的最小单位,这里需要注意的是,在没有使用线程之前,进程还是系统进行调度的最小单位,不过由于对进程进行调度时所需要的时间以及空间代价比较大,所以现在进程一般只作为资源分配的最小单位,而不是调度的单位
线程
上面提到了,线程现在普通作为调度的最小单位,一个进程中至少包含一个线程,并且可以创建多个线程,这些线程之间共享进程所拥有的资源,并且可以执行不同的任务
Java中多线程的创建
上面我们简单了解了进程与线程的概念之后,接下来我们来学习Java中线程的创建与使用
在具体使用之前,我们需要明确两个概念
任务,任务是指具线程所要执行的具体内容,也就是所要执行的代码,需要明确的是,任务跟线程是没有太明确的关系的,任务是描述,而线程则是具体执行的工具
线程,线程只是负责执行委托给它的任务,不清楚具体的内容是什么,因为具体的内容是定义在任务之中
明确了这两个概念之后,接着来看下Java中所提供的实现多线程的工具
任务的描述
任务描述的方式总体上来看有两种,一种是没有具体返回值的任务,另一个一种是可以有返回值的任务,分别对应的接口是Runnable
、Callable<T>
接下来我们来看下具体描述任务的方式
/**
* 计算任务描述
* 实现Runnable接口并且重写run方法
*/
class CalcTask1 implements Runnable{
/**
* 具体的任务描述
* 稍后具体的任务会有对应的线程来执行
*/
@Override
public void run() {
int sum = 0;
for (int i = 0; i < 100000; i++){
sum +=i ;
}
System.out.println(sum);
}
}
/**
* 计算任务描述
* 实现Callable接口并重写call方法
*/
class CalcTask2 implements Callable<Integer>{
/**
* 具体的任务描述
* 稍后具体的任务会有对应的线程来执行
*/
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100000; i++){
sum +=i ;
}
return sum;
}
}
需要注意的是,Callable接口是泛型设计,需要为其指定对应的返回类型
除了上面的这两种描述方式外,其实还有另外一种,那就是直接继承Thread类,并且重写对应的run方法,这种方式也是可以用于描述对应的任务,不过会导致具体任务与对应的线程绑定在一起
/**
* 通过继承Thread并且重写run方式来描述任务
*/
class CalcTask3 extends Thread{
@Override
public void run() {
int sum = 0;
for (int i = 0; i < 100000; i++){
sum +=i ;
}
System.out.println(sum);
}
}
使用多线程的方式
上面我们提到了,在Java中,在使用多线程的时候,一般是把操作逻辑与具体的执行者分开,这种实现方式看上去比较麻烦,但是实际上将实现逻辑与具体执行者分离是一种非常好的设计,由于线程的创建以及回收时非常耗费资源的,所以为了避免频繁的创建与回收线程,我们可以采用线程池的方式来管理线程,而如果是将实现逻辑与具体的执行者绑定,就无法充分利用这种优势了
从本质上来讲,实现多线程的方式只有一种,那就是创建对应的线程,并且将具体的操作逻辑交给线程,并且调用对应线程的start方法,如下所示
// 创建对应的任务
Runnable calcTask = new CalcTask1();
// 将任务提交给线程
// 这里需要注意的是,如果是直接继承自Thread,则直接调用对应的
// 线程对象的run方法即可
Thread executor = new Thread(calcTask);
// 启动线程
executor.start();
上面这种实现方式是比较原始的方式,通过这种方式来启动多线程的缺点是,在需要使用线程的时候,必须自己手动创建,也就是说,无法充分利用线程池的优势,而且,还必须自己管理线程的生命周期,于是,在JDK5的时候,Java引入了一个通用的线程执行框架Executor(关于Executor将在后面学习到),从而简化了这一系列的操作过程
// 获取一个ExecutorService实例
ExecutorService service = Executors.newCachedThreadPool();
// 创建对应的任务
Runnable calcTask = new CalcTask1();
// 将任务提交给ExecutorService
service.submit(calcTask);
// 关闭对应的ExecutorService
service.shutdown();
总结
本小节主要学习了进程、线程的相关概念,以及在Java中描述任务的方式,实现 Runnable
、Callable<T>
以及继承Thread
,并且学习学习了启动线程来驱动对应任务的方式,直接将任务交给Thread对象,或者将其交给Executor框架