【并发编程】(学习笔记-Java线程)-part2

while (true) {

log.debug(“running”);

}

}, “t1”).start();

new Thread(() -> {

while (true) {

log.debug(“running”);

}

}, “t2”).start();

}

}

得出的结果:

  • 交替执行

  • 谁先谁后,不由我们控制

3.查看进程线程的方法


windows

  • 任务管理器可以直接查看进程和线程数,也可以用来杀死进程。

  • tasklist查看进程(通过cmd)。

  • taskkill杀死进程。

linux

  • ps -fe查看所有进程。

  • ps -fT -p <PID>查看某个进程(PID)的所有线程。

  • kill <PID>杀死进程。

  • top按大写H切换是否显示线程。

  • top -H -p <PID>查看某个进程(PID)的所有线程。

Java

  • jps命令查看所有Java进程。

  • jstack <PID>查看某个Java进程(PID)的所有线程状态。

  • jconsole来查看某个Java线程中线程的运行情况(图形界面)。

4.原理之线程运行


栈与栈帧

Java Virtual Machine Stacks(Java 虚拟机栈)

我们都知道JVM中由堆,栈,方法区所组成,其中栈内存是给谁用呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。

  • 每个栈有多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存。

  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。

线程上下文切换(Thread Context Switch)

因为一下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码。

  • 线程的cpu时间片用完。

  • 垃圾回收。

  • 有更高优先级的线程需要运行。

  • 线程自己调用slepp,yield,wait,join,park,synchronized,lock等方法。

当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条jvm指令的执行地址,是线程私有的

  • 状态包括程序计数器,虚拟机栈中每个栈帧的信息,如局部变量,操作数栈,返回地址等。

  • Context Switch 频繁发生影响性能。

5.常用方法


| 方法名 | static | 功能说明 | 注意 |

| — | — | — | — |

| start() | | 启动一个新线程,在新的线程运行run方法中的代码 | start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException |

| run() | | 新线程启动后会调用的方法 | 如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为 |

| join() | | 等线程运行结束 | |

| join(long n) | | 等待线程运行结束,最多等待n毫秒 | |

| getld() | | 获取线程长整型的id | id唯一 |

| getName() | | 获取线程名 | |

| setName(String) | | 修改线程名 | |

| getPriority() | | 获取线程优先级 | |

| setPriority(int) | | 修改线程优先级 | java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率 |

| getState() | | 获取线程状态 | Java 中线程状态是用 6 个 enum 表示,分别为: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED |

| isInterrupted() | | 判断是否被打断 | 不会清除 打断标记 |

| isAlive() | | 线程是否存活(还没有运行完毕) | |

| interrupt() | | 打断线程 | 如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标记 ;如果打断的正在运行的线程,则会设置打断标记 ;park 的线程被打断,也会设置打断标记 |

| interrupted() | static | 判断当前线程是否被打断 | 会清除打断标记 |

| currentThread() | static | 获取当前正在执行的线程 | |

| sleep(long n) | static | 让当前执行的线程休眠n毫秒,休眠时让出cpu的时间片给其它线程 | |

| yield() | static | 提示线程调度器让出当前线程对cpu的使用 | 主要是为了测试和调试 |

5-1 start与run

start

  1. 启动一个新线程,在新的线程运行run方法中的代码。

  2. start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException

run

  1. 新线程启动后会调用的方法。

  2. 如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为

调用run

程序还是在main线程执行,还是同步执行的

@Slf4j

public class Test4 {

public static void main(String[] args) {

Thread t1 = new Thread(“t1”) {

@Override

public void run() {

log.debug(“running…”);

}

};

t1.run();

log.debug(“running”);

}

}

输出

12:17:47 DEBUG [main] (Test4.java:11) - running…

12:17:47 DEBUG [main] (Test4.java:15) - runnin

调用start

此时是两个线程,异步执行

@Slf4j

public class Test4 {

public static void main(String[] args) {

Thread t1 = new Thread(“t1”) {

@Override

public void run() {

log.debug(“running…”);

}

};

t1.start();

log.debug(“running”);

}

}

输出

12:14:15 DEBUG [main] (Test4.java:15) - running

12:14:15 DEBUG [t1] (Test4.java:11) - running…

小结

  • 直接调用run方法是在主线程中执行了run,没有启动新的线程。

  • 使用start是启动了新的线程,通过新的线程间接执行run中的代码。

5-2 sleep与yield

sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态,可通过state()方法查看

  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException

  3. 睡眠结束后的线程未必会立刻得到执行

  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性 。

//休眠一秒

TimeUnit.SECONDS.sleep(1);

//休眠一分钟

TimeUnit.MINUTES.sleep(1);

yield

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态(仍然有可能被执行),然后调度执行其它线程

  2. 具体的实现依赖于操作系统的任务调度器

线程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它。

  • 如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但cpu闲时,优先级几乎没作用。

举个栗子:

@Slf4j

public class Test9 {

public static void main(String[] args) throws InterruptedException {

Runnable task1 = () -> {

int count = 0;

for (; ; ) {

System.out.println("—>1 " + count++);

}

};

Runnable task2 = () -> {

int count = 0;

for (; ; ) {

Thread.yield();

System.out.println(" —>2 " + count++);

}

};

Thread t1 = new Thread(task1, “t1”);

Thread t2 = new Thread(task2, “t2”);

//t1.setPriority(Thread.MAX_PRIORITY);

//t2.setPriority(Thread.MIN_PRIORITY);

t1.start();

t2.start();

}

}

可以看出yield()起到了作用,t2进入Runnable状态,开启设置优先级后结果也类似,需要注意的是,最终的结果无论是哪种方式,都是由调度器决定最后时间片的分配

5-3 join方法详解

join

用于等待某个线程结束。哪个线程内调用join()方法,就等待哪个线程结束,然后再去执行其他线程。

为什么需要join

@Slf4j

public class Test10 {

static int r = 0;

public static void main(String[] args) {

test1();

}

private static void test1() {

log.debug(“开始”);

Thread t1 = new Thread(() -> {

log.debug(“开始”);

try {

sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

log.debug(“结果”);

r = 10;

});

t1.start();

//t1.join();

log.debug(“结果为:{}”, r);

log.debug(“结束”);

}

}

13:36:47 DEBUG [main] (Test10.java:18) - 开始

13:36:47 DEBUG [Thread-0] (Test10.java:20) - 开始

13:36:47 DEBUG [main] (Test10.java:30) - 结果为:0

13:36:47 DEBUG [main] (Test10.java:31) - 结束

13:36:48 DEBUG [Thread-0] (Test10.java:26) - 结束

分析

  • 因为主线程和线程t1是并行执行的,t1线程需要1秒之后才能算出r=10

  • 而主线程一开始就要打印r的结果,所以只能打印出r=0

解决方法

  • join,加在t1.start()之后即可。

应用之同步:

以调用的角度来讲,如果

  • 需要等待结果返回,才能继续运行就是同步

  • 不需要等待结果返回,就能继续运行就是异步

5-4 interrupt方法详解

用于打断阻塞(sleep wait join…)的线程。 处于阻塞状态的线程,CPU不会给其分配时间片。

  • 如果一个线程在在运行中被打断,打断标记会被置为true

  • 如果是打断因sleep wait join方法而被阻塞的线程,会将打断标记置为false

打断sleep,wait,join的线程

这里以sleep为例:

@Slf4j

public class Test11 {

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(() -> {

log.debug(“sleep…”);

try {

Thread.sleep(5000);//wait,join

} catch (InterruptedException e) {

e.printStackTrace();

}

}, “t1”);

t1.start();

Thread.sleep(1000);

log.debug(“interrupt”);

t1.interrupt();

log.debug(“打断标记:{}”, t1.isInterrupted());//false

}

}

打断正常运行的线程

打断正常运行的线程,不会清空打断状态

@Slf4j

public class Test12 {

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(() -> {

while (true) {

boolean interrupted = Thread.currentThread().isInterrupted();

if (interrupted) {

log.debug(“退出循环!”);

break;

}

}

}, “t1”);

t1.start();

Thread.sleep(1000);

log.debug(“interrupt”);

t1.interrupt();

log.debug(“打断标记:{}”, t1.isInterrupted());//true

}

}

18:51:03 DEBUG [main] (Test12.java:20) - interrupt

18:51:03 DEBUG [t1] (Test12.java:13) - 推出循环!

18:51:03 DEBUG [main] (Test12.java:22) - 打断标记:true

模式之两阶段终止:

img

代码实现:

@Slf4j

public class Test23 {

public static void main(String[] args) throws InterruptedException {

TwoPhaseTermination tpt = new TwoPhaseTermination();

tpt.start();

Thread.sleep(3500);

tpt.stop();

}

}

@Slf4j

class TwoPhaseTermination {

private Thread monitor;//设置监控线程

//启动监控线程

public void start() {

monitor = new Thread(() -> {

while (true) {

Thread current = Thread.currentThread();

if (current.isInterrupted()) {

log.debug(“料理后事”);

//终止线程执行

break;

}

try {

Thread.sleep(1000); //情况1,sleep打断,打断标记为false

log.debug(“执行监控记录”); //情况2,正常打断,打断标记为true

} catch (InterruptedException e) { //情况1

e.printStackTrace();

current.interrupt();//重新设置打断标记

}

}

}, “monitor”);

monitor.start();

}

//停止监控线程

public void stop() {

//打断线程

monitor.interrupt();

}

}

5-5 不推荐的打断方法

这些方法已过时,容易破坏同步代码块,造成线程死锁

  • stop():停止线程运行(可能造成共享资源无法被释放,其他线程无法使用这些共享资源)

  • suspend():(暂停线程)

  • resume():(恢复线程)方法

6.主线程和守护线程


默认情况下,Java 进程需要等待所有线程都运行结束才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

栗子:

@Slf4j

public class Test15 {

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(() -> {

while (true) {

if (Thread.currentThread().isInterrupted()) {

break;

}

}

log.debug(“结束”);

}, “t1”);

t1.setDaemon(true);//设置t1为守护线程,即使主线程执行完了,t1线程也会结束,不会死循环

t1.start();

Thread.sleep(1000);

log.debug(“结束”);

}

}

守护线程的应用

  • 垃圾回收器线程就是一种守护线程

  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

7.线程的状态


7-1 五种状态

这是从 操作系统 层面来描述的

img

  • 初始状态:仅是在语言层面创建了线程对象,还未与操作系统线程关联(例如线程调用了start方法)

  • 可运行状态:(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行

  • 运行状态:指获取了 CPU 时间片运行中的状态

  • 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换

  • 阻塞状态

  • 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入 【阻塞状态】

  • 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】

  • 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们

  • 终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

7-2 六种状态

这是从 Java API 层面来描述的,根据 Thread.State 枚举,分为六种状态

img

  • NEW 线程刚被创建,但是还没有调用 start() 方法

  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了操作系统层面的 【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)

  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,如sleep就位TIMED_WAITING, join为WAITING状态。后面会在状态转换一节详述。

  • TERMINATED 当线程代码运行结束

这里附《并发编程的艺术》这本书的一张图:

在这里插入图片描述

举个栗子:

@Slf4j

public class Test16 {

static Object obj;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

由于篇幅有限,这里就不一一罗列了,20道常见面试题(含答案)+21条MySQL性能调优经验小编已整理成Word文档或PDF文档

MySQL全家桶笔记

还有更多面试复习笔记分享如下

Java架构专题面试复习

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
bject obj;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-GT3ZTqWL-1713547847776)]

[外链图片转存中…(img-txr1Zl00-1713547847778)]

[外链图片转存中…(img-NsYTcKOe-1713547847778)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

由于篇幅有限,这里就不一一罗列了,20道常见面试题(含答案)+21条MySQL性能调优经验小编已整理成Word文档或PDF文档

[外链图片转存中…(img-6awGyg0y-1713547847779)]

还有更多面试复习笔记分享如下

[外链图片转存中…(img-ErVYr6e4-1713547847780)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 30
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值