目录
[1. 线程与进程的关系](#1. 线程与进程的关系)
[2. 线程状态:Thread.sleep(millis: 1000)](#2. 线程状态:Thread.sleep(millis: 1000))
[3. 方法重写要求](#3. 方法重写要求)
[写法1:继承 Thread,重写 run](#写法1:继承 Thread,重写 run)
[写法2:实现 Runnable,搭配 Thread执行](#写法2:实现 Runnable,搭配 Thread执行)
[写法3:继承 Thread,使用匿名内部类](#写法3:继承 Thread,使用匿名内部类)
[写法4:实现 Runnable,使用匿名内部类](#写法4:实现 Runnable,使用匿名内部类)
[三、常见面试题:Thread对象与 start()的关系](#三、常见面试题:Thread对象与 start()的关系)
[问题:多次调用 start()会怎样?](#问题:多次调用 start()会怎样?)
[四、补充:高耦合 vs 低耦合(生活类比)](#四、补充:高耦合 vs 低耦合(生活类比))
一、多线程基础概念
1. 线程与进程的关系
-
线程是轻量级进程 ,进程是重量级进程。
-
进程是操作系统资源分配的基本单位 ,线程是操作系统调度执行的基本单位。
-
进程之间不会相互影响 ,但线程可能会相互影响(因为线程共享进程资源)。
-
进程内部包含线程,同一个进程的多个线程之间共享同一份资源。
-
线程创建不需要申请资源,销毁也不需要释放资源(相比进程开销小)。
-
CPU发展瓶颈 → 多核心(多线程充分利用多核)。
2. 线程状态:Thread.sleep(millis: 1000)
sleep让当前线程进入阻塞状态(暂停执行指定时间)。
3. 方法重写要求
- 父类方法和子类方法,签名完全一致(包括:方法名、参数列表、声明的抛出异常...)。
二、线程创建的几种写法
写法1:继承 Thread,重写 run
java
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
写法2:实现 Runnable,搭配 Thread执行
java
// 1. 定义 Runnable 实现类
class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// 2. 创建线程
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start(); // 启动新线程,执行 Runnable 内部的 run
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
-
Runnable自身一般不会单独执行 ,需要搭配"载体"(Thread)执行。 -
不再需要重写
run(已实现在Runnable中)。 -
本质 :启动的新线程会执行
Runnable内部的run方法。
写法3:继承 Thread,使用匿名内部类
java
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t.start(); // 记住:调用 start 才是真正创建线程
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
-
步骤:
-
创建
Thread的子类(匿名内部类,无名字)。 -
重写
run。 -
创建
Thread子类的实例,并用t引用指向。
-
-
特点:方便、一次性使用。
写法4:实现 Runnable,使用匿名内部类
java
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
t.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
- 此处针对
Runnable定义匿名内部类,返回结果是一个Runnable的引用,把这个引用传入Thread。
写法5:【简化】lambda表达式
java
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
-
数据结构角度来看:枚举、lambda、反射。
-
lambda本质上是一个"匿名函数",很多地方都能支持。 -
Java 语法非常死板,必须得有类;最初 Java 和其他语言都有 lambda,先搞了个匿名内部类过渡。
-
函数式接口 (如
Runnable是函数式接口)。
三、常见面试题:Thread对象与 start()的关系
问题:多次调用 start()会怎样?
java
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("hello");
});
thread.start();
thread.start(); // 报错!
thread.start();
}
-
报错:
java.lang.IllegalThreadStateException: Thread already started -
原因:Java 约定,一个
Thread对象,和一个操作系统中的线程是"一一对应"关系。 -
因此,一个
Thread对象,只能start一次。
- 后续学习
Thread上的其他方法/属性,都是控制这个对应系统中的线程。
四、补充:高耦合 vs 低耦合(生活类比)
-
高耦合:女朋友生病了,我推掉所有工作,全身心在医院照顾 → 假设陌生人生病了,对我没啥影响,朋友圈点赞之交,该干啥干啥,影响非常小。
-
低耦合 :任务(Runnable)和线程(Thread)解耦 → 任务和线程概念绑定(继承
Thread)时,若改成其他形式,需要大规模修改代码;而Runnable没有和线程概念绑定,任务可方便地迁移到其他载体上。
五、包(package)的概念
-
Java 中类太多(标准库、第三方库、自己写的)→ 乱。
-
包:一组有关系的类放到一起。
-
包默认情况下不会引入到代码中,使当前代码引入的类比较少、不容易乱。
-
import java.util.*;:导入包中所有类(不推荐)。 -
最基础常用的:
import java.lang.*(默认导入)。