ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java使用多种方式实现多线程及线程池的使用

2022-08-29 10:32:44  阅读:152  来源: 互联网

标签:java Thread thread 任务 线程 多线程 public pool


 一、多线程实现了什么?

为了解决负载均衡问题,充分利用CPU资源.为了提高CPU的使用率,采用多线程的方式去同时完成几件事情而不互相干扰.为了处理大量的IO操作时或处理的情况需要花费大量的时间等等,比如:读写文件,视频图像的采集,处理,显示,保存等

二、多线程的使用

在java中,多线程得主要实现方式有四种:继承Thread类,实现Runnable接口、实现callable接口通过FutureTask包装器来创建Thread线程,使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,而后两种是带返回值的。除此之外,通过Timer启动定时任务,或者通过像Spring Task和quartz这样的第三方任务调度框架也可以开启多线程任务

1、继承Thread类创建线程

Thread类本质上也是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程比较简单,通过继承Thread类并复写run()方法,就可以启动新线程并执行自己定义的run()方法。

public class ThreadDemo extends Thread {
    
    public ThreadDemo(String name) {
        // 设置当前线程的名字
        this.setName(name);
    }
    
    @Override
    public void run() {
        System.out.println("当前运行的线程名为: " + Thread.currentThread().getName());
    }
    
    public static void main(String[] args) throws Exception {
        // 注意这里,要调用start方法才能启动线程,不能调用run方法
        new ThreadDemo("MyThreadOne").start();
        new ThreadDemo("MyThreadTwo").start();
        
    }
    
}

输出结果:

当前运行的线程名为: MyThreadOne
当前运行的线程名为: MyThreadTwo

2、实现Runnable接口创建线程 由于Java是单继承机制,如果自己的类已经继承自另一个类,则无法再直接继承Thread类,此时,可以通过实现Runnable接口来实现多线程。

实现Runnable接口并实现其中的run方法,然后通过构造Thread实例,传入Runnable实现类,然后调用Thread的start方法即可开启一个新线程。

public class MyRunnable implements Runnable {
    
    @Override
    public void run() {
        System.out.println("当前运行的线程名为: " + Thread.currentThread().getName());
    }
    
    public static void main(String[] args) throws Exception {
        MyRunnable runnable = new MyRunnable();
        new Thread(runnable, "MyThreadOne").start();
        new Thread(runnable, "MyThreadTwo").start();
        
    }
    
}

输出结果:

当前运行的线程名为: MyThreadOne
当前运行的线程名为: MyThreadTwo

3、实现Callable接口通过FutureTask包装器来创建Thread线程

首先需要一个实现Callable接口的实例,然后实现该接口的唯一方法call逻辑,接着把Callable实例包装成FutureTask传递给Thread实例启动新线程。FutureTask本质上也实现了Runnable接口,所以同样可以用来构造Thread实例。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
​
public class MyCallable {
    
    public static void main(String[] args) throws Exception {
        // 创建线程任务,lambada方式实现接口并实现call方法
        Callable<Integer> callable = () -> {
            System.out.println("线程任务开始执行了...");
            Thread.sleep(2000);
            return 1;
        };
        
        // 将任务封装为FutureTask
        FutureTask<Integer> task = new FutureTask<>(callable);
        
        // 开启线程,执行线程任务
        new Thread(task).start();
        
        // ====================
        // 这里是在线程启动之后,线程结果返回之前
        System.out.println("线程启动之后,线程结果返回之前...");
        // ====================
        
        // 为所欲为完毕之后,拿到线程的执行结果
        Integer result = task.get();
        System.out.println("主线程中拿到异步任务执行的结果为:" + result);
        
    }
    
}

运行结果:

线程启动之后,线程结果返回之前...
线程任务开始执行了...
主线程中拿到异步任务执行的结果为:1

4、使用ExecutorService、Callable、Future实现有返回结果的线程(线程池方式)

ExecutorService、Callable、Future三个接口都是属于Executor框架。可返回值的任务必须实现Callable接口。通过ExecutorService执行Callable任务后,可以获取到一个Future的对象,在该对象上调用get()就可以获取到Callable任务返回的结果了。

注意:Future的get方法是阻塞的,即:线程无返回结果,get方法会一直等待。

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
​
public class CreateThreadDemo4 {
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("---- 主程序开始运行 ----");
        Date startTime = new Date();
        
        int taskSize = 5;
        // 创建一个线程池,Executors提供了创建各种类型线程池的方法,具体详情请自行查阅
        ExecutorService executorService = Executors.newFixedThreadPool(taskSize);
        
        // 创建多个有返回值的任务
        List<Future> futureList = new ArrayList<Future>();
        for (int i = 0; i < taskSize; i++) {
            Callable callable = new MyCallable(i);
            // 执行任务并获取Future对象
            Future future = executorService.submit(callable);
            futureList.add(future);
        }
        
        // 关闭线程池
        executorService.shutdown();
​
        // 获取所有并发任务的运行结果
        for (Future future : futureList) {
            // 从Future对象上获取任务的返回值,并输出到控制台
            System.out.println(">>> " + future.get().toString());
        }
​
        Date endTime = new Date();
        System.out.println("---- 主程序结束运行 ----,程序运行耗时【" + (endTime.getTime() - startTime.getTime()) + "毫秒】");
    }
}
​
class MyCallable implements Callable<Object> {
    private int taskNum;
​
    MyCallable(int taskNum) {
        this.taskNum = taskNum;
    }
​
    public Object call() throws Exception {
        System.out.println(">>> " + taskNum + " 线程任务启动");
        Date startTime = new Date();
        Thread.sleep(1000);
        Date endTime = new Date();
        long time = endTime.getTime() - startTime.getTime();
        System.out.println(">>> " + taskNum + " 线程任务终止");
        return taskNum + "线程任务返回运行结果, 当前任务耗时【" + time + "毫秒】";
    }
}

输出结果:

---- 主程序开始运行 ----
>>> 0 线程任务启动
>>> 1 线程任务启动
>>> 2 线程任务启动
>>> 3 线程任务启动
>>> 4 线程任务启动
>>> 0 线程任务终止
>>> 1 线程任务终止
>>> 0线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 1线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 4 线程任务终止
>>> 3 线程任务终止
>>> 2 线程任务终止
>>> 2线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 3线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 4线程任务返回运行结果, 当前任务耗时【1001毫秒】
---- 主程序结束运行 ----,程序运行耗时【1009毫秒】

5、其他创建线程的方式

当然,除了以上四种主要的线程创建方式之外,也还有很多其他的方式可以启动多线程任务。比如通过Timer启动定时任务,或者通过像Spring Task和quartz这样的第三方任务调度框架也可以开启多线程任务,关于第三方任务调度框架的例子还请查询相关资料。

三、java中线程池得使用

1、为什么不适用 new Thread?

首先从我秉持的原则入手,“简洁优雅”。试想如果在一段代码中你需要创建很多线程,那么你就不停地调用 new Thread(...).start() 么?显然这样的代码一点也不简洁,也不优雅。初次之外这样的代码还有很多坏处:

每次都要新建一个对象,性能差; 建出来的很多个对象是独立的,缺乏统一的管理。如果在代码中无限新建线程会导致这些线程相互竞争,占用过多的系统资源从而导致死机或者 oom; 缺乏许多功能如定时执行、中断等。

从这些坏处很容易可以看出解决方法,那就是弄一个监管者来统一的管理这些线程,并将它们存到一个集合(或者类似的数据结构)中,而且还要动态地分配它们的任务。当然Java已经给我们提供好十分健全的东西来使用了,那就是线程池

Java线程池 Java提供了一个工厂类来构造我们需要的线程池,这个工厂类就是 Executors 。这个类提供了很多方法,我们这里主要讲它提供的4个创建线程池的方法,即

newCachedThreadPool() newFixedThreadPool(int nThreads) newScheduledThreadPool(int corePoolSize) newSingleThreadExecutor()

newCachedThreadPool: 这个方法正如它的名字一样,创建一个可缓存的无界线程池,如果线程池长度超过处理需要,可灵活回收空线程,若无可回收,则新建线程。当线程池中的线程空闲时间超过60s,则会自动回收该线程,当任务超过线程池的线程数则创建新的线程,线程池的大小上限为Integer.MAX_VALUE,可看作无限大。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
/**
 * 可缓存无界线程池测试 
 * 当线程池中的线程空闲时间超过60s则会自动回收该线程,核心线程数为0 
 * 当任务超过线程池的线程数则创建新线程。线程池的大小上限为Integer.MAX_VALUE, 
 * 可看做是无限大。
 */
​
public class newCachedThreadPool {
    
    public static void main(String[] args) {
        // 创建可缓存的无界线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); // 创建缓存线程池
        
        for (int i = 0; i < 10; i++) {
            final int index = i;
            
            // 每次发布任务前等待一段时间,如1s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            // 执行任务
            cachedThreadPool.execute(() -> System.out.println(Thread.currentThread().getName() + ":" + index));
        }
    }
}

 

输出结果:

pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-1:3
pool-1-thread-1:4
pool-1-thread-1:5
pool-1-thread-1:6
pool-1-thread-1:7
pool-1-thread-1:8
pool-1-thread-1:9

newFixedThreadPool(int nThreads)

创建一个指定大小的线程池,可以看到这个方法中带了一个参数,这个方法创建的线程池是定长的,这个参数就是线程池的大小。也就是说,在同一时间执行的线程数量只能是 nThreads 这么多,这个线程池可以有效的控制最大并发数从而防止占用过多资源。超出的线程会放在线程池的一个队列里等待其他线程执行完,它是一个无界队列

/**
 * 创建固定线程数量的线程池测试
 * 创建一个固定大小的线程池,该方法可指定线程池的固定大小,对于超出的线程会在LinkedBlockingQueue队列中等待
 * 核心线程数可以指定,线程空闲时间为0
 */
public class newFixedThreadPool {
    
    public static void main(String[] args) {
        
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); // 创建缓存线程池,大小为3
        
        for (int i = 0; i < 10; i++) {
            final int index = i;
            
            // 每次发布任务前等待一段时间,如1s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            // 执行任务
            fixedThreadPool.execute(() -> System.out.println(Thread.currentThread().getName() + ":" + index));
        }
    }
}

输出结果:

pool-1-thread-1:0
pool-1-thread-2:1
pool-1-thread-3:2
pool-1-thread-1:3
pool-1-thread-2:4
pool-1-thread-3:5
pool-1-thread-1:6
pool-1-thread-2:7
pool-1-thread-3:8
pool-1-thread-1:9

 

可以看到我创建了一个大小为3的线程池,也就是说它支持的最大并发线程数是3,运行后发现这些数确实是3个3个为一组输出的。

合理得设置定长线程池是一件非常重要的事

从 Scheduled 大概可以猜出这个线程池是为了解决上面说过的第3个坏处,也就是缺乏定时执行功能。这个线程池也是定长的,参数 corePoolSize 就是线程池的大小,即在空闲状态下要保留在池中的线程数量。

而要实现调度需要使用这个线程池的 schedule() 方法 (注意这里要把新建线程池的返回类 ExecutorService 改成 ScheduledExecutorService 噢)

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
​
public class newScheduledThreadPool {
    
    // 执行任务
    
    public static void main(String[] args) {
    
        // 注意!这里把 ExecutorService 改成了 ScheduledExecutorService ,否则没有定时功能
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3); // 创建缓存线程池
        
        for (int i = 0; i < 1; i++) {
            final int index = i;
            
            // 每次发布任务前等待一段时间,如1s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            // 执行任务
            scheduledThreadPool.schedule(() -> System.out.println(Thread.currentThread().getName() + ": 我会在3秒后执行。"),
                    3, TimeUnit.SECONDS);
        }
    }
}

输出结果

pool-1-thread-1: 我会在3秒后执行。

newSingleThreadExecutor() 这个线程池就比较简单了,他是一个单线程池,只使用一个线程来执行任务。但是它与 newFixedThreadPool(1, threadFactory) 不同,它会保证创建的这个线程池不会被重新配置为使用其他的线程,也就是说这个线程池里的线程始终如一。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class newSingleThreadExecutor {
    
    public static void main(String[] args) {
        
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); // 创建单线程池
        
        for (int i = 0; i < 10; i++) {
            final int index = i;
            
            // 执行任务
            singleThreadExecutor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + ":" + index);
                
                // 模拟执行任务耗时1秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

输出结果

pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-1:3
pool-1-thread-1:4
pool-1-thread-1:5
pool-1-thread-1:6
pool-1-thread-1:7
pool-1-thread-1:8
pool-1-thread-1:9
 


标签:java,Thread,thread,任务,线程,多线程,public,pool
来源: https://www.cnblogs.com/gengjiangtao/p/16635002.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有