Java线程的一些概念
Java线程与操作系统线程的关系一般是1:1的实现方式。这意味着每一个Java线程都会映射到一个操作系统的原生线程。
线程的优先级
线程优先级是线程调度的重要因素之一。Java中的线程优先级范围从1到10,默认优先级为5。优先级高的线程相对于优先级低的线程更有可能获得CPU时间片。然而,线程优先级只是一个建议,具体实现依赖于底层操作系统的线程调度机制。
线程优先级的设置
在Java中,每个线程都有一个优先级,范围从1(最低优先级)到10(最高优先级),默认优先级为5。可以通过Thread
类的setPriority(int newPriority)
方法来设置线程的优先级。
Thread thread = new Thread();
thread.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级
线程优先级的操作系统底层实现
Java线程的优先级并不是由Java虚拟机(JVM)独立维护的,而是依赖于操作系统的线程调度机制。具体来说,Java线程的优先级设置会传递给操作系统,由操作系统的线程调度器来决定线程的执行顺序。
线程调度机制
线程调度是操作系统决定哪个线程在何时运行的过程。Java线程调度依赖于底层操作系统的调度器,通常采用时间片轮转和优先级调度策略。
操作系统通常采用两种主要的线程调度策略:
- 抢占式调度:高优先级的线程会抢占低优先级线程的CPU时间片。
- 时间片轮转:每个线程分配一定的时间片,轮流执行。
Java线程的调度通常是抢占式的,这意味着高优先级的线程会优先获得CPU资源。
在多核处理器系统中,调度器还需要考虑线程的亲和性(affinity),即尽量让线程在同一个CPU核上运行,以减少上下文切换的开销。
优先级的保证
不难看出,虽然可以设置线程优先级,但这并不保证高优先级的线程一定会先于低优先级的线程执行。这是因为:
- 操作系统的调度策略:不同操作系统对线程优先级的处理方式不同,有些操作系统可能会忽略Java设置的优先级。
- 系统负载:在高负载情况下,操作系统可能会调整调度策略以保证系统的整体性能和响应速度。
Java线程的优先级是通过与操作系统的线程调度机制协同工作来实现的。Java本身并不独立维护线程优先级,而是依赖操作系统的调度器来管理线程的执行顺序。虽然设置了优先级,但实际执行顺序还受到操作系统调度策略和系统负载的影响。
线程和协程
线程基础
线程
线程是操作系统能够进行调度的最小单位。一个进程可以包含多个线程,这些线程共享进程的资源。每个线程拥有独立的栈和程序计数器,操作系统为每个线程分配CPU时间片,通过抢占式调度进行线程切换。线程的上下文切换涉及保存和恢复CPU寄存器、程序计数器、栈指针等,开销较大。
协程(Coroutines)是一种更广泛的并发编程概念,存在于多种编程语言中。协程是一种比线程更轻量级的并发处理单元。协程可以在执行过程中暂停并在稍后恢复,从而实现高效的并发处理。协程允许函数在执行过程中暂停和恢复,从而实现异步编程。
虚拟线程(Virtual Threads)是JDK 19引入的一种轻量级线程,于JDK 21正式发布。它们由JVM管理和调度,而不是操作系统的物理线程。虚拟线程的设计目标是减少线程上下文切换的开销,解决传统线程在高并发场景下的性能瓶颈,使得Java程序能够轻松创建和管理大量线程,从而提高并发性能。
虚拟线程不再直接映射到操作系统的内核线程,虚拟线程依赖于纤程(Fiber)或绿色线程(Green Thread)的概念,由JVM在用户态实现调度。通过使用虚拟线程,Java应用可以在不增加系统资源开销的情况下处理大量并发任务。
虚拟线程的特点
- 轻量级:虚拟线程由JVM管理,而不是操作系统。这意味着它们的创建、销毁和调度开销极低,可以轻松创建数以万计的虚拟线程。
- 高并发:虚拟线程适合处理大量并发请求,特别是在I/O密集型任务中表现出色。它们可以显著提高以“一个请求一个线程”模型编写的Web应用程序的吞吐量。
- 易用性:虚拟线程的接口与传统线程相同,开发者无需学习新的概念即可使用。
虚拟线程的优势
- 资源效率:虚拟线程的创建和销毁成本极低,不会像传统线程那样耗尽系统资源。
- 简化代码:使用虚拟线程可以避免复杂的异步编程模型,代码更易读、更易维护。
- 高性能:在处理I/O密集型任务时,虚拟线程可以显著减少线程阻塞时间,提高系统整体性能。
虽然Java的虚拟线程在某些方面类似于协程,但它们在实现和使用上有一些不同。
使用举例
使用Java中的线程
- Java通过
java.lang.Thread
类和java.util.concurrent
包提供了对线程的支持。
创建Java线程的两种主要方式是继承Thread
类和实现Runnable
接口这两种方式。
-
继承
Thread
类
通过继承Thread
类来创建线程时,需要重写run
方法。然后可以创建该类的实例并调用start
方法来启动线程。class MyThread extends Thread { public void run() { System.out.println("Thread is running"); } } public class ThreadDemo { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }
在这个例子中,
MyThread
类继承了Thread
类,并重写了run
方法。main
方法中创建了MyThread
的实例并启动了线程。 -
实现
Runnable
接口
通过实现Runnable
接口来创建线程时,需要实现run
方法。然后可以将该实现类的实例传递给Thread
类的构造函数,并调用start
方法来启动线程。class MyRunnable implements Runnable { public void run() { System.out.println("Thread is running"); } } public class RunnableDemo { public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); } }
在这个例子中,
MyRunnable
类实现了Runnable
接口,并实现了run
方法。main
方法中创建了MyRunnable
的实例,并将其传递给Thread
类的构造函数,然后启动了线程。
使用Kotlin协程
Kotlin协程是基于挂起函数(suspend function)实现的。挂起函数可以在不阻塞线程的情况下挂起执行。
-
Kotlin协程使用示例
import kotlinx.coroutines.* fun main() = runBlocking { launch { delay(1000L) println("World!") } println("Hello,") }
-
协程上下文与调度器
Kotlin协程提供了多种调度器(如Dispatchers.IO
,Dispatchers.Default
)来控制协程的执行环境。
使用虚拟线程
虚拟线程的使用非常简单,以下是几种常见的创建和使用方式:
-
直接创建并运行虚拟线程:
Thread vt = Thread.startVirtualThread(() -> { System.out.println("Start virtual thread..."); Thread.sleep(10); System.out.println("End virtual thread."); });
-
创建虚拟线程但不自动运行:
Thread vt = Thread.ofVirtual().unstarted(() -> { System.out.println("Start virtual thread..."); Thread.sleep(1000); System.out.println("End virtual thread."); }); vt.start();
-
通过虚拟线程的ThreadFactory创建虚拟线程:
ThreadFactory tf = Thread.ofVirtual().factory(); Thread vt = tf.newThread(() -> { System.out.println("Start virtual thread..."); Thread.sleep(1000); System.out.println("End virtual thread."); }); vt.start();
-
使用虚拟线程执行器:
package cn.lee; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class VirtualThreadDemo { public static void main(String[] args) throws InterruptedException { try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < 10000; i++) { final int taskId = i; executor.submit(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("Virtual thread! Task ID: " + taskId); }); } executor.shutdown(); executor.awaitTermination(10, TimeUnit.MINUTES); // 等待所有任务完成 System.out.println("任务执行完毕..."); } } }
线程同步与通信
在Java中,线程的同步与通信是并发编程的核心概念。理解这些概念不仅有助于编写高效的多线程应用程序,还能避免常见的并发问题,如死锁和数据不一致。
线程同步
线程同步是指在多线程环境中,为了避免多个线程同时访问共享资源导致的数据不一致问题,Java提供了同步机制。常用的同步方法包括synchronized
关键字和Lock
接口。
-
synchronized
关键字synchronized
关键字是Java中最常用的同步机制。它可以用来修饰方法或代码块,确保同一时间只有一个线程可以执行被修饰的代码。public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } }
-
Lock
ReentrantLock
是Lock的一个实现,Lock提供的另一种同步机制,功能比synchronized
更强大。它提供了更灵活的锁定操作和条件变量。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Counter { private int count = 0; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }
线程通信
线程通信是指多个线程之间通过某种机制进行信息交换。Java提供了多种方式来实现线程通信。
wait()
、notify()
和notifyAll()
上述几个方法是Object类的一部分,用于线程间的通信。wait()
方法使线程进入等待状态,notify()
和notifyAll()
方法则唤醒等待的线程。
public class SharedResource {
private int value;
private boolean available = false;
public synchronized void produce(int value) throws InterruptedException {
while (available) {
wait();
}
this.value = value;
available = true;
notifyAll();
}
public synchronized int consume() throws InterruptedException {
while (!available) {
wait();
}
available = false;
notifyAll();
return value;
}
}
Condition
Condition
是与ReentrantLock
配合使用的线程通信机制,提供了类似wait()
、notify()
和notifyAll()
的方法,但功能更强大。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SharedResource {
private int value;
private boolean available = false;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (available) {
condition.await();
}
this.value = value;
available = true;
condition.signalAll();
} finally {
lock.unlock();
}
}
public int consume() throws InterruptedException {
lock.lock();
try {
while (!available) {
condition.await();
}
available = false;
condition.signalAll();
return value;
} finally {
lock.unlock();
}
}
}
通过合理使用synchronized
、ReentrantLock
、wait()
、notify()
以及Condition
,可以有效地管理多线程环境中的共享资源和线程间的通信。
线程和操作系统底层的实现原理
Java线程的实现依赖于操作系统的线程机制。在现代操作系统中,线程通常由内核支持,称为内核线程(Kernel-Level Thread, KLT)。Java线程与内核线程的映射关系主要有1:1 模型
和N:M 模型
。
线程模型
1:1 线程模型
- 创建与销毁:当你在Java中创建一个线程时,JVM会通过本地方法接口(JNI)调用操作系统的API来创建一个对应的操作系统线程。
- 调度:线程的调度由操作系统负责,操作系统会根据线程的优先级和状态来分配CPU时间片。
- 性能:这种模型的优点是能够充分利用多核处理器的优势,每个线程都可以独立地在不同的CPU上运行。
其他线程模型
- 多对一模型:多个用户线程映射到一个操作系统线程上。这种模型的缺点是如果一个线程阻塞,整个进程都会阻塞。
- 多对多模型:多个用户线程映射到多个操作系统线程上,结合了多对一和一对一模型的优点。这种模型允许更灵活的线程管理,但实现较为复杂。
Java线程与操作系统线程的映射
Java使用1:1线程模型,即每个Java线程直接映射到一个内核线程。这意味着当一个Java线程被创建时,JVM会调用操作系统的API创建一个相应的内核线程。以下是Java线程的创建和映射过程:
线程创建
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
thread.start()
方法会调用操作系统的API来创建一个内核线程,并将其与Java线程进行绑定。
结论
Java线程的实现和调度机制是多线程编程的核心。理解线程优先级、调度策略以及内核线程和用户线程的实现原理,有助于开发高效并发程序。通过合理利用Java提供的线程管理和同步工具,可以有效提升应用程序的性能和可靠性。