从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。–Wikipedia
- 其中,从硬件上实现并发,即把CPU设计为多个核心。我们经常听到了“四核”、“八核”就是这个意思。举个不恰当的例子,“四核”就意味着一个人有四个大脑,可以同时进行思考,非常牛逼。
- 从软件上实现并发,即同时开启多个进程和线程。我们要研究的就是,如何在同一进程中开启多个线程,并且要如何管理它们。
程序、进程、线程
程序: 是含有指令和数据的文件
,被存储在磁盘
或其他的数据存储设备中。
进程诞生背景:
1,在多道程序环境
下,允许多个程序并发
执行,此时它们将失去封闭性,并具有间断性及不可再现性的特征。
2,为此引入了进程(Process)的概念,以便更好地描述和控制程序的并发执行,实现操作系统的并发性和共享性。
进程的定义为:
- 进程是进程实体的
运行过程
,是系统进行资源分配和调度的一个独立单位。 - 它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,
内存空间
,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
线程诞生背景:
1,引入进程的目的,是为了使多道程序并发执行,以提高资源利用率和系统吞吐量;
2,而引入线程,则是为了减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能。
线程的定义为:
- 与进程相似,但线程是一个比进程
更小
的执行单位。 - 一个进程在其执行的过程中可以产生
多个
线程。 - 与进程不同的是同类的多个线程
共享
同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程
。
理解:
1,你的公司这个整体是个操作系统;
2,公司有多个部门,一个部门下有多个小组,每个小组是一个进程;
3,每个小组下有多个成员,每个成员就是一个线程;
4,当一个项目A下来的时候,公司把A项目拿个你所在技术部的第一小组做;
5,小组就是一个进程,小组中的每个成员就是一个线程。
Java的多线程机制
Java实现多线程机制的方式:
- java.lang.Thread类实现
- java.lang.Runnable接口实现
- java.util.concurrent.Callable接口实现
注意:
每个线程,都有自己的名字。main方法作为主线程,线程名就是“main”,其他新建线程也有名字,默认是“Thread-0”,“Thread-1”类此递增。
Thread类实现多线程
操作步骤:
- 自定义一个类继承Thread类。
- 重写Thread类的run方法,把自定义线程的任务代码写在run方法上。
- 创建Thread的子类对象,并且调用start方法启动一个线程。
- 注意:千万不要直接调用run方法,调用start方法的时候线程就会开启,线程一旦开启就会执行run方法中代码,如果直接调用run方法,那么就 相当于调用了一个普通的方法而已。
类方法:
- String getName(); 获取线程名字
- static Thread currentThread(); 在main线程中执行,返回CPU当前正在执行的线程名
- static void sleep(毫秒数); 线程运行到此代码时,睡眠了一定的毫秒数后再执行
第一个Thread类继承实现的多线程:
public class Main {
public static void main(String[] args) {
//创建第一个线程实例
MyThread mt = new MyThread();
// 修改线程名字
mt.setName("第一个线程运行");
// 启动线程
mt.start(); //告诉系统创建一个独立的线程来运行这个实例中的run方法
// 创建第二个线程实例
MyThread mt2 = new MyThread();
mt2.setName("第二个线程运行");
// 启动线程
mt2.start(); //告诉系统又创建一个独立的线程来运行这个实例中的run方法
}
}
class MyThread extends Thread {
@Override
public void run() {//
for (int i = 0; i < 5; i++) {
System.out.println(getName() + ":" + i);
}
}
}
//注意:每次运行的结果都是不一样的
Thread构造器:
- Thread(String name) 创建线程类时就赋值名字(需要给线程类写一个有参构造器,调用Thread类的构造方法)
- Thread(Runnable target) 包装Runnable对象(线程任务类)
- Thread(Runnable target, String) 包装线程任务类,并初始化线程名字
获取:
- String getName(); 获取线程名字
- void setName(); 设置线程名字。注意设置名字的操作应该在调用start方法之前。
- long getId(); 获取线程ID
- Thread.state getState(); 获取线程状态,Java 中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
- static Thread currentThread(); 返回对当前正在执行的线程对象的引用。这个代码在哪个线程中,就得到哪个线程对象。
System.out.println(Thread.currentThread().getName()); 输出当前正在运行线程的名
每个线程,都有自己的名字。main方法作为主线程,线程名就是“main”,其他新建线程也有名字,默认是“Thread-0”,“Thread-1”类此递增。
判定:
- boolean isInterrupted(); 判断是否被打断
- boolean isAlive(); 判断是否还存活
休眠:
- public static void sleep(long millis) throws InterruptedException
- public static void sleep(long millis, int nanos) throws InterruptedException
- 注意:调用sleep方法会进入计时等待状态,等时间到了,进入的是就绪状态而并非是运行状态!
Runnable接口实现
操作步骤:
- 自定义一个类实现Runnable接口。
- 实现Runnable接口 的run方法,把自定义线程的任务定义在run方法上。
- 创建Runnable实现类对象。
- 创建Thread类的对象,并且把Runnable实现类的对象作为实参传递。
- 调用Thread对象 的start方法开启一个线程。
Runnable只有一个方法: void run();
第一个Runnable接口实现的多线程:
public class Main {
public static void main(String[] args) {
//创建线程实例
MyThread mt = new MyThread();
Thread t = new Thread(mt);
// 修改线程名字
t.setName("张三");
// 启动线程
t.start();
// 创建线程实例
MyThread mt2 = new MyThread();
Thread t2 = new Thread(mt2);
t2.setName("老王");
// 启动线程
t2.start();
}
}
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
Callable接口实现
背景:
- Thread类、Runnable接口实现线程,都不能向外抛出异常、值返回
- 要实现线程执行后有返回值、异常的则用Callable
实现步骤:
- 定义一个线程任务类实现Callable接口,申明线程执行的结果类型。
- 重写线程任务类的call方法,这个方法可以直接返回执行的结果。
- 创建一个Callable线程任务对象。
- 把Callable任务对象包装成一个未来任务(FutureTask)对象。FutureTask可以在线程执行完毕后取得到执行结果。注意FutureTask的本质就是Runnable,便于被Thread包装。
- 将FutureTask对象放入Thread。
//第一个Callable接口实现实例
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) {
//3.创建线程任务对象
Callable callable = new MyCallable();
//4.把Callable包装成未来任务对象
FutureTask<String> futureTask = new FutureTask<>(callable);
//5.放入Thread类创建线程对象
Thread t01 = new Thread(futureTask, "Callable线程实例01");
t01.start();
for(int i=0;i<5;i++) {
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
//在最后去获取线程执行的结果。如果该线程没有结果,则让出CPU等线程执行完再取结果
try {
String rs = futureTask.get(); // 获取call方法返回的结果(正常/异常结果)
System.out.println(rs);
} catch (Exception e) {
e.printStackTrace();
}
}
/*
输出样例:
Callable线程实例01==>0
Callable线程实例01==>1
Callable线程实例01==>2
Callable线程实例01==>3
Callable线程实例01==>4
main==>0
main==>1
main==>2
main==>3
main==>4
Callable线程实例01线程结果值为:500
Process finished with exit code 0
*/
}
//1.定义线程任务类
class MyCallable implements Callable<String> {
//2.重写线程任务执行体
@Override
public String call() throws Exception {
int sum = 0;
for(int i=0; i<5;i++) {
System.out.println(Thread.currentThread().getName() + "==>"+i);
sum += 100;
}
return Thread.currentThread().getName()+"线程结果值为:"+sum;
}
}
类实现和接口实现的区别
问题:为什么需要定一个类去实现Runnable接口呢?继承Thread类和实现Runnable接口有啥区别呢?
解答:
- 实现Runnable接口,避免了继承Thread类的单继承局限性。覆盖Runnable接口中的run方法,将线程任务代码定义到run方法中。
- 创建Thread类的对象,只有创建Thread类的对象才可以创建线程。
- 线程任务已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的子类对象,所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。
匿名内部类实现多线程
public class Main {
public static void main(String[] args) {
//继承方式 XXX extends Thread{ public void run(){}}
new Thread() {
public void run() {
System.out.println("!!!");
}
}.start();
// 实现接口方式 XXX implements Runnable{ public void run(){}}
Runnable r = new Runnable() {
public void run() {
System.out.println("###");
}
};
new Thread(r).start();
//简写法
new Thread(new Runnable() {
public void run() {
System.out.println("@@@");
}
}).start();
}
}
线程特性
线程状态
根据Thread.State的静态内部类枚举,线程分为六种状态:
- NEW状态:线程被构建。执行了new后的状态。
- RUNNABLE状态:可运行状态,Java将操作系统中的线程就绪和运行两种状态统称为“运行中”。执行了st- 法后的状态。
- BLOCKED状态:阻塞状态。
- WAITING状态:等待状态。表名当前线程需要等待其他线程做出一些特定的动作(通知或者中断)以后才-
- TIME_WAITING状态:超时等待状态。不同于WATING,它可以在指定的事件内自行返回。
- TERMINATED状态:终止状态。
线程状态间的转换:
线程的上下文切换: - 多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。
- 当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
- 概括来说,当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
如何获取线程状态
public class Hello {
public static void main(String[] args) {
MyThread mth = new MyThread();
Thread th = new Thread(mth); //线程0
System.out.println(th.getState()); //执行过new以后,线程的状态就为NEW
th.start();
System.out.println(th.getState());
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println("启动线程:"+Thread.currentThread().getName());
}
}
停止线程
停止线程的思想方法:
A. 标志位法
B. Thread类的stop方法(已过时,禁止使用!)
C.抛异常法
D.线程中段法(见下文)
核心要点:
- 不要使用stop或者destroy等过时方法
- 不建议在run方法中使用死循环,可以利用次数
- 建议使用标志位来停止线程
设置一个标志位,停止线程
public class Hello {
public static void main(String[] args) {
MyThread mth = new MyThread();
Thread th0 = new Thread(mth); //线程0
th0.start();
//让线程th0运行3秒钟,自动停止
try {
Thread.sleep(3000);
} catch(InterruptedException e) {
e.printStackTrace();
}
mth.stopCurrentThread();
}
}
class MyThread implements Runnable {
//volatile修饰符用来保证其它线程读取的总是该变量的最新的值
private volatile boolean exitFlag = false;
public void stopCurrentThread() {
exitFlag = true;
}
@Override
public void run() {
while(!exitFlag) {
System.out.println("线程:"+Thread.currentThread().getName()+"正在运行...");
}
System.out.println("线程:"+Thread.currentThread().getName()+"停止运行");
}
}
中断线程
void interrupt();
中断此线程。static boolean interrupted();
检查当前线程是否被中断,重置当前线程的中断状态为false;boolean isInterrupted();
检查某线程是否已经中断。
线程中断来停止线程
public class Hello {
public static void main(String[] args) {
MyThread mth = new MyThread();
Thread th0 = new Thread(mth); //线程0
th0.start();
//让线程th0运行3秒钟,自动停止
try {
Thread.sleep(3000);
} catch(InterruptedException e) {
e.printStackTrace();
}
th0.interrupt(); //向th0线程发送终止通知
}
}
class MyThread implements Runnable {
@Override
public void run() {
while(!Thread.interrupted()) {
System.out.println("线程:"+Thread.currentThread().getName()+"正在运行...");
}
System.out.println("线程:"+Thread.currentThread().getName()+"停止运行");
}
}
守护线程
什么是守护线程:
- 唯一的用途是为其他线程提供服务;
- 当之神下守护线程时,JVM就退出了;
Boolean isDaemon();
检查此线程是否为守护线程。
void setDaemon(Boolean isDaemon);
设定该线程为守护线程
守护线程用法
public class Hello {
public static void main(String[] args) {
MyThread mth = new MyThread();
Thread th0 = new Thread(mth); //线程0
Thread th1 = new Thread(mth); //线程1
th0.start();
System.out.println("th0是守护线程?"+th0.isDaemon());
th1.setDaemon(true); //先设置为守护线程,再启动
th1.start();
System.out.println("th1是守护线程?"+th1.isDaemon());
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println("我是线程:" + Thread.currentThread().getName());
}
}
优先级
操作方法(java.lang.Thread类):
- void setPriority(int newPriority); 设置此线程的优先级
- void getPriority(); 返回此线程的优先级
可选值:
- static int MAX_PRIORITY 线程可以具有的最大优先级。
- static int MIN_PRIORITY 线程可以具有的最低优先级。
- static int NORM_PRIORITY 分配给线程的默认优先级。
数字越大,优先级越高:有效范围在1~10之间
线程优先级使用示例
public class Hello {
public static void main(String[] args) {
MyThread mth = new MyThread();
Thread th0 = new Thread(mth); //线程0
Thread th1 = new Thread(mth); //线程1
th0.setPriority(10); //先设定优先级,再启动
th0.start();
System.out.println("线程th0的优先级:"+th0.getPriority());
th1.setPriority(5);
th1.start();
System.out.println("线程th1的优先级:"+th1.getPriority());
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println("我是线程:" + Thread.currentThread().getName());
}
}
一个核心问题
为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
new
一个 Thread,线程进入了新建
状态;- 调用
start()
方法,会启动一个线程并使线程进入了就绪
状态,当分配到时间片后就可以开始运行了。 - start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
- 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
- 总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。