Java线程详细解读

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线程调度依赖于底层操作系统的调度器,通常采用时间片轮转和优先级调度策略。

操作系统通常采用两种主要的线程调度策略:

  1. 抢占式调度:高优先级的线程会抢占低优先级线程的CPU时间片。
  2. 时间片轮转:每个线程分配一定的时间片,轮流执行。

Java线程的调度通常是抢占式的,这意味着高优先级的线程会优先获得CPU资源。
在多核处理器系统中,调度器还需要考虑线程的亲和性(affinity),即尽量让线程在同一个CPU核上运行,以减少上下文切换的开销。

优先级的保证

不难看出,虽然可以设置线程优先级,但这并不保证高优先级的线程一定会先于低优先级的线程执行。这是因为:

  1. 操作系统的调度策略:不同操作系统对线程优先级的处理方式不同,有些操作系统可能会忽略Java设置的优先级。
  2. 系统负载:在高负载情况下,操作系统可能会调整调度策略以保证系统的整体性能和响应速度。

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接口这两种方式。
  1. 继承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的实例并启动了线程。

  2. 实现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)来控制协程的执行环境。

使用虚拟线程

虚拟线程的使用非常简单,以下是几种常见的创建和使用方式:

  1. 直接创建并运行虚拟线程

    Thread vt = Thread.startVirtualThread(() -> {
        System.out.println("Start virtual thread...");
        Thread.sleep(10);
        System.out.println("End virtual thread.");
    });
    
  2. 创建虚拟线程但不自动运行

    Thread vt = Thread.ofVirtual().unstarted(() -> {
        System.out.println("Start virtual thread...");
        Thread.sleep(1000);
        System.out.println("End virtual thread.");
    });
    vt.start();
    
  3. 通过虚拟线程的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();
    
  4. 使用虚拟线程执行器

    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();
        }
    }
}

通过合理使用synchronizedReentrantLockwait()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提供的线程管理和同步工具,可以有效提升应用程序的性能和可靠性。

  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值