本文看下多线程基础相关内容。
戈登.摩尔,英特尔公司的创始人之一,其发现了一个计算机的发展规律,即,处理器的性能每24个月就会翻一倍,这就是摩尔定律,开始这个预测是正确的,但是后来处理器性能的提升速度严重放缓,这就是摩尔定律失效,既然单个CPU已经无法满足业务需求,那么就多个CPU,也就是多核,有了多核,自然就有了多线程。有了多核,多线程之后,就可以进行并行计算,提高更高的计算能力。
目前多核CPU有两种架构,共享内存架构,即多个CPU共享同一块内存,如下图:
多个CPU通过消息总线BUS,访问同一块内存,这种架构方式存在如下问题:
1:多个CPU通过同一个BUS存取数据,导致BUS拥堵
2:多个CPU访问同一块内存获取数据,导致内存资源争抢严重
这个问题可以通过以下这种架构来解决,即分片,多个内存,分配给一个或多个CPU使用,如果某CPU需要访问其他CPU的内存的话则可以通过router路由完成,这种架构叫做非一致性内存访问架构,如下图:
线程最终的创建还是需要依赖于底层的操作系统,所以Java中的线程,有三个层面的概念,Java代码层面,jvm层面,操作系统层面,参考下图:
解释如下:
1:创建完Thread对象后调用其start方法
2,3:JVM创建JVM层面的JavaThread对象,然后通过操作系统创建操作系统层次的OSThread对象
4:在JVM的虚拟机栈上给该线程分配内存
5:给该线程分配TLAB
6:操作系统调度执行线程,自动执行thread的run方法逻辑
7:run方法执行完毕,操作系统结束线程
先看一个问题,jvm什么时候会退出,看老外写的原版表述The Java Virtual Machine exits when the only threads running are all daemon threads.
,即当jvm中运行的线程都是守护线程时,jvm会退出,如下:
public class NoneDaemonThreadTest {
public static void main(String[] args) {
Thread noneDaemonThread = new Thread(() -> {
while (true) {
try {
// Thread.sleep(1);
TimeUnit.SECONDS.sleep(1);
System.out.println("hi,i am still running...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
noneDaemonThread.start();
try {
// Thread.sleep(5);
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main thread exits...");
}
}
运行:
如何实现在main函数执行完毕后让jvm退出呢?只需要设置setDaemon(true),就行了,如下:
运行,如下:
当然我们也可以添加一个jvm的钩子来确定jvm确实是退出了,如下:
那么守护进程有什么用呢?如果我们希望执行一些清理,信息收集等类型的工作时,但又不希望因为其存在而影响jvm的正常退出,因为当没有其他的非守护进程存在时,其存在也就没有意义了,最典型的,比如GC线程,就是守护线程,其是为了清理因为非守护线程运行而生成的对象而存在的。如果是工作中有类似的需求,我们也要尽量使用非守护进程,不要使用守护进程,避免因为其影响JVM的正常退出。
我们创建线程的时候可以给java.lang.Thread一个java.lang.Runnable类一个参数,像这样:
new Thread(new Runnable() {
@Override
public void run() {
}
})
也可以直接继承java.lang.Thread,像这样:
new Thread() {
@Override
public void run() {
super.run();
}
}
可以这么写的原因是java.lang.Thread类是java.lang.Runnable接口的实现类,如下:
public
class Thread implements Runnable {}
JVM在执行run方法时,会优先执行thread类本身的run方法,如果thread本身没有实现run方法,会执行构造函数中Runnable接口子类的run方法。
线程名称,平时么有用,但是当排查导致问题的线程时会比较有用。
是否为守护线程,true是,false不是。守护线程本文前面内容已经分析过了。
只能通过Thread构造函数传入。
启动线程,之后进入可运行状态,等待操作系统调度执行。
先看个图:
1:如果线程执行时,被锁阻塞,则进入同步队列
2:如果线程正常获取锁,在同步代码块内部调用对象的wait方法,则该线程进入对象的等待队列
3:如果线程正常获取锁,在同步代码内部调用对象的notify/notifyAll方法,则等待队列线程进入到同步队列,待锁释放后争抢锁
4:如果线程正常获取锁,并且同步代码块正常执行完毕,则同步队列中的线程争抢锁,最终哪个线程获取到锁,不确定
调用某对象wait方法,必须持有该对象的对象锁,调用后当前线程进入waiting状态,并且进入对象的等待队列,等待notify/notifyAll方法唤醒,执行后释放对象锁,进入对象的等待队列,等待notify/notifyAll方法唤醒。另外调用wait时如果线程没有持有该对象的对象锁则会抛出IllegalMonitorException,notify/notifyAll方法如果是没有持有当前对象的对象锁,同样会抛出java.lang.IllegalMonitorException,这点使用的时候要特别小心。
锁对象和线程可参考下图:
wait方法会让出CPU,并释放锁,不同于sleep,sleep只会让出CPU时间片,但并不会释放锁,所以说其他线程依然无法进入同步代码块
底层执行的wait方法,如下:
public final synchronized void join(long millis)
throws InterruptedException {
...
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
...
wait(delay);
}
}
}
假定线程A在线程B中执行join方法,即A.join,则线程B会进入到线程A的等待队列,在线程A执行完毕后,底层会自动调用线程A的notifyAll方法,即A.notifyAll,则线程B从线程A的等待队列进入到同步队列,进而获取锁继续执行。参考如下代码。
package dongshi.daddy;
import java.util.concurrent.TimeUnit;
public class Huohuo {
public static void main(String[] args) throws Exception {
Thread task = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable content...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
task.start();
task.join();
System.out.println("main run end...");
}
}
运行,主线程等待task线程执行完毕才继续执行,如下:
runnable content...
main run end...
执行过程分析:
获取当前上线文正在执行的线程。
本部分看下线程可能的状态,以及会改变线程状态的操作。
如:
void fn() {
...
Thread.sleep(time);
...
}
让出CPU,线程从RUNNABLE装填变为TIME_WATING状态,作用是给其他线程使用CPU的机会,当睡眠到时则自动进入RUNNABLE状态,等待操作系统调度。需要注意该操作不会释放对象锁。
如:
void fn() {
...
Thread.yield();
...
}
Thread类的静态本地方法,会让出CPU时间片,从RUNNING状态重新进入就绪状态,即会立即开始争抢CPU时间片。
如:
void fn() {
...
Thread t = new Thread();
t.join();
...
}
在当前线程调用某些线程t的join方法,则当前线程会等待线程t执行完毕之后,由底层调用notify方法唤醒,重新进入到可运行状态。不会释放当前线程的对象锁,但是会释放线程t的对象锁,因为底层调用的其实是t.wait();
java.lang.Object类的本地方法,当还行obj.wait后,线程会释放obj的对象锁,并进入obj的等待队列。wait到时或者是有notify/notifyAll方法唤醒,重新变为就绪状态,开始争抢CPU时间片。
唤醒在对象的等待队列中处于WAITING,TIMED_WAITING状态的线程,notify唤醒随机的一个,notifyAll唤醒所有的线程,进入就绪状态。
如果我们想要中断一个线程的执行,该怎么做?详细你首先会想到Thread的实例方法interrupt,但该方法真的可以吗?如下代码:
public class Huohuo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
System.out.println();
}
});
t1.start();
t1.interrupt();
}
}
运行后你会发现,jvm进程依然在运行,也就是t1.interrupt();
程序的执行对于t1线程毫无作用,其实该方法只有当执行了Object.wait,Thread.sleep(),Thread.join而进入到WATIING,TIMED_WAITING状态后,才会有效果,并且在线程的run方法内会抛出java.lang.InterruptedException,这是一个非运行时异常,需要try住,修改如下:
public class Huohuo {
static Object lock1 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
System.out.println();
try {
lock1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
System.out.println("中断线程t1");
t1.interrupt();
}
}
运行,如下:
中断线程t1
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at dongshi.daddy.Huohuo.lambda$main$0(Huohuo.java:10)
at java.lang.Thread.run(Thread.java:748)
也就是说想要中断线程,必须先让想要中断的线程处于WAITING,TIMED_WAITING状态。具体可参考下图:
面试官: 谈谈什么是守护线程以及作用 ? 。
Java等待唤醒机制wait/notify深入解析 。