Java中创建线程的四种方法解析

 更新时间:2023年10月21日 10:03:29   作者:浪浪山的猿  
这篇文章主要介绍了Java中创建线程的四种方法解析,线程是Java编程语言中的一个重要概念,它允许程序在同一时间执行多个任务,线程是程序中的执行路径,可以同时执行多个线程,每个线程都有自己的执行流程,需要的朋友可以参考下

前言

在 Java 中,实现多线程的主要有以下四种

  1. 继承 Thread 类,重写 run() 方法;
  2. 实现 Runnable 接口,实现 run() 方法,并将 Runnable 实现类的实例作为 Thread 构造函数的参数 target;
  3. 实现 Callable 接口,实现 call() 方法,然后通过 FutureTask 包装器来创建 Thread 线程;
  4. 通过 ThreadPoolExecutor 创建线程池,并从线程池中获取线程用于执行任务;

继承Thread类

创建步骤:

  1. 定义类继承Thread;
  2. 重写Thread类中的run方法;
  3. 实例化线程对象;
  4. 调用线程的start方法开启线程;

代码:

package com.scg.springcloudordercenter.controller;
 
// 1.继承Thread类
public class ThreadDemo extends Thread{
 
    private int ticket=20;
 
    // 2.重写run 方法
    public void run(){
        for(int i=0;i<20;i++){
 
            if(this.ticket>0){
                System.out.println("剩余票数=="+ticket--);
            }
        }
 
    }
}
 
class ThreadTest {
    public static void main(String[] args) {
        // 3.实例化线程对象
        ThreadDemo md = new ThreadDemo() ;
        // 4.调用start()开启线程
        md.start();
    }
}
 
 

通过实现 Runnable 接口

创建步骤:

  1. 实现Runnable接口;
  2. 重写Run()方法;
  3. 实例化实现类;
  4. 将实例化线程类转化为线程对象;
  5. 开启线程 调用start()方法;

代码

package com.scg.springcloudordercenter.controller;
 
 
// 1.实现Runnable类
public class RunnableDemo implements Runnable{
 
    private int ticket=20;
 
    // 2.重写run 方法
    public void run(){
        for(int i=0;i<20;i++){
 
            if(this.ticket>0){
                System.out.println("剩余票数=="+ticket--);
            }
        }
 
    }
}
 
class RunnableTest {
    public static void main(String[] args) {
        // 3.实例化实现类
        RunnableDemo rd = new RunnableDemo() ;
        // 4.将实例化实现类转化为线程对象
        Thread thread = new Thread(rd);
        // 5.开启线程
        thread.start();
    }
}
 
 
实现 Runnable 接口比继承 Thread 类所具有的优势主要有:
① 可以避免 JAVA 中单继承的限制;
② 线程池只能放入实现 Runable 或 Callable类线程,不能直接放入继承 Thread 的类
③ 代码可以被多个线程共享,代码和数据独立,适合多个相同的程序代码的线程去处理同一个资源的情况

实现Callable接口

实现步骤

1、定义一个线程任务类实现Callable接口,声明线程执行的结果类型。

2、重写线程任务类的call()方法,这个方法可以直接返回执行的结果。

3、创建一个Callable的线程任务对象。

4、把Callable的线程任务对象包装成一个未来任务对象。

5、把未来任务对象包装成线程对象。

6、调用线程start()方法,启动线程。

7、获取线程执行结果。

代码:

/**
 * @author gf
 * @date 2023/2/22
 */
// 1、定义一个线程任务类实现Callable接口,声明线程执行的结果类型。
public class CallableTicket implements Callable<Object > {
 
    private int ticket=20;
 
    // 2、重写线程任务类的call()方法,这个方法可以直接返回执行的结果。
    @Override
    public Object  call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                sum += i;
            }
        }
        return sum;
    }
 
}
package com.scg.springcloudordercenter.controller;
 
import java.util.concurrent.FutureTask;
 
/**
 * @author gf
 * @date 2023/2/22
 */
public class CallableMain {
 
    public static void main(String[] args) {
 
        // 3、创建一个Callable的线程任务对象。
        CallableTicket callableTicket = new CallableTicket();
 
        // 4、把Callable的线程任务对象包装成一个未来任务对象。
        FutureTask futureTask = new FutureTask(callableTicket);
 
        // 5、把未来任务对象包装成线程对象。
        Thread thread = new Thread(futureTask);
 
        // 6、调用线程start()方法,启动线程。
        thread.start();
 
        // 7、获取线程执行结果。如果此时获取结果的任务还未执行完成,会让出CPU,直至任务执行完成才获取结果。
        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()方法返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:"+sum);
        } catch (Exception e) {
            e.printStackTrace();
        }
 
 
    }
}

优点:

与使用Runnable相比,Callable功能更强大

  • 相比run方法,可以有返回值。
  • 方法可以抛异常。
  • 支持泛型的返回值。
  • 需要借助FutureTask类,比如获取返回结果。

Future接口

  • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
  • FutureTask是Future接口的唯一实现类。
  • FutureTask同时实现了Runable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

使用线程池

通过Executors创建

它提供了四种线程池:

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public class Demo01 {
    public static void main(String[] args) {
        //ExecutorService threadPool = Executors.newSingleThreadExecutor(); //创建单个线程
        //ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);//创建一个固定大小的线程池
        //ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定大小的线程池
        ExecutorService threadPool = Executors.newCachedThreadPool(); //创建大小可伸缩的线程池
 
        try {
            for (int i = 0; i < 30; i++) {
                //使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown(); //线程池使用完毕后需要关闭
        }
 
    }
}

注意:不建议使用这种方法创建线程池。因为newFixedThreadPool 和newSingleThreadExecutor允许的最大请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。newCachedThreadPoo和newScheduledThreadPool允许的创建线程的最大数量为Integer.MAX_VALUE,,从而导致OOM。

通过ThreadPoolExecutor创建

ThreadPoolExecutor有7个核心参数

  1. ThreadPoolExecutor(int corePoolSize, //核心线程池大小,始终存在
  2. int maximumPoolSize, //最大线程数
  3. long keepAliveTime, //空闲线程等待时间,超时则销毁
  4. TimeUnit unit, //时间单位
  5. BlockingQueue<Runnable> workQueue, //等待阻塞队列
  6. ThreadFactory threadFactory, //线程工厂
  7. RejectedExecutionHandler handler) //线程拒绝策略

其最后一个参数拒绝策略共有四种:

  • new ThreadPoolExecutor.AbortPolicy():达到最大承载量,不再处理,并且抛出异常
  • new ThreadPoolExecutor.CallerRunsPolicy():达到最大承载量,从哪来的去哪里
  • new ThreadPoolExecutor.DiscardPolicy():达到最大承载量,丢掉任务,但不抛出异常
  • new ThreadPoolExecutor.DiscardOldestPolicy():达到最大承载量,尝试与最早执行的线程去竞争,不抛出异常
public class Demo02 {
    public static void main(String[] args) {
        //new ThreadPoolExecutor.AbortPolicy():达到最大承载量,不再处理,并且抛出异常
        //new ThreadPoolExecutor.CallerRunsPolicy():达到最大承载量,从哪来的去哪里
        //new ThreadPoolExecutor.DiscardPolicy():达到最大承载量,丢掉任务,但不抛出异常
        //new ThreadPoolExecutor.DiscardOldestPolicy():达到最大承载量,尝试与最早执行的线程去竞争,不抛出异常
 
        //最大线程池大小该如何定义
        //1.cpu密集型,逻辑处理器个数
        //2.io密集型 > 判断程序十分耗IO的线程,最大线程池大小应该比这个大
        int maxPools= Runtime.getRuntime().availableProcessors();
        System.out.println(maxPools);
 
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                maxPools,
                3,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );
 
        try {
            for (int i = 0; i < 10; i++) {
                //使用线程池来创建线程
                //最大承载:maximumPoolSize+workQueue,超过执行拒绝策略
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown(); //线程池使用完毕后需要关闭
        }
 
    }
}

使用线程池的优点;

1.减少资源的消耗。重复利用已经创建的线程,避免频繁的创造和销毁线程,减少消耗。

2.提高响应速度。当执行任务时,不需要去创建线程再来执行,只要调动现有的线程来执行即可。

3.提高了线程的管理性。线程是稀缺资源,使用线程池可以进行统一的分配、调优和监控。

到此这篇关于Java中创建线程的四种方法解析的文章就介绍到这了,更多相关Java创建线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Boot集成Druid出现异常报错的原因及解决

    Spring Boot集成Druid出现异常报错的原因及解决

    Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。本文讲述了Spring Boot集成Druid项目中discard long time none received connection异常的解决方法,出现此问题的同学可以参考下
    2021-05-05
  • SpringBoot自动配置深入探究实现原理

    SpringBoot自动配置深入探究实现原理

    在springboot的启动类中可以看到@SpringBootApplication注解,它是SpringBoot的核心注解,也是一个组合注解。其中@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解尤为重要。今天我们就来浅析这三个注解的含义
    2022-08-08
  • Java装饰器设计模式初探

    Java装饰器设计模式初探

    这篇文章主要为大家详细介绍了Java装饰器设计模式,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • spring cloud gateway中如何读取请求参数

    spring cloud gateway中如何读取请求参数

    这篇文章主要介绍了spring cloud gateway中如何读取请求参数的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Java中通过三级缓存解决Spring循环依赖详解

    Java中通过三级缓存解决Spring循环依赖详解

    这篇文章主要介绍了Java中通过三级缓存解决Spring循环依赖详解,当出现两个或多个 Bean 在初始化时相互依赖的情况时,Spring Boot 会将其中一个 Bean 提前暴露出来,以便其他 Bean 能够在初始化时正确地引用它,这一策略能有效避免循环依赖导致的问题,需要的朋友可以参考下
    2023-09-09
  • druid连接泄露故障全面分析

    druid连接泄露故障全面分析

    这篇文章主要介绍了druid连接泄露故障全面分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • java如何实现抽取json文件指定字段值

    java如何实现抽取json文件指定字段值

    这篇文章主要介绍了java如何实现抽取json文件指定字段值,具有很好的参考价值,希望对大家有所帮助。
    2022-06-06
  • EntityWrapper如何在and条件中嵌套or语句

    EntityWrapper如何在and条件中嵌套or语句

    这篇文章主要介绍了EntityWrapper如何在and条件中嵌套or语句,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • JAVA线程sleep()和wait()详解及实例

    JAVA线程sleep()和wait()详解及实例

    这篇文章主要介绍了JAVA线程sleep()和wait()详解及实例的相关资料,探讨一下sleep()和wait()方法的区别和实现机制,需要的朋友可以参考下
    2017-05-05
  • Java常用命令汇总

    Java常用命令汇总

    这篇文章主要介绍了Java常用命令汇总,小编觉得挺不错的,这里给大家分享下,供需要的朋友参考。
    2017-10-10

最新评论