近来读了好几篇“老马说编程”写的 Java 多线程文章,这里记录一下第一篇的摘要,以及拓展查找了点相关的东西,算是复习巩固(有些冷知识平时还真没留意过)。原文:(65) 线程的基本概念 / 计算机程序的思维逻辑
在 Java 中创建线程有两种方式,一种是继承 Thread,另外一种是实现 Runnable 接口。
extends Thread
-
继承 Thread 的类,重载 run 方法,方法签名是固定的,public,没有参数,没有返回值,不能抛出 checked Exception (可以抛出unchecked Exception),下图帮大家复习一下两种 Exception
调用 Thread 的 start 方法后,线程开始执行;如果不调用 start,run 的代码会在当前线程执行
怎么确认在跑的那一个线程呢?
/**
* Returns a reference to the currently executing thread object.
* @return the currently executing thread.
*/
public static native Thread currentThread();
难得看到 native 关键字,所以 Thread 是 JNI (Java Native Interface),略懵逼,哪位高人来指导下?
- 线程 id 是递增的整数,线程name可以设置
/* For generating thread ID */
private static long threadSeqNumber;
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
implements Runnable
- 仅仅实现 Runnable 是不够的,需要new Thread,启动线程都是调用 Thread 对象的 start 方法
程序什么时候退出,关于 daemon 线程
- 启动线程会启动一条单独的执行流,整个程序只有在所有线程都结束的时候才退出
- 但 daemon 线程是例外,当整个程序中剩下的都是 daemon 线程的时候,程序就会退出。
- daemon 线程一般是其他线程的辅助线程,在它辅助的主线程退出的时候,它就没有存在的意义了。
- Java 也会创建多个线程,除了 main 线程外,至少还有一个负责垃圾回收的线程,这个线程就是 daemon 线程,在 main 线程结束的时候,垃圾回收线程也会退出。
关于 Thread 的小知识
- sleep():睡眠期间,该线程会让出CPU,但睡眠的时间不一定是确切的给定毫秒数,可能有一定的偏差。
- yield():告诉操作系统的调度器,我现在不着急占用CPU,你可以先让其他线程运行。(想起 Python 的 yield 😄)
- ** join()**:让调用join的线程等待该线程结束,比如希望main线程在子线程结束后再退出
public static void main(String[] args) throws InterruptedException {
Thread thread = new HelloThread();
thread.start();
thread.join();
}
- 已经 deprecated 的方法
- public final void stop()
- public final void suspend()
- public final void resume()
stop() 会引起线程涉及的 monitor 状态的不确定性,所以不建议被调用,那么怎么 stop 一个 Thread 啊,懵逼了吧。
- 怎么合理的 stop 一个线程呢?
参考 StackOverflow 的帖子 (Thread.stop() - deprecated)
The Answer: In Java there's no clean, quick or reliable way to stop a thread.
Instead, Threads rely on a cooperative mechanism called Interruption. This means that Threads could only signal other threads to stop, not force them to stop.
- 一种办法是通过一个简单的 flag 变量来控制
public class Task implements Runnable {
private volatile boolean isRunning = true;
public void run() {
while (isRunning) {
//do work
}
}
public void kill() {
isRunning = false;
}
}
- 类似还有通过 interrupt 来中断线程
public class Task1 implements Runnable {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
// do work
}
}
}
// in other thread, call interrupt()
myThread.interrupt();
如果run里面不判断 Thread.currentThread().isInterrupted() 直接执行 interrupt() 会怎样呢?哪位读者来回答一下 _
多线程的共享变量
不同执行流可以访问和操作相同的变量
不同执行流可以执行相同的程序代码
当多条执行流执行相同的程序代码时,每条执行流都有单独的栈,方法中的参数和局部变量都有自己的一份
[内存可见性] 多个线程可以共享访问和操作相同的变量,但一个线程对一个共享变量的修改,另一个线程不一定马上就能看到,甚至永远也看不到。原因:在计算机系统中,除了内存,数据还会被缓存在CPU的寄存器以及各级缓存中,当访问一个变量时,可能直接从寄存器或CPU缓存中获取,而不一定到内存中去取,当修改一个变量时,也可能是先写到缓存中,而稍后才会同步更新到内存中。
多线程的成本
创建开销(空间和时间):每个线程创建必要的数据结构、栈、程序计数器。
线程切换开销(时间):上下文切换,这个切换不仅耗时,而且使CPU中的很多缓存失效,是有成本的。
如果执行的任务都是CPU密集型的,那创建超过CPU数量的线程是没有必要的,并不会加快程序的执行。
IO密集型的,创建超过 CPU 核数的线程是会提高效率的(线程数到多少合适呢?Netty的源码里有相关的解说)