java 实现多线程的四种方式

1、继承Thread类的方式进行实现

java.lang.Thread是Java提供的线程类,继承了该类的就是一个线程类,所有的线程对象都必须是 Thread 类或其⼦类的实例。每个线程的作⽤是完成⼀定的任务,实际上就是执⾏⼀段程序流即⼀段顺序执⾏的代码,Java 使⽤线程执⾏体来代表这段程序流。主线程和分支线程是并发执行的,谁在前谁在后是随机的抢占式分配。Java中通过继承Thread类来创建并启动多线程的步骤如下:

        1. 定义 Thread 类的⼦类,并重写该类的 run() ⽅法,该 run() ⽅法的⽅法体就代表了线程需要完成的任务,因此把 run() ⽅法称为线程执⾏体。       

public class MyThread extends Thread{
    @Override
    public void run() {
        // 线程要执行代码,getName()方法可以在线程启动时获取线程的名字
        for (int i = 0; i < 15; i++) {
            System.out.println(getName() + " Holle world");
        }
    }
}

        2. 创建 Thread ⼦类的实例,即创建了线程对象

        3. 调⽤线程对象的 start() ⽅法来启动该线程

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
 
        t1.setName("线程1");
        t2.setName("线程2");
 
        t1.start();
        t2.start();
    }
}
2、实现Runnable接口的方式进行实现

采⽤ java.lang.Runnable 实现也是⾮常常⻅的⼀种,只需要重写 run ⽅法即可。

        步骤如下:

        1. 定义 Runnable 接⼝的实现类,并重写该接⼝的 run() ⽅法,该 run() ⽅法的⽅法体同样是该线程的线程执⾏体。

public class MyRun implements Runnable {
    @Override
    public void run() {
        // 线程要执行代码
        for (int i = 0; i < 15; i++) {
            // currentThread()方法获取到当前线程的对象
            // 把上面的两行代码改成链式编程的写法
            System.out.println(Thread.currentThread().getName() + " Holle world");
        }
    }
}

        2. 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。

        3. 调⽤线程对象的 start() ⽅法来启动线程。

public class ThreadDemo {
    public static void main(String[] args) {
        // 创建MyRun的对象
        // 表示多线程要执行的任务
        MyRun mr = new MyRun();
        // 创建线程对象,并且把任务传递给线程对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
 
        //给线程设置名字
        t1.setName("线程1");
        t2.setName("线程2");
        //开启线程
        t1.start();
        t2.start();
    }
}
3、利用Callable接口和Future接口方式实现

        Callable接口是Runable接口的增强版。同样用call()方法作为线程的执行体,增强了之前的run()方法。因为call方法可以有返回值,也可以声明抛出异常。

        Future接口用来代表Callable接口里的call()方法的返回值,FutureTask类是Future接口的一个实现类,FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。

        步骤如下:

                1.创建一个类MyCallable实现Callable接口
                2.重写call (是有返回值的,表示多线程运行的结果)

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int result = 0;
        System.out.println(Thread.currentThread().getName() + " Holle world");
        for (int i = 0; i < 15; i++) {
            result += i;
        }
        return result;
    }
}


                3.创建MyCallable的对象(表示多线程要执行的任务)
                4.创建FutureTask的对象(作用管理多线程运行的结果)
                5.创建Thread类的对象,并启动(表示线程)

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建MyCallable的对象(表示多线程要执行的任务)
        MyCallable mc = new MyCallable();
        //创建FutureTask的对象(作用管理多线程运行的结果)
        FutureTask<Integer> fu = new FutureTask<>(mc);
        FutureTask<Integer> fu1 = new FutureTask<>(mc);
        //创建线程的对象
        Thread t1 = new Thread(fu);
        Thread t2 = new Thread(fu1);
        t1.setName("线程1");
        t2.setName("线程2");
        //启动线程
        t1.start();
        t2.start();
        //获取多线程运行的结果
        Integer result1 = fu.get();
        Integer result2 = fu1.get();
        System.out.println(result1 + " : " + result2);
    }
}
4、使用线程池

在实际开发中,一般不使用以上三种方式,而是使用线程池的方式。

比如以下是阿里开发规范的相关规定:

线程池执行任务逻辑和线程池参数的关系

执行逻辑说明:

判断核心线程数是否已满,核心线程数大小和corePoolSize参数有关,未满则创建线程执行任务
若核心线程池已满,判断队列是否满,队列是否满和workQueue参数有关,若未满则加入队列中
若队列已满,判断线程池是否已满,线程池是否已满和maximumPoolSize参数有关,若未满创建线程执行任务
若线程池已满,则采用拒绝策略处理无法执执行的任务,拒绝策略和handler参数有关
Executors创建返回ThreadPoolExecutors对象
Executors创建返回ThreadPoolExecutor对象的方法共有三种:

Executors#newCachedThreadPool => 创建可缓存的线程池
Executors#newSingleThreadExecutor => 创建单线程的线程池
Executors#newFixedThreadPool => 创建固定长度的线程池

        // 创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        CompletionService<String> completionService = new ExecutorCompletionService<>(pool);
        List<Future<String>> resultList = new ArrayList<>();
        // for 循环
        for (String param : list) {
            Future<String> result = completionService.submit(()->{
                // 业务逻辑
                
                // 线程的结果
                return "成功";
            });
            resultList.add(result);
        }
        pool.shutdown();
        // 遍历线程的结果
        List<String> list = new ArrayList<>();
        for (Future<String> result : resultList) {
            try {
                System.out.println(result.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
ExecutorService executorService = Executors.newFixedThreadPool(10);

executorService.execute(() -> {
 System.out.println("Asynchronous task");
});

executorService.shutdown();

ExecutorService的关闭
当我们使用完成ExecutorService之后应该关闭它,否则它里面的线程会一直处于运行状态。

当然以上方式也有弊端,我们再看一下阿里开发规范的相关规定:

因此,为了更好地控制和管理线程池,推荐使用ThreadPoolExecutor类来手动创建线程池,它可以自定义线程名称,还可以根据具体的需求自己设置线程池的参数:例如核心线程数、最大线程数、队列类型、线程工厂等、以及自定义拒绝策略来处理任务无法执行的情况。

实际上Executors本身只是一个工具类。它创建线程池的3种方式,都是是通过new 一个 ThreadPoolExecutor实现的:

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);
  • corePoolSize:线程池中持久保持的核心线程数,可通过setCorePoolSize函数动态更改。
  • runnableTaskQueue:用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue
  • maximumPoolSize:线程池允许创建的最大线程数,可通过setMaximumPoolSize函数动态更改
  • ThreadFactory:用于设置创建线程的工厂。
  • RejectedExecutionHandler:当队列和线程池都满了,此时无法加入新的任务,线程池饱和。该参数表示饱和策略,默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy
  • keepAliveTime:默认情况下是指非核心线程变成空闲状态后的存活时间,不影响核心线程,可通过setKeepAliveTime函数动态更改。++但是可通过调用allowCoreThreadTimeOut函数,来设置该变量对核心线程起作用。++
  • TimeUnit:keepAliveTime的时间单位。
所以,针对可能出现的问题,自定义一个工具类来处理。
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 处理内容:线程池工具类
 */
public class ThreadPoolExecutorFactory {
    /**
     * corePoolSize 池中所保存的线程数,包括空闲线程。
     */
    private static final int corePoolSize = 10;
    /**
     * maximumPoolSize - 池中允许的最大线程数(采用LinkedBlockingQueue时没有作用)。
     */
    private static final int maximumPoolSize = 50;
    /**
     * keepAliveTime -当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间,线程池维护线程所允许的空闲时间
     */
    private static final int keepAliveTime = 5;

    /**
     * 执行前用于保持任务的队列(缓冲队列)
     */
    private static final int capacity = 3000;

    /**
     * 线程池对象
     */
    private static ThreadPoolExecutor threadPoolExecutor = null;

    //构造方法私有化
    private ThreadPoolExecutorFactory(){}

    public static ThreadPoolExecutor getThreadPoolExecutor(){
        if(null == threadPoolExecutor){
            ThreadPoolExecutor t;
            synchronized (ThreadPoolExecutor.class) {
                t = threadPoolExecutor;
                if(null == t){
                    synchronized (ThreadPoolExecutor.class) {
                        t = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),new ThreadPoolExecutor.DiscardOldestPolicy());
                    }
                    threadPoolExecutor = t;
                }
            }
        }
        return threadPoolExecutor;

    }
}
// 调用多线程工具类
FutureTaskUtil futureTaskUtil = new FutureTaskUtil(ThreadPoolExecutorFactory.getThreadPoolExecutor());

// 查询并返回业务数据
Future<Object> fu1 = futureTaskUtil.submit(() -> {	
	return baseService.getList(pd);
});
List<Map> = (List<Map>) futureTaskUtil.getResult(fu1);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sunyanchun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值