一、基本概念
进程
程序(Program)就是指令和数据的集合,而进程(Process)就是一个程序运行的过程,是系统分配资源的一个单位。
Windows 任务管理器打开后,能看到的一个个的条目就是进程。
线程
而每个进程可以有很多个线程(Thread),线程是 CPU 调度和执行的单位。比如 QQ 可以同时发消息和接收消息,这里就分别有发送消息和接受消息的两个线程。CPU 在每个线程之间随机选择执行,保证在一段时间内,每个线程都能按照规定和要求完成。
多线程
多线程让 CPU 能同时处理多个任务。
很多多线程都是模拟出来的,真正的多线程是指有多个 CPU,即多核,比如服务器。如果是模拟出来的多线程,比如单核 CPU,CPU 在同一个时间点只能执行一个代码,此时 CPU 在每一个线程之间反复横跳执行,因为切换的非常快,宏观层面上来看,聊 QQ 和听音乐看起来是同时运行的,但微观层面来看,每个时刻只有一个线程在运行。
进程和线程的区别
进程单独占有⼀定的内存地址空间,所以进程间存在内存隔离,数据是分开的,数据共享复杂但是同步简单,各个进程之间互不⼲扰;⽽线程共享所属进程占有的内存地址空间和资源,数据共享简单,但是同步复杂。可靠性较低。
进程的创建和销毁不仅需要保存寄存器和栈信息,还需要资源的分配回收以及⻚调度,开销较⼤;线程只需要保存寄存器和栈信息,开销较⼩。
概念总结
- 线程就是独立的执行路径。
- 程序运行时,即使没有自己创建线程,那么至少也会有一个 main 线程作为系统的入口,用于执行整个程序。
- 在一个进程中,如果开辟了多个线程,线程的运由调度器安排调度,先后顺序由系统决定,无法人为直接干预。
- 对同一份资源操作时(比如打印机),存在资源抢夺问题,需要加入并发控制。
- 线程切换有额外的开销。
- 每个线程在自己的工作内存中交互,内存控制不得当会造成数据不一致。
二、Java 中的线程
- main 线程
- 用户开辟的线程
- GC 线程
- 其他线程
三、创建 Java 线程的几种方式
继承 Thread 类
不建议使用,Java 中的单继承会造成局限性。
- 编写自定义类继承 Thread 类
public class MyThread extends Thread {
}
- 重写父类的 run()方法
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("MyThread" + i);
}
}
}
- 创建自定义类的实例,并调用 start()方法
public static void main(String[] args) {
MyThread myth = new MyThread();
myth.start();
for (int i = 0; i < 50; i++) {
System.out.println("MainThread" + i);
}
}
- 查看输出结果
MyThread0
MyThread1
MyThread2
MyThread3
MyThread4
MyThread5
MyThread6
MyThread7
MyThread8
MyThread9
MyThread10
MyThread11
MyThread12
MainThread0
MainThread1
MainThread2
MainThread3
MainThread4
MyThread13
MyThread14
...
进程已结束,退出代码为 0
实现 Runnable 接口
建议使用,避免单继承造成的局限性,使用灵活,方便同一个对象被多个线程使用。
原理:通过 Thread 对象代理执行。
- 创建自定义类并实现 Runnable 接口
public class MyRunnableImpl implements Runnable{
}
- 重写 run()方法
public class MyRunnableImpl implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("MyThread" + i);
}
}
}
- 创建自定义类的实例,创建 Thread 类的实例并传入自定义类实例,调用 Thread 类实例 start()方法
public static void main(String[] args) {
MyRunnableImpl runnable = new MyRunnableImpl();
Thread th = new Thread(runnable);
th.start();
for (int i = 0; i < 50; i++) {
System.out.println("MainThread" + i);
}
}
- 查看输出结果
MyThread0
MyThread1
MyThread2
MyThread3
MyThread4
MyThread5
MyThread6
MyThread7
MyThread8
MyThread9
MyThread10
MyThread11
MyThread12
MainThread0
MainThread1
MainThread2
MainThread3
MainThread4
MyThread13
MyThread14
...
进程已结束,退出代码为 0
实现 Callable 接口
原理和上一个方法类似。
- 编写自定义类实现 Callable 接口,需要返回值类型,重写 call 方法
public class MyCallableImpl implements Callable<String> {
@Override
public String call() throws Exception {
DateFormat format = DateFormat.getDateInstance();
// 获取当前线程名
String currName = Thread.currentThread().getName();
return currName + ":" + format.format(new Date());
}
}
- 创建对象
MyCallableImpl myCallable1 = new MyCallableImpl();
MyCallableImpl myCallable2 = new MyCallableImpl();
MyCallableImpl myCallable3 = new MyCallableImpl();
- 创建执行服务
ExecutorService service = Executors.newFixedThreadPool(3);
- 提交执行
Future<String> task1 = service.submit(myCallable1);
Future<String> task2 = service.submit(myCallable2);
Future<String> task3 = service.submit(myCallable3);
- 获取结果
try {
String str1 = task1.get();
String str2 = task2.get();
String str3 = task3.get();
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
} catch (Exception e) {
e.printStackTrace();
}
- 关闭服务
service.shutdown();
- 查看运行结果
pool-1-thread-1:2021年12月6日
pool-1-thread-2:2021年12月6日
pool-1-thread-3:2021年12月6日
进程已结束,退出代码为 0