1.线程
1.1 Java线程和os的现场关系
Java 线程和操作系统线程的关系:现在的 Java 线程的本质其实就是操作系统的线程。一个 Java 程序的运行是 main 线程和多个其他线程同时运行
[5] Attach Listener //添加事件
[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
[3] Finalizer //调用对象 finalize 方法的线程
[2] Reference Handler //清除 reference 线程
[1] main //main 线程,程序入口
1.2 线程和进程
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
用户线程和内核线程分为三种模式:
- 一对一(一个用户线程对应一个内核线程)
- 多对一(多个用户线程映射到一个内核线程)
- 多对多(多个用户线程映射到多个内核线程)
其中在 Windows 和 Linux 等主流操作系统中,Java 线程采用的是一对一的线程模型,也就是一个 Java 线程对应一个系统内核线程
1.3进程线程关系和优缺点
线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
- 程序计数器独立是因为每个线程执行的位置不同,切换时候要获取当前的执行位置
- 虚拟机栈: 每个 Java 方法在执行之前会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
- 本地方法栈: 和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
- 为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的
- 堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
1.4 如何创建线程
继承Thread类,实现Runnable接口,实现callable接口,使用线程池,使用CompletableFuture类等
不管哪种方式,最终还是依赖于new Thread().start()创建线程
- 线程和线程体:线程是个容器,线程体才是可运行的任务,类似runnable接口等是在创建线程体即多线程任务,这个任务需要绑定到Thread上,调用start执行
public class ExtendsThread extends Thread {
@Override
public void run() {
System.out.println("1......");
}
public static void main(String[] args) {
new ExtendsThread().start();
}
}
public class ImplementsRunnable implements Runnable {
@Override
public void run() {
System.out.println("2......");
}
public static void main(String[] args) {
ImplementsRunnable runnable = new ImplementsRunnable();
new Thread(runnable).start();
}
}
public class ImplementsCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("3......");
return "zhuZi";
}
public static void main(String[] args) throws Exception {
ImplementsCallable callable = new ImplementsCallable();
FutureTask<String> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
ThreadPoolExecutor poolB = new ThreadPoolExecutor(2, 3, 0,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
poolB.submit(()->{
System.out.println("4B......");
});
poolB.shutdown();
public static void main(String[] args) throws InterruptedException {
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println("5......");
return "zhuZi";
});
// 需要阻塞,否则看不到结果
Thread.sleep(1000);
}
1.5 线程的生命周期和状态
New -> Runnable -> (Blocked/Waiting/Timed Waiting) -> Terminated
new新建状态:即new Thread()
Runnable可运行状态: thread.start(); // 线程现在处于RUNNABLE状态,但可能未立即运行,因为
时间片很短切换太快所以区分**ready和running没什么意义**
Blocked(阻塞状态):比如进入同步代码块synchronized时候,如果获取不到锁,就进入该状态
Waiting(等待状态):Object.wait()或Thread.join()并等待另一个线程的执行结果时进入waiting状态
,直到有其他线程调用notify()或notifyAll()来唤醒它。
Timed Waiting(超时等待状态):Thread.sleep()或Object.wait(long timeout)方法时,它进入
TIMED_WAITING状态,直到超时时间结束或被唤醒。
Terminated(终止状态):线程执行完成或者抛出未捕获的异常后会进入TERMINATED状态,表示线程的生命周期结束
/
1.6什么是线程上下文切换
线程有自己的运行信息(上下文),比如私有的程序计数器和栈,所以当线程退出占用cpu时候需要保存上下文,以便下次占用cpu执行时恢复现场
常见的线程退出cpu情境如下:
- 主动让出cpu,比如sleep和wait
- 时间片用完
- 调用阻塞类型的系统中断,比如请求IO
- 被终止或结束运行
1.7 Thread.sleep()和Object.wait()对比
Thread.java
public static native void sleep(long millis) throws InterruptedException;
Object.java
public final native void wait(long timeout) throws InterruptedException;
Thread.sleep() | Object.wait() | |
---|---|---|
作用 | 暂停当前线程指定时间 | 进入wait状态,直到其他线程调用notify() 或notifyAll() 唤醒它 |
场景 | 模拟延时,不适合锁和线程间通信的场景 | 生产者-消费者模式,或其他线程同步场景 |
锁机制 | 不释放锁 | 释放锁 |
恢复条件 | 超时 | notify() 或notifyAll(),使用带时间参数的方法时超时恢复 |
使用位置 | 不要求 | 比如要同步块或方法中使用,因为会释放锁,所以线程必须先持有锁,否则报IllegalMonitorStateException |
public class WaitNotifyExample {
private static final Object lock = new Object();
public static void main(String[] args) {
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread is waiting");
lock.wait(); // 线程等待
System.out.println("Thread resumed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread notifyingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread is notifying");
lock.notify(); // 唤醒等待线程
}
});
waitingThread.start();
try {
Thread.sleep(2000); // 主线程休眠2秒以确保waitingThread先运行
} catch (InterruptedException e) {
e.printStackTrace();
}
notifyingThread.start();
}
}
Thread is waiting
(wait后释放对象锁,所以notifyingThread 可以占用锁lock进入同步代码块执行notify)
Thread is notifying
Thread resumed
1.8 为什么wait不放在Thread方法里
对象头里有锁信息(markword里有锁标志) ,因为锁是和对象关联的,wait要释放锁必然要操作对象头的锁信息操作,放在Object.java可以知道释放的是哪个对象的锁
而sleep只是让线程等待,不涉及对象和对象锁,所以放在Thread.java
1.9 可以直接调用Thread类的run方法吗?
new Thread后调用start进入就绪状态,线程被分配到时间片后就自动执行run()。
如果直接执行run方法相当于main线程下执行普通方法不是多线程执行
代码如下,直接调用run相当于在main线程执行
public class RunTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
// t1.run(); // 直接调用 run()
t1.start();
Thread.sleep(1000);
System.out.println("Main thread: " + Thread.currentThread().getName());
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Running in: " + Thread.currentThread().getName());
}
}
Running in: Thread-0
Main thread: main
public class RunTest {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.run(); // 直接调用 run()
// t1.start();
Thread.sleep(1000);
System.out.println("Main thread: " + Thread.currentThread().getName());
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Running in: " + Thread.currentThread().getName());
}
}
Running in: main
Main thread: main