文章目录
- Java 多线程
-
- 一、多线程概述
- 二、 多线程创建方式
-
- 1、继承 Thread 类创建线程
- 2、实现 Runnable 接口
- 3、实现 Callable 接口
- 三、Thread 常用的方法
- 四、线程安全
-
- 什么是线程安全问题?
- 线程安全问题出现的原因
- 程序模拟线程安全
- 五、线程同步
-
- 线程同步方式1:同步代码块
- 线程同步方式2:同步方法
- 线程同步方式3:Lock 锁
- 六、线程池
-
- 什么是线程池?
- 创建线程池
-
- ThreadPoolExecutor 构造器
- 线程池创建的注意事项(面试常问)
- 线程池处理 Runnable 任务
- 线程池处理 Callable 接口
- 七、使用 Executors 工具类得到线程池
- 八、并发和并行
-
- 进程
- 并发的含义
- 并行的含义
- 九、线程的生命周期
-
- 什么是线程生命周期?
- Java 线程状态
- 线程的6种状态及互相转换
- 线程的6种状态总结
Java 多线程
一、多线程概述
- 线程(Thread)是一个
程序内部的一条执行流程
。 - 程序如果有一条执行流程,就是
单线程程序
。 - 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由 CPU 负责调度执行)。
二、 多线程创建方式
Java 是通过 java.lang.Thread 类的对象代表多线程。主要方式有三种 :
-
继承 Thread 类,重写 run 方法
-
实现 Runnable 接口
-
实现 Callable 接口
1、继承 Thread 类创建线程
- 创建子类 MyThread 继承 Thread 线程类
- 重写 Thread 类的 run 方法
- 创建的 MyThread 线程类的对象代表一个线程
- 调用 start() 方法启动线程(自动执行 run 方法)
示例代码:
/**
* 1、创建子类 MyThread 继承 Thread 线程类
*/
public class MyThread extends Thread{
// 2、重写 Thread 类的 run 方法
@Override
public void run() {
super.run();
// 描述线程的执行任务
for (int i = 1; i < 9; i++) {
System.out.println("子线程 MyThread 输出:" + i);
}
}
}
public class ThreadTest1 {
// main 方法是由一条默认的主线程负责执行
public static void main(String[] args) {
// 3、创建的 MyThread 线程类的对象代表一个线程
Thread t = new MyThread();
// 4、启动线程(自动执行 run 方法)
t.start(); // main线程 t线程
for (int i = 1; i < 9; i++) {
System.out.println("主线程 main 输出:" + i);
}
}
}
测试结果:
继承 Thread 类创建线程 优缺点:
- 优点:编码简单
- 缺点:继承了 Thread 类无法继承其他类,不利于功能扩展
多线程注意事项:
- 启动线程必须使用 start 方法,不是 run 方法
如果调用 run 方法,那么 Thread t = new MyThread()
就会把 t 仅仅当做一个 Java 对象而不是一个线程,此时就只有一个 main 线程,程序会先执行 run 方法然后执行下面的代码;
start 方法是向 CPU 注册,告诉 CPU Thread t = new MyThread()
是一个单独的执行流程。
- 不要把主线程任务放在启动子线程之前,否则永远先执行主线程再执行子线程
2、实现 Runnable 接口
- 定义任务类,实现 Runnable 接口
- 重写 run 方法
- 创建任务对象
- 把任务对象交给线程对象处理
代码示例:
/**
* 1、定义任务类,实现 Runnable 接口
*/
public class MyRunnable implements Runnable {
// 2、重写 run 方法
@Override
public void run() {
for (int i = 1; i < 9; i++) {
System.out.println("子线程输出=== " + i);
}
}
}
public static void main(String[] args) {
// 3、创建任务对象
Runnable target = new MyRunnable();
// 4、把任务对象交给线程对象处理
// public Thread(Runnable target)
new Thread(target).start();
for (int i = 1; i < 9; i++) {
System.out.println("主线程 main 输出:" + i);
}
}
测试结果:
实现 Runnable 接口的匿名内部类写法
public class ThreadTest2_2 {
public static void main(String[] args) {
// 直接创建 Runnable 接口的匿名内部类(任务对象)
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 1; i < 9; i++) {
System.out.println("子线程输出=== " + i);
}
}
};
new Thread(target).start();
// 简化形式1
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 9; i++) {
System.out.println("子线程2输出=== " + i);
}
}
}).start();
// 简化形式2
new Thread(() -> {
for (int i = 1; i < 9; i++) {
System.out.println("子线程3输出=== " + i);
}
}).start();
for (int i = 1; i < 9; i++) {
System.out.println("主线程输出:" + i);
}
}
}
3、实现 Callable 接口
假如线程执行完毕后需要一些数据的返回,前面两种方式重写的 run 方法均不能返回结果。
此时可以通过 Callable 接口和 FutureTask 类
来实现。
通过源码可以看出来,@FunctionalInterface
表示他是一个函数式接口,同时定义了泛型与 call 方法泛型一致。如果在实现 Callable 的时候定义了泛型则重写 call 方法的返回类型返回类型固定,否则返回 Object
代码示例:
import java.util.concurrent.Callable;
/**
* 1、定义任务类,实现 Callable 接口
*/
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;