
1.进程包含线程
2.进程是操作系统资源分配的基本单位
3.同一个进程中的多个线程之间,共用同一份资源(内存,文件)

父类引用可以指向子类对象
Thread t = new Thread() {
};new Thread() { ... } 创建的是 Thread 的一个匿名子类实例(匿名内部类本质是子类或接口实现类),它重写了 Thread 的 run() 方法,拥有自己的具体实现。Thread t 声明了一个 Thread 类型的变量 t,用于存储这个匿名子类的实例。由于匿名子类本身是 Thread 的子类,因此可以安全地赋值给父类类型的变量。package thread;
public class demo3 {
public static void main(String[] args) throws InterruptedException {
Thread t;
t = new Thread(){
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hello niming");
}
}
};
t.start();
System.out.println("main______________________");
while (true) {
Thread.sleep(100);
System.out.println("hello main");
}
}
}注意:Thread.sleep 这是静态的方法,一定要注意
用于一般是一次性的,用完就不需要了,就可以使用匿名内部类
Runnable a = nwe Runnable() {
};接口本身不能被实例化,但匿名内部类会隐式创建一个实现了该接口的子类,并同时创建这个子类的实例。因此,new 接口名() { ... } 本质上是创建了接口的匿名实现类的实例
public class Demo4 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
Thread t = new Thread(runnable);
t.start();
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}// 使⽤lambda 表达式创建 Runnable ⼦类对象
Thread t3 = new Thread(() -> System.out.println("使⽤匿名类创建 Thread ⼦类对象"));
Thread t4 = new Thread(() -> {
System.out.println("使⽤匿名类创建 Thread ⼦类对象");
});-增加运行速度 可以观察多线程在⼀些场合下是可以提高程序的整体运行效率的。
• 使用 System.nanoTime() 可以记录当前系统的纳秒级时间戳.
• se rial 串行的完成⼀系列运算. concurrency 使用两个线程并行的完成同样的运算.
public class ThreadAdvantage {
// 多线程并不一定能提高速度,可以观察,count 不同,实际的运行效果也是不同的
private static final long count = 10_0000_0000;
public static void main(String[] args) throws InterruptedException {
// 使用并发方式
concurrency();
// 使用串行方式
serial();
}
private static void concurrency() throws InterruptedException {
long begin = System.nanoTime();
// 利用一个线程计算 a 的值
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
}
});
thread.start();
// 主线程内计算 b 的值
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
// 等待 thread 线程运行结束
thread.join();
// 统计耗时
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 / 1000;
System.out.printf("并发:%.f 毫秒\n", ms);
}
private static void serial() {
// 全部在主线程内计算 a、b 的值
long begin = System.nanoTime();
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 / 1000;
System.out.printf("串行:%.f 毫秒\n", ms);
}
}并发:399.651856毫秒
串行:720.616911毫秒
Thread 类是JVM用来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的Thread对象与之关 联。 用我们上面的例子来看,每个执行流,也需要有⼀个对象来描述,类似下图所示,而Thread类的对象 就是用来描述⼀个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。

2.1 Thread的常见的构造方法

1.第一个构造方法Thread () 需要重写 run 方法
2.第二个方法就不需要重写 run 方法了
3.第四个第五个是给线程起名字

main方法执行完,程序就结束了(进程)
我们不结束main 方法不就行了
虽然虽然主线程结束了但是t1 ,t2,t3 线程没有结束,就会影响进程继续存在,这就是前台线程
JVM自带的线程,他们的存在不影响进程的结束,他们随着进程的就结束就结束了(因为其他的都执行完了,这个线程也没有存在的必要了),这种的就是后台进程
说白了就是我们程序员的可以控制的就是前台线程,不能控制结束的就是后端线程

• ID是线程的唯一标识,不同线程不会重复
• 名称是各种调试工具用到
• 状态表示线程当前所处的⼀个情况,下面我们会进一步说明
• 优先级噶的线程理论上来说更容易被调度到
• 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有非后台线程结束后,才会结束运行。
• 是否存活,即简单的理解,为run方法是否运行结束了
• 线程的中断问题,下面我们进一步说明
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + ": 我还活着");
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": 我即将死去");
});
System.out.println(Thread.currentThread().getName() + ": ID: " + thread.getId());
System.out.println(Thread.currentThread().getName() + ": 名称: " + thread.getName());
System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());
System.out.println(Thread.currentThread().getName() + ": 优先级: " + thread.getPriority());
System.out.println(Thread.currentThread().getName() + ": 后台线程: " + thread.isDaemon());
System.out.println(Thread.currentThread().getName() + ": 活着: " + thread.isAlive());
System.out.println(Thread.currentThread().getName() + ": 被中断: " + thread.isInterrupted());
thread.start();
while (thread.isAlive()) {}
System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());
}
}这是关于 Java 守护线程(后台线程)的概念梳理:
isDaemon() 方法用于判断线程是否为守护线程(后台线程)。注意:我们创建的线程默认是前台线程包括main主线程,可以通过setDaemon()方法来修改
package thread;
public class demo_setDaemon {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(true){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("test thread");
}
},"testFinished");
//要设置在start开始之前,设置为了后台进程
t.setDaemon(true);
t.start();
for (int i = 0; i < 10000; i++) {
Thread.sleep(10);
System.out.println("888");
}
}
}运行结果:

t:线程 t 内部是一个无限循环,每秒打印一次 "test thread"(实际通过 sleep(1) 控制频率,接近持续运行),并命名为 "testFinished"。
t.setDaemon(true) 将 t 标记为守护线程,注意:此设置必须在 start() 之前调用,否则会抛出异常。
main 线程)循环 10000 次,每次休眠 10 毫秒并打印 "888",执行完毕后主线程结束。
运行结果与结论:
"888" 后),主线程作为前台线程结束。t 会被强制终止(即使其内部的无限循环未执行完),整个进程随之结束。守护线程的生命周期依赖于前台线程:
Java 中 Thread 对象与系统实际线程的关系,核心要点如下:
isAlive() 方法:用于判断系统中的线程是否处于 “存活” 状态(即线程是否已启动且未终止)。
一一对应关系:Java 代码中创建的 Thread 对象,和系统底层的实际线程是一一对应的(一个 Thread 对象对应一个系统线程)。
生命周期差异:Thread 对象的生命周期(作为 Java 对象的存在时间)和系统线程的生命周期(实际执行任务的时间)并不相同。
package thread;
public class Demo8 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
while (true) {
System.out.println(t.isAlive());
Thread.sleep(1000);
}
}
}执行结果

线程执行逻辑与生命周期:线程入口方法(如 run() 方法)中的逻辑执行完毕后,系统中对应的线程会随之销毁(操作系统层面的线程终止)。
代码示例说明:示例中线程内的循环执行 3 次,每次休眠 1000 毫秒(共 3 秒),逻辑执行完毕后,该线程在系统中就会被销毁。
3秒后这时候这个线程已经执行完成了,但是这个线程还没有结束,所以就会出现这个情况
t 是否存活(通过 isAlive() 方法),子线程 t 运行时长约 3 秒,但主线程可能打印出 3 个或 4 个 “true”(表示子线程存活)。t 结束的时机可能存在重叠,因此结果不确定(可能 3 次,也可能 4 次)。isInterrupted()isInterrupted() 是 Java 中 Thread 类的一个实例方法,用于判断当前线程对象关联的线程的中断标志位是否被设置。调用该方法后,不会清除中断标志位(这是它和 Thread.interrupted() 方法的关键区别)
t 启动后,每秒钟打印 “线程运行中...”,并通过 !Thread.currentThread().isInterrupted() 判断是否继续循环。t.interrupt() 设置线程 t 的中断标志位。t 若在 sleep 期间被中断,会捕获 InterruptedException,此时中断标志位会被自动清除;示例中手动调用 Thread.currentThread().interrupt() 重新设置标志位,确保循环条件 !isInterrupted() 为 false,从而退出循环。t.isInterrupted() 都会返回 true,因为该方法不会清除中断标志位。public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
// 循环判断线程是否被中断
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程运行中...");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
// 捕获到中断异常后,中断标志位会被清除
// 若要保持中断状态,需手动重新设置
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
System.out.println("线程因中断退出循环");
});
t.start();
Thread.sleep(3000); // 主线程休眠3秒
System.out.println("准备中断线程 t");
t.interrupt(); // 设置线程 t 的中断标志位
// 查看中断状态(不会清除标志位)
System.out.println("线程 t 是否被中断:" + t.isInterrupted());
System.out.println("线程 t 是否被中断:" + t.isInterrupted());
}
}之前我们已经看到了如何通过覆写 run 方法创建⼀个线程对象,但线程对象被创建出来并不意味着线 程就开始运行了。
这是对 Java 中 start() 方法的说明,可整理为以下要点:
• 覆写run方法是提供给线程要做的事情的指令清单
• 线程对象可以认为是把李四、王五叫过来了
• 而调⽤start()方法,就是喊⼀声:”行动起来!“,线程才真正独立去执行了。
Java 中 Thread 对象使用规则的说明,可整理为以下要点:
start() 方法的限制:每个 Thread 对象只能调用一次 start() 方法来启动线程,若重复调用会抛出异常。Thread 对象,不能重复利用已启动过的 Thread 对象来创建新线程。
调用start方法,才真的在操作系统的底层创建出⼀个线程.
李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我 们需要增加⼀些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停⽌转账,那张三 该如何通知李四停止呢?这就涉及到我们的停止线程的方式了。 目前常见的有以下两种方式:
1.通过共享的标记来进行沟通
2.调用 interrupt() 方法来通知
示例-1:使用自定义的变量来作为标志位. 需要给标志位上加volatile关键字(这个关键字的功能后面介绍)
在 Lambda 表达式中,如果希望使用其定义作用域之外的变量,就会触发 “变量捕获” 语法。
[var])和引用捕获([&var]),值捕获会复制变量的值,引用捕获则是指向原变量的引用;final 关键字修饰的变量,其值不可变。final 修饰,但在程序执行过程中其值从未被修改过的变量,Java 编译器会将其视为 “effectively final”。当 Lambda 作为回调函数时,其执行时机可能 “很晚”。
i),且以引用方式捕获(或 Python 式的闭包引用),当线程执行时,i 可能已经是循环结束后的值,导致逻辑不符合预期。
Java Lambda 捕获引用类型变量规则:

拷贝意味着这样的变量就不适合进行修改
修改一方,另一方不会随之变化(本质上是两个变量)
这样的一边变,一边不变,会对程序员造成很大的困扰,Java的大佬就想办法设计了不让你修改
GC 是 “垃圾回收(Garbage Collection)” 的缩写,是 Java 等编程语言中自动管理内存的机制,主要作用如下:

public class ThreadDemo {
private static class MyRunnable implements Runnable {
public volatile boolean isQuit = false;
@Override
public void run() {
while (!isQuit) {
System.out.println(Thread.currentThread().getName()
+ ": 别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+ ": 啊!险些误了大事");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");
target.isQuit = true;
}
}Thread.interrupt() 方法的作用说明:
t.interrupt() 会修改线程 t 内部的中断标志位(一个 boolean 类型的变量),用于主动标记线程需要终止的意图。
sleep 这类会让线程进入阻塞状态的方法。例如,若线程因 sleep 处于阻塞,调用 interrupt() 会使其退出阻塞,并抛出 InterruptedException,从而让线程有机会处理中断逻辑。
此时线程一般就会掀桌了,放置一个break 就不会了

示例-2:使用 Thread.interrupted() 或者Thread.currentThread().isInterrupted() 代替自定义标志位. Thread内部包含了⼀个boolean类型的变量作为线程是否被中断的标记

public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
// 两种方法均可以
while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ ":别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()
+ ":有内鬼,终止交易!");
// 注意此处的 break
break;
}
}
System.out.println(Thread.currentThread().getName()
+ ":啊!险些误了大事");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ":让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ":老板来电话了,得赶紧通知李四对方是个骗子!");
thread.interrupt();
}
}thread收到通知的方式有两种:
1.如果线程因为调用wait/join/sleep等方法而阻塞挂起,则以InterruptedException异常的形式通 知,清除中断标志 。当出现InterruptedException的时候,要不要结束线程取决于catch中代码的写法.可以选择忽 略这个异常,也可以跳出循环结束线程.
2.否则,只是内部的⼀个中断标志被设置,thread可以通过 Thread.currentThread().isInterrupted()判断指定线程的中断标志被设置,不清除中断标志 这种方式通知收到的更及时,即使线程正在sleep也可以马上收到
有时,我们需要等待⼀个线程完成它的工作后,才能进行自己的下⼀步工作。例如,张三只有等李四 转账成功,才决定是否存钱,这时我们需要⼀个方法明确等待线程的结束。
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Runnable target = () -> {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName()
+ ":我还在工作!");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":我结束了!");
};
Thread thread1 = new Thread(target, "李四");
Thread thread2 = new Thread(target, "王五");
System.out.println("先让李四开始工作");
thread1.start();
thread1.join();
System.out.println("李四工作结束了,让王五开始工作");
thread2.start();
thread2.join();
System.out.println("王五工作结束了");
}
}⼤家可以试试如果把两个join注释掉,现象会是怎么样的呢?
join(0) : 它会主动放弃 CPU 的使用权
thread.join()后,会被放入等待队列,直到thread线程执行完毕(run()方法结束)。thread终止时,JVM 会触发唤醒机制,将调用线程从等待队列中取出,使其重新具备参与 CPU 调度的资格,进而继续执行后续代码。附录:

这个方法我们已经非常熟悉了

public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}也是我们比较熟悉⼀组方法,有⼀点要记得,因为线程的调度是不可控的,所以,这个方法只能保证 实际休眠时间是大于等于参数设置的休眠时间的。

public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
}
}线程的状态是⼀个枚举类型Thread.State
public class ThreadState {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
}
}
}• NEW:安排了工作,还未开始行动(new 了 ,Tread 对象还没start)
• RUNNABLE:可工作的.又可以分成正在工作中和即将开始工作(就绪,1)线程正在CPU上进行,2)随时可以去CPU上执行)
. • BLOCKED:这几个都表水排队等着其他事情(也是一种阻塞,是因为锁造成的阻塞)
• WAITING:这几个都表示排队等着其他事情(没有超时间的阻塞等待)
• TIMED_WAITING:这几个都表示排队等着其他事情(指时间的阻塞,线程阻塞不参与CPU调度,不继续执行了,阻塞的时间是有限的)
• TERMINATED:工作完成了(内核中的线程已经结束了,但是Thread对象还在)

大家不要被这个状态转移图吓到,我们重点是要理解状态的意义以及各个状态的具体意思
还是我们之前的例子:
刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是NEW状态; 当李四、王五开始去窗口排队,等待服务,就进入到 RUNNABLE 状态。该状态并不表示已经被银行 工作人员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的 调度; 当李四、王五因为⼀些事情需要去忙,例如需要填写信息、回家取证件、发呆⼀会等等时,进入BLOCKED 、 WATING 、 TIMED_WAITING 状态,至于这些状态的细分,我们以后再详解; 如果李四、王五已经忙完,为 TERMINATED 状态。 所以,之前我们学过的isAlive()方法,可以认为是处于不是NEW和TERMINATED的状态都是活着的
观察1: 关注 NEW 、 RUNNABLE 、 1 2 3 4 TERMINATED 状态的转换
public class ThreadStateTransfer {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 1000_0000; i++) {
}
}, "李四");
System.out.println(t.getName() + ": " + t.getState());;
t.start();
while (t.isAlive()) {
System.out.println(t.getName() + ": " + t.getState());;
}
System.out.println(t.getName() + ": " + t.getState());;
}
}观察2:关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换
public static void main(String[] args) {
final Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("hehe");
}
}
}, "t2");
t2.start();
}使⽤jconsole可以看到t1的状态是TIMED_WAITING,t2的状态是BLOCKED
修改上面的代码,把 t1中的sleep换成wait
public static void main(String[] args) {
final Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
try {
// [
修改这⾥就可以了
!!!!!]
// Thread.sleep(1000);
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t1");
...
}使用jconsole可以看到t1的状态是WAITING
结论:
• BLOCKED表示等待获取锁,WAITING和TIMED_WAITING表示等待其他线程发来通知
. • TIMED_WAITING线程在等待唤醒,但设置了时限;WAITING线程在无线等待唤醒