java多线程编程知识点汇总

目录

一、多线程实现方式

1. 继承Thread类

2. 实现Runnable接口

3. 线程同步示例

 二、阿里线程池规范

1.线程池的作用

2.不推荐使用Executors直接创建线程池

3.推荐使用ThreadPoolExecutor创建线程池

4.线程池的参数配置建议

5.线程池的生命周期管理

6.创建线程池示例

7.线程池总结

三、获取线程执行结果

1. 使用Callable和Future

2. 使用CompletableFuture

3. 继承Thread类并覆写run方法,通过共享变量获取结果

4. 使用ForkJoinPool

5.获取线程结果总结


一、多线程实现方式

Java多线程编程是Java并发编程的核心部分,它允许程序同时执行多个任务。在Java中,实现多线程主要有两种方式:继承Thread类和实现Runnable接口。下面,我将分别给出这两种方式的示例。

1. 继承Thread

通过继承Thread类来创建线程是最基本的一种方式。你需要创建一个扩展自Thread类的子类,并重写其run()方法。然后,你可以创建该子类的实例来创建新的线程。

// MyThread.java
class MyThread extends Thread {
    public void run() {
        System.out.println("线程运行中: " + Thread.currentThread().getName());
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.start(); // 启动线程
        t2.start(); // 启动另一个线程
    }

2. 实现Runnable接口

另一种创建线程的方式是实现Runnable接口。你需要创建一个实现了Runnable接口的类的实例,这个实例将被用作Thread对象的构造器参数。然后,通过调用Thread对象的start()方法来启动线程。

// MyRunnable.java
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("线程运行中: " + Thread.currentThread().getName());
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        Thread t2 = new Thread(new MyRunnable());

        t1.start(); // 启动线程
        t2.start(); // 启动另一个线程
    }

3. 线程同步示例

在多线程编程中,线程同步是一个重要的概念,它用于控制多个线程对共享资源的访问。下面是一个简单的使用synchronized关键字进行线程同步的示例。

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

public class CounterThread extends Thread {
    private final Counter counter;

    public CounterThread(Counter counter) {
        this.counter = counter;
    }

    public void run() {
        for (int i = 0; i < 10000; i++) {
            counter.increment();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new CounterThread(counter);
        Thread t2 = new CounterThread(counter);

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("Final count: " + counter.getCount());
    }

在这个例子中,Counter类中的increment()getCount()方法都被synchronized关键字修饰,这确保了当一个线程正在执行这些方法时,其他线程不能同时执行这些方法,从而保证了count变量的线程安全。

 二、阿里线程池规范

阿里线程池规范主要涉及线程池的创建、配置、使用以及避免的一些常见问题。

1.线程池的作用

线程池能够充分合理地协调利用CPU、内存、I/O等系统资源,通过复用线程、控制最大并发数、实现定时和周期性任务执行、任务队列缓存策略和拒绝机制等功能,提高系统性能和稳定性。

2.不推荐使用Executors直接创建线程池

阿里巴巴开发手册中明确规定,不建议使用Executors类直接创建线程池,原因如下:

  • FixedThreadPool和SingleThreadPool:这两个线程池允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM(内存溢出)。

  • CachedThreadPool:允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量的线程,同样会导致OOM

3.推荐使用ThreadPoolExecutor创建线程池

通过ThreadPoolExecutor的构造方法,可以明确地设置线程池的参数,从而避免上述风险。ThreadPoolExecutor的构造方法包含七个主要参数:

  • corePoolSize(核心线程数):线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut

  • maximumPoolSize(最大线程数):线程池允许的最大线程数,必须大于等于1,且大于等于corePoolSize

  • keepAliveTime(空闲线程存活时间):当线程数大于corePoolSize时,这是多余空闲线程在终止前等待新任务的最长时间。

  • unit(时间单位):keepAliveTime的时间单位。

  • workQueue(任务队列):用于存放待执行的任务的阻塞队列。

    可选值描述
    ArrayBlockingQueue基于数组的阻塞队列,需要指定队列的大小。当任务队列满时,会尝试创建新线程,如果线程数达到maximumPoolSize,则执行拒绝策略。
    LinkedBlockingQueue基于链表的阻塞队列,如果不指定大小,默认大小为Integer.MAX_VALUE。这意味着如果线程数达到corePoolSize,新任务将一直在队列中等待,除非线程池被关闭或线程池中的线程数达到maximumPoolSize且队列也满。
    SynchronousQueue一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的对应移除操作,否则插入操作将一直阻塞。这种队列适合传递性任务,即生产者线程生成一个任务后,必须立即由消费者线程处理,不存在中间状态的任务。
    PriorityBlockingQueue一个支持优先级排序的阻塞队列,可以根据任务的优先级来执行任务。
  • threadFactory(线程工厂):用于创建新线程。

    可选值描述
    Executors.defaultThreadFactory()默认的线程工厂,创建的线程属于同一个ThreadGroup,且线程名遵循一定的命名规则。
    自定义ThreadFactory通过实现ThreadFactory接口,可以自定义线程的创建过程,包括设置线程的优先级、守护线程状态、线程名等。
  • handler(拒绝策略):当任务队列已满,且线程数达到maximumPoolSize时,对新任务执行的拒绝策略。

    可选值描述
    ThreadPoolExecutor.AbortPolicy()默认策略,直接抛出RejectedExecutionException异常。
    ThreadPoolExecutor.CallerRunsPolicy()用调用者所在的线程来执行任务。
    ThreadPoolExecutor.DiscardPolicy()忽略新任务,不抛出异常。
    ThreadPoolExecutor.DiscardOldestPolicy()丢弃队列中最近的一个任务,并执行当前任务。
          另外,也可以自定义拒绝策略,通过实现RejectedExecutionHandler接口,可以自定义拒绝策略的行为。

4.线程池的参数配置建议

  • 核心线程数:根据系统可用的核心处理器数量进行配置,一般推荐为CPU内核数 * 2 + 1(对于IO密集型应用)或CPU内核数 + 1(对于CPU密集型应用)。

  • 最大线程数:一般设置为大于或等于核心线程数,具体值根据系统负载和业务需求进行调整。

  • 任务队列:推荐使用有界队列,如ArrayBlockingQueue,以避免无限制的任务堆积导致的内存溢出。

  • 拒绝策略:根据业务需求选择合适的拒绝策略,如CallerRunsPolicy(调用者运行策略)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)等。

5.线程池的生命周期管理

线程池的生命周期管理包括创建、运行、关闭等阶段。在应用程序结束或不再需要线程池时,应正确关闭线程池,以释放系统资源。

6.创建线程池示例

基于Java标准库java.util.concurrent中的ThreadPoolExecutor类,结合阿里巴巴推荐的实践来创建一个线程池

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;

public class AlibabaThreadPoolExample {

    public static void main(String[] args) {
        // 核心线程数
        int corePoolSize = 5;
        // 最大线程数
        int maximumPoolSize = 10;
        // 非核心线程空闲存活时间
        long keepAliveTime = 60L;
        // 时间单位
        TimeUnit unit = TimeUnit.SECONDS;
        // 任务队列ArrayBlockingQueue,它是基于数组的阻塞队列,有界队列有助于防止资源耗尽
        ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
        // 线程池拒绝策略:这里采用了CallerRunsPolicy,即如果线程池已满,则直接在调用者线程中运行被拒绝的任务。这是一种简单的反馈机制,能够减缓任务提交速度,但可能会影响到调用者线程的性能。根据实际需要,可以选择其他的拒绝策略,如AbortPolicy(直接抛出异常)、DiscardPolicy(丢弃无法处理的任务)、DiscardOldestPolicy(丢弃队列头部的任务,尝试再次提交当前任务)。
        RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();

        // 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue,
                handler);

        // 提交任务
        for (int i = 0; i < 120; i++) {
            final int taskId = i;
            executor.execute(() -> {
                // 模拟任务执行
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务 " + taskId + " 执行完成");
            });
        }

        // 关闭线程池
        // 注意:这里为了示例不关闭线程池,实际使用中需要根据情况合理关闭
        // executor.shutdown();
        // 等待所有任务完成
        // try {
        //     if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
        //         executor.shutdownNow();
        //     }
        // } catch (InterruptedException e) {
        //     executor.shutdownNow();
        // }
    }
}

7.线程池总结

阿里线程池规范的核心在于避免使用Executors类直接创建线程池,而是通过ThreadPoolExecutor的构造方法明确设置线程池的参数,以规避资源耗尽的风险。同时,合理的参数配置和生命周期管理也是保障线程池高效稳定运行的关键。

三、获取线程执行结果

1. 使用CallableFuture

Callable接口类似于Runnable,但它可以返回一个结果,并且可以抛出一个异常。你可以将Callable任务提交给ExecutorService,它会返回一个Future对象。通过Future对象,你可以检查计算是否完成,等待计算完成,并检索计算结果。

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        // 模拟耗时任务
        Thread.sleep(1000);
        return 123;
    }
});

// 等待任务执行完成,并获取结果
try {
    System.out.println(future.get()); // 输出:123
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

executor.shutdown(); 

2. 使用CompletableFuture

CompletableFuture是Java 8引入的,提供了更为强大的异步编程能力。它实现了FutureCompletionStage接口,允许你以非阻塞的方式等待异步操作的完成,并且可以添加回调函数来处理结果或异常。

CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时任务
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    return 123;
});

//非阻塞的方式等待异步操作的完成

completableFuture.thenAccept(result -> System.out.println(result)); // 输出:123

// 或者阻塞等待结果
try {
    System.out.println(completableFuture.get()); // 输出:123
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();

3. 继承Thread类并覆写run方法,通过共享变量获取结果

不是一个推荐的做法,因为它违背了线程之间的解耦原则,并且容易引发竞态条件(race condition)和同步问题。但如果你的应用场景确实需要这么做,你可以通过定义一个共享变量来存储线程的执行结果,并使用适当的同步机制(如synchronizedvolatileLock)来确保线程安全。

4. 使用ForkJoinPool

ForkJoinPool是Java 7引入的,用于执行可以分解为更小任务的并行算法。它特别适用于递归任务分解。ForkJoinPool中的任务通过RecursiveAction(无返回值)或RecursiveTask<V>(有返回值)来提交。通过RecursiveTask,你可以获取到子任务的结果。

ForkJoinPool pool = ForkJoinPool.commonPool();
ForkJoinTask<Integer> task = pool.submit(() -> {
    // 递归或并行计算逻辑
    return 123;
});

try {
    System.out.println(task.get()); // 输出:123
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

5.获取线程结果总结

在Java中,获取线程执行结果的主要方式是使用CallableFuture,以及Java 8引入的CompletableFuture。这两种方式都提供了丰富的API来支持异步编程和结果处理。继承Thread类并通过共享变量获取结果的方式虽然可行,但通常不推荐使用,因为它容易导致线程间的耦合和同步问题。ForkJoinPool则适用于需要递归分解任务的场景。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值