Java线程基础

线程的概念

在Java中,线程是程序执行的最小单元。它允许我们将复杂的任务划分成多个子任务并发执行,以提高应用程序的响应速度和性能。每一个线程都有自己独立的运行栈和程序计数器,线程在操作系统中可以看作是轻量级的进程。

线程与进程的区别

线程和进程是计算机程序并行执行的基本单元。进程有自己独立的地址空间,而所有线程共享进程的地址空间和资源。线程之间的通信和数据交换成本较低,这意味着多线程程序比多进程程序更高效。

Java多线程编程

创建线程的优势

使用多线程,可以在同一程序中并行执行多个操作,尤其是在进行I/O操作或进行复杂计算时。多线程可以有效地利用CPU资源,尤其是在多核CPU上。

实际场景中的多线程应用示例

设想一个服务器应用程序,需要同时处理成百上千的客户端请求。如果串行处理这些请求,那么当服务器处理前面的请求时,后面的请求必须等待,这会导致严重的响应延迟。通过使用多线程,每个请求可以在单独的线程中几乎同时进行处理,显著提升服务的吞吐量和响应时间。

线程的实现方式

继承Thread类

在Java中,创建线程的最直接方式就是继承Thread类,然后重写其run方法。当调用线程的start方法时,会新开启一个线程并执行run方法中的代码。

代码示例

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的操作
        System.out.println("Thread running via MyThread class.");
    }
    
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 开启线程
    }
}

实现Runnable接口

实现Runnable接口是另一种创建线程的方式。这种方式更灵活,因为它允许类继承其他类的同时,还能实现多线程的功能。

代码示例

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的操作
        System.out.println("Thread running via MyRunnable implementation.");
    }
    
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start(); // 开启线程
    }
}

实现Callable接口

如果您希望线程执行完毕后能返回值,您可以实现Callable接口。Callable接口的call方法允许返回值,并且可以抛出异常。

代码示例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() {
        // 线程执行的操作
        System.out.println("Thread running via MyCallable implementation.");
        return 123; // 返回值
    }
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
        Thread thread = new Thread(futureTask);
        thread.start(); // 开启线程
        
        // 获取Callable线程的返回值
        Integer result = futureTask.get();
        System.out.println("Result from MyCallable: " + result);
    }
}

线程的生命周期

线程状态详解

Java线程有五种基本状态:新建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、计时等待(TIMED_WAITING)和终止(TERMINATED)。

线程状态转换图(PlantUML图)

下面我们使用PlantUML来描述这些状态以及它们之间的转换: file

生命周期相关的方法

Java提供了一系列方法来控制线程的生命周期,比如start(), join(), wait(), notify(), sleep(), 等等。

代码示例

public class ThreadLifeCycleExample {
    private static final Object LOCK = new Object();
    
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized(LOCK) {
                try {
                    System.out.println("Thread1 is WAITING.");
                    LOCK.wait(); // 线程进入WAITING状态
                } catch(InterruptedException e) {
                    Thread.currentThread().interrupt(); // 处理中断
                }
                System.out.println("Thread1 is RUNNABLE again.");
            }
        });
        
        Thread thread2 = new Thread(() -> {
            synchronized(LOCK) {
                System.out.println("Thread2 is RUNNABLE and will notify Thread1.");
                LOCK.notifyAll(); // 通知正在LOCK上等待的线程
            }
        });

        thread1.start();
        Thread.sleep(100); // 确保thread1先运行
        thread2.start();
    }
}

Java线程池

线程池的概念及优势

线程池(Thread Pool)是一种基于池化技术的线程使用方式,它可以管理和复用多个线程,减少线程创建和销毁所带来的开销。线程池不仅减少了资源消耗,还可以提高响应速度、提高线程的可管理性。

常见线程池类型

Java中的java.util.concurrent包提供了多种线程池,包括FixedThreadPool(固定大小线程池)、CachedThreadPool(可缓存线程池)、SingleThreadExecutor(单线程化的线程池)、以及ScheduledThreadPool(定时任务线程池)。

线程池参数详解

线程池创建时可以指定多种参数,比如核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、存活时间(keepAliveTime)、工作队列(workQueue)等等,这些参数共同决定了线程池的行为。

代码示例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        
        for(int i = 0; i < 10; i++) {
            int taskNumber = i;
            executorService.submit(() -> {
                // 线程池中的线程执行的任务
                System.out.println("Executing task " + taskNumber +
                                   " on thread " + Thread.currentThread().getName());
            });
        }

        executorService.shutdown(); // 关闭线程池

        try {
            // 等待线程池中的所有任务完成
            if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
                executorService.shutdownNow();
            } 
        } catch (InterruptedException e) {
            executorService.shutdownNow();
        }
    }
}

Java线程池进阶

自定义线程池

对于特定的应用需求,内置的线程池可能不够用。这时,你可以通过ThreadPoolExecutor来创建自定义线程池,它提供了更加灵活的配置选项。

代码示例

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CustomThreadPoolExample {
    public static void main(String[] args) {
        // 自定义线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, // 核心线程数
                4, // 最大线程数
                100, // 线程没有任务执行时最多保持多久时间会终止
                TimeUnit.MILLISECONDS, // 时间单位
                new ArrayBlockingQueue<>(2), // 任务队列
                new ThreadPoolExecutor.DiscardOldestPolicy() // 饱和策略
        );
        
        for (int i = 0; i < 5; i++) {
            int taskNumber = i;
            executor.submit(() -> {
                System.out.println("Executing task " + taskNumber);
            });
        }
        
        executor.shutdown();
    }
}

Fork/Join框架解读

Fork/Join框架是Java 7引入的一种用于并行执行任务的机制,它是建立在工作窃取算法之上,特别适合处理可以递归划分的任务。

代码示例

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class ForkJoinExample extends RecursiveTask<Integer> {
    private final int[] numbers;
    private final int start;
    private final int end;
    
    public ForkJoinExample(int[] numbers, int start, int end) {
        this.numbers = numbers;
        this.start = start;
        this.end = end;
    }
    
    @Override
    protected Integer compute() {
        int length = end - start;
        if (length <= 2) {
            return computeDirectly();
        } else {
            int split = length / 2;
            ForkJoinExample left = new ForkJoinExample(numbers, start, start + split);
            ForkJoinExample right = new ForkJoinExample(numbers, start + split, end);
            invokeAll(left, right);
            return left.join() + right.join();
        }
    }
    
    private Integer computeDirectly() {
        int sum = 0;
        for (int i = start; i < end; i++) {
            sum += numbers[i];
        }
        return sum;
    }
    
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        ForkJoinExample task = new ForkJoinExample(numbers, 0, numbers.length);
        int result = pool.invoke(task);
        System.out.println("Result: " + result);
    }
}