线程的概念
18.1.1 程序和进程的概念
程序 - 数据结构 + 算法,主要指存放在硬盘上的可执行文件。
进程 - 主要指运行在内存中的可执行文件。
目前主流的操作系统都支持多进程,为了让操作系统同时可以执行多个任务,但进程是重量级的,也就是新建一个进程会消耗CPU和内存空间等系统资源,因此进程的数量比较局限。
18.1.2 线程的概念
为了解决上述问题就提出线程的概念,线程就是进程内部的程序流,也就是说操作系统内部支持多进程的,而每个进程的内部又是支持多线程的,线程是轻量的,新建线程会共享所在进程的系统资源,因此目前主流的开发都是采用多线程。
多线程是采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机制。
18.2 线程的创建(重中之重)
18.2.1 Thread类的概念
java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。
Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。
案例代码
public class ThreadTest {
public static void main(String[] args) {
// 1.使用无参方式构造Thread类型的对象
// 由源码可知:Thread类中的成员变量target的数值为null。
Thread t1 = new Thread();
// 2.调用run方法进行测试
// 由源码可知:由于成员变量target的数值为null,因此条件if (target != null)不成立,跳过{}中的代码不执行
// 而run方法中除了上述代码再无代码,因此证明run方法确实啥也不干
t1.run();
// 3.打印一句话
System.out.println("我想看看你到底是否真的啥也不干!");
}
}
18.2.2 创建方式
自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法。
自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用start方法。
线程的创建和启动方式一 继承Thread类
//封装类package com.lagou.task18;
public class SubThreadRun extends Thread {
@Override
public void run() {
// 打印1 ~ 20之间的所有整数
for (int i = 1; i <= 20; i++) {
System.out.println("run方法中:i = " + i); // 1 2 ... 20
}
}
}
//测试类package com.lagou.task18;
public class SubThreadRunTest {
public static void main(String[] args) {
// 1.声明Thread类型的引用指向子类类型的对象
**Thread t1 = new SubThreadRun();
// 2.调用run方法测试,本质上就是相当于对普通成员方法的调用,因此执行流程就是run方法的代码执行完毕后才能继续向下执行
//t1.run();
// 用于启动线程,Java虚拟机会自动调用该线程类中的run方法
// 相当于又启动了一个线程,加上执行main方法的线程是两个线程
t1.start();
// 打印1 ~ 20之间的所有整数
for (int i = 1; i <= 20; i++) {
System.out.println("-----------------main方法中:i = " + i); // 1 2 ... 20
}
}
}
**
线程的创建和启动方式二 Runnable接口类
//封装类
**package com.lagou.task18;**
**public class SubRunnableRun implements Runnable {**
** @Override**
** public void run() {**
** // 打印1 ~ 20之间的所有整数**
** for (int i = 1; i <= 20; i++) {**
** System.out.println("run方法中:i = " + i); // 1 2 ... 20**
** }**
** }**
**}**
测试类**package com.lagou.task18;**
**public class SubRunnableRunTest {**
** public static void main(String[] args) {**
** // 1.创建自定义类型的对象,也就是实现Runnable接口类的对象**
** **``**SubRunnableRun srr = new SubRunnableRun();**
** // 2.使用该对象作为实参构造Thread类型的对象**
** // 由源码可知:经过构造方法的调用之后,Thread类中的成员变量target的数值为srr。**
** **``**Thread t1 = new Thread(srr);**
** // 3.使用Thread类型的对象调用start方法**
** // 若使用Runnable引用构造了线程对象,调用该方法(run)时最终调用接口中的版本**
** // 由run方法的源码可知:if (target != null) {**
** // target.run();**
** // }**
** // 此时target的数值不为空这个条件成立,执行target.run()的代码,也就是srr.run()的代码**
** t1.start();**
** //srr.start(); Error**
** // 打印1 ~ 20之间的所有整数**
** for (int i = 1; i <= 20; i++) {**
** System.out.println("-----------------main方法中:i = " + i); // 1 2 ... 20**
** }**
** }**
**}**
线程的创建和启动方式三 实现Callable接口
(1)实现Callable接口
从Java5开始新增加创建线程的第三种方式为实现java.util.concurrent.Callable接口。
方法声明 功能介绍
V call()
计算结果并返回
(2)FutureTask类
java.util.concurrent.FutureTask类用于描述可取消的异步计算,该类提供了Future接口的基本实现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,也可以用于获取方法调用后的返回结果。
常用的方法如下:
方法声明 功能介绍
FutureTask(Callable callable)
根据参数指定的引用来创建一个未来任务
V get()
获取call方法计算的结果
执行代码 实现Callable接口
<br />`**package **
com.lagou.task18**;<br />**
import **java.util.concurrent.Callable**
;**import **
java.util.concurrent.ExecutionException**;<br />**
import **java.util.concurrent.FutureTask**
;**public class **
ThreadCallableTest **implements **
Callable {<br />``<br />
**@Override<br />**
**public **
Object **call**
() **throws **
Exception {<br />
**// **
计算**1 ~ 10000**
之间的累加和并打印返回** **
int **sum = **
0**;<br />**
for **(**
int **i = **
1**; **
i <= **10000**
; **i++) {**`<br />`** sum +=i**
;** **
}<br />
System.**_out_**
.println(**"**
计算的累加和是:**" **
+ sum)**; **
// 50005000** **
return **sum**
;** **
}<br />``<br />
**public static void **
main**(String[] args) {**`<br />
** ThreadCallableTest tct = **``**new **``**ThreadCallableTest()**``**;<br />**``** **``**FutureTask ft = **``**new **``**FutureTask(tct)**``**;<br />**``** **``**Thread t1 = **``**new **``**Thread(ft)**``**;<br />**``** **``**t1.start()**``**;<br />**``** **``**Object obj = **``**null;<br />**``** try **``**{**
** obj = ft.get()**``**;<br />**``** **``**} **``**catch **``**(InterruptedException e) {**
** e.printStackTrace()**``**;<br />**``** **``**} **``**catch **``**(ExecutionException e) {**
** e.printStackTrace()**``**;<br />**``** **``**}**
** System.**``**_out_**``**.println(**``**"**``**线程处理方法的返回值是:**``**" **``**+ obj)**``**; **``**// 50005000<br />**``** **``**}**
**}**
18.2.5 方式的比较
继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类,而实现
Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后的开发中推荐使用第二种方式。
18.2.3 相关的方法
方法声明 功能介绍**Thread() **
**使用无参的方式构造对象**
**Thread(String name) **
**根据参数指定的名称来构造对象**
**Thread(Runnable target)**
**根据参数指定的引用来构造对象,其中**``**Runnable是个接口类型**
**Thread(Runnable target,**
**String name)**
**根据参数指定引用和名称来构造对象**
**void run()**
**若使用**``**Runnable引用构造了线程对象,调用该方法时最终调用接口中的版本**
**若没有使用**``**Runnable引用构造线程对象,调用该方法时则啥也不做**
**void start() **
**用于启动线程,**``**Java**``**虚拟机会自动调用该线程的**``**run**``**方法**
18.2.4 执行流程
执行main方法的线程叫做主线程,执行run方法的线程叫做新线程/子线程。
main方法是程序的入口,对于start方法之前的代码来说,由主线程执行一次,当start方法调用成功后线程的个数由1个变成了2个,新启动的线程去执行run方法的代码,主线程继续向下执行,两个线程各自独立运行互不影响。
当run方法执行完毕后子线程结束,当main方法执行完毕后主线程结束。
两个线程执行没有明确的先后执行次序,由操作系统调度算法来决定。