基本概念
- 程序(program)=数据结构 + 算法,一段静态的代码
- 进程(process):程序的一次执行过程。
程序是静态的,进程是动态的
进程是资源分配的基本单位,系统在运行时会为每个进程分配不同的内存区域。
- 线程(Thread):程序内部的一条执行路径
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程是调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
若一个进程中的多个线程共享相同的内存单元/内存地址空间->虽然使得线程之间的通信更简便,高效。但多个线程操作共享的系统资源可能会带来安全隐患。
Java中,方法区和堆一个进程一份,虚拟栈和程序计数器一个线程一份。
单核CPU:一种假的多线程,并发
多核CPU:并行
多线程的优势
- 提高应用程序的响应。对图形化界面更有意义,可以增强用户体验。
- 提高CPU的利用率
- 改善程序结构。将既长又复杂的进程分成多个线程,独立运行,利于理解和修改
什么时候用多线程
- 程序需要同时执行两个或多个线程
- 程序需要实现一些需要等待的任务时,例如,用户输入,文件读写操作,网络操作,搜索等。
- 需要一些后台运行的程序
线程的创建和使用
方式1:继承Thread类
package vip.zdkk.java;
/**
* @author: zdkk
* @create 2022-05-11 17:18
*
* 多线程的创建方式1:
* 1. 继承Thread类
* 2. 重写Thread类run方法 -> 写需要执行的操作
* 3. 创建Thread类子类的对象
* 4. 通过此对象调用start()方法 -> ①启动当前线程 ②调用当前线程的run()
* 不能用该对象再调用start()方法,会抛出 IllegalThreadStateException 异常,需要重新创建一个对象
*
* 例:遍历100以内所有的偶数
*/
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
for (int i = 0; i < 100; i++) {
if ((i % 2) == 1) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
new MyThread().start();
}
}
// 输出
main 1
Thread-0 0
Thread-0 2
Thread-0 4
Thread-0 6
main 3
Thread-0 8
main 5
main 7
main 9
Thread-0 10
Thread-0 12
Thread-0 14
Thread-0 16
Thread-0 18
Thread-0 20
Thread-0 22
Thread-0 24
Thread-0 26
Thread-0 28
main 11
main 13
main 15
main 17
main 19
main 21
main 23
main 25
main 27
main 29
main 31
main 33
main 35
main 37
main 39
Thread-0 30
main 41
main 43
main 45
main 47
main 49
main 51
main 53
main 55
main 57
Thread-0 32
main 59
main 61
main 63
main 65
Thread-0 34
Thread-0 36
Thread-0 38
Thread-0 40
Thread-0 42
Thread-0 44
Thread-0 46
Thread-0 48
Thread-0 50
Thread-0 52
Thread-0 54
Thread-0 56
main 67
Thread-0 58
Thread-0 60
Thread-0 62
Thread-0 64
Thread-0 66
Thread-0 68
main 69
Thread-0 70
Thread-0 72
Thread-0 74
Thread-0 76
main 71
Thread-0 78
Thread-0 80
Thread-0 82
Thread-0 84
main 73
Thread-0 86
Thread-0 88
Thread-0 90
Thread-0 92
main 75
Thread-0 94
main 77
main 79
main 81
main 83
main 85
Thread-0 96
main 87
main 89
main 91
Thread-0 98
main 93
main 95
main 97
main 99
Thread-1 0
Thread-1 2
Thread-1 4
Thread-1 6
Thread-1 8
Thread-1 10
Thread-1 12
Thread-1 14
Thread-1 16
Thread-1 18
Thread-1 20
Thread-1 22
Thread-1 24
Thread-1 26
Thread-1 28
Thread-1 30
Thread-1 32
Thread-1 34
Thread-1 36
Thread-1 38
Thread-1 40
Thread-1 42
Thread-1 44
Thread-1 46
Thread-1 48
Thread-1 50
Thread-1 52
Thread-1 54
Thread-1 56
Thread-1 58
Thread-1 60
Thread-1 62
Thread-1 64
Thread-1 66
Thread-1 68
Thread-1 70
Thread-1 72
Thread-1 74
Thread-1 76
Thread-1 78
Thread-1 80
Thread-1 82
Thread-1 84
Thread-1 86
Thread-1 88
Thread-1 90
Thread-1 92
Thread-1 94
Thread-1 96
Thread-1 98
Process finished with exit code 0
问题一:我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。
问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start().
方式2:实现Runnable接口
package vip.zdkk.java;
/**
* @author: zdkk
* @create 2022-05-11 22:55
*
* 创建多线程的方法2:实现Runnable接口
* 1. 创建一个实现Runnable接口的类
* 2. 实现类实现Runnable接口中的抽象方法run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()
* 调用该Thread对象的run方法,而此run方法内调用了传给Thread类实例的Runnable类型的对象的run方法
*/
public class ThreadTest2 {
public static void main(String[] args) {
// 3. 创建实现类的对象
MyThread2 m1 = new MyThread2();
// 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
// 5. 通过Thread类的对象调用start(),实际调用的是该Thread类对象的run方法,然后在方法内部调用m1的run方法
new Thread(m1).start();
// 再启动一个线程
new Thread(m1).start();
}
}
// 1. 创建一个实现Runnable接口的类
class MyThread2 implements Runnable {
// 2. 实现类实现Runnable接口中的抽象方法run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
}
第二种方式的优势:
- 类的继承更贴合实际,而不是通通继承Thread
- 共享数据不用写成
static
类型
线程的调度
基于时间片的抢占式调度策略
线程的优先级等级:
MAX_PRIORITY = 10;
MIN_PRIORITY = 1;
NORM_PRIORITY = 5
默认是NORM_PRIORITY
高优先级的线程要抢占低优先级线程CPU的执行权。但只是从概率上讲,高优先级的线程高概率被执行。