Java进阶(5)——创建多线程的方法extends Thread和implements Runnable的对比 线程池及常用的线程池

news2025/1/12 10:38:35

目录

  • 引出
  • 创建多线程的方法
    • Thread类创建多线程
    • Runnable接口创建多线程
    • Thread类的常用方法
    • 两者的对比
  • 线程池
    • 是啥?为啥用?
    • 结合案例理解常用的线程池
    • 继承Runnable接口的工人实体类
    • newSingleThreadExecutor(单线程)
    • newFixedThreadPool(加锁)
    • newCachedThreadPool
    • newScheduledThreadPool(定时任务)
    • newSingleThreadScheduledExecutor
  • 总结

引出


1.创建多线程两种方法,继承thread,实现runnable,常用runnable;
2.线程池,管理线程,控制并发;
3.常用的线程池,newFixedThreadPool定容量;
4.newCachedThreadPool容量大,Integer.MAX_VALUE;
5.newScheduledThreadPool(定时任务),newSingleThreadScheduledExecutor单线程的线程池的定时任务;
6.newSingleThreadExecutor(单线程),单线程的线程池;

在这里插入图片描述

在这里插入图片描述

创建多线程的方法

JDK提供了Thread类和Runnable接口来让我们实现自己的“线程”类。

  • 继承Thread类,并重写run方法;
  • 实现Runnable接口的run方法;

Thread类创建多线程

继承Thread类:

package com.tianju.book.jpa.syn;

public class Demo1 {
    public static class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println("通过继承Thread创建多线程");
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

注意要调用start()方法后,该线程才算启动!

我们在程序里面调用了start()方法后,虚拟机会先为我们创建一个线程,然后等到这个线程第一次得到时间片时再调用run()方法。

注意不可多次调用start()方法。在第一次调用start()方法后,再次调用start()方法会抛出IllegalThreadStateException异常。

在这里插入图片描述

Runnable接口创建多线程

在这里插入图片描述

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

在这里插入图片描述

package com.tianju.book.jpa.syn;

public class Demo2 {
    public static class MyThread implements Runnable{

        @Override
        public void run() {
            System.out.println("继承runnable实现多线程");
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("匿名内部类");
            new MyThread().run();
        });
        thread.start();
    }
}

Thread类的常用方法

这里介绍一下Thread类的几个常用的方法:

  • currentThread():静态方法,返回对当前正在执行的线程对象的引用;
  • start():开始执行线程的方法,java虚拟机会调用线程内的run()方法;
  • yield():yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。这里需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的;
  • sleep():静态方法,使当前线程睡眠一段时间;
  • join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的;

两者的对比

实现一个自定义的线程类,可以有继承Thread类或者实现Runnable接口这两种方式,它们之间有什么优劣呢?

  • 由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
  • Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
  • Runnable接口出现,降低了线程对象和线程任务的耦合性。
  • 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。

所以,我们通常优先使用“实现Runnable接口”这种方式来自定义线程类。

线程池

是啥?为啥用?

线程池是一种用于管理和复用线程的机制。它是一种线程管理的抽象概念,可以有效地管理线程的创建、执行和销毁,以提高应用程序的性能和资源利用率。

线程池中包含一组预先创建的线程,这些线程可以被重复使用来执行任务。当有任务需要执行时,线程池中的线程会被分配给任务,并在任务完成后返回线程池,以便可以被其他任务复用。这样可以避免频繁地创建和销毁线程,减少了线程创建和销毁的开销,提高了系统的响应速度和资源利用率。

使用线程池主要有以下三个原因:

  1. 创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程
  2. 控制并发的数量。并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃。(主要原因)
  3. 可以对线程做统一管理

结合案例理解常用的线程池

Executors类中提供的几个静态方法来创建线程池

继承Runnable接口的工人实体类

name:工人名字,

workTime:工作时间,用java的毫秒模拟现实中的1分钟

materials:物料的数量,假设一共有100个物料;

package com.tianju.book.jpa.syn;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 工厂仿真的工人
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Worker implements Runnable{
    private String name;

    private Integer workTime;

    private static Integer materials=100; // 物料数量

    @Override
    public void run() {
        System.out.println(name+"开始工作");
        try {
            Thread.sleep(workTime); // 模拟工作时间
            materials--; // 每次消耗1个物料
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(name+"工作完成,耗时"+workTime+"分钟,"+"当前剩余物料:"+materials);
    }
}

newSingleThreadExecutor(单线程)

创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

有且仅有一个核心线程( corePoolSize == maximumPoolSize=1),使用了LinkedBlockingQueue(容量很大),所以,不会创建非核心线程。所有任务按照先来先执行的顺序执行。如果这个唯一的线程不空闲,那么新来的任务会存储在任务队列里等待执行。

package com.tianju.book.jpa.syn;

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

/**
 * 进行工厂的仿真
 */
public class FactorySimulationSingle {

    public static void main(String[] args) {
        // 工人池,单线程的线程池
        ExecutorService workerPool = Executors.newSingleThreadExecutor();
        for (int i= 1;i<10;i++){
            Worker worker = new Worker("worker" + i, i * 10);
            workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
        }
        Thread thread2 = new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                Worker worker = new Worker("TH-worker" + i, i * 10);
                workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
            }
        });
        thread2.start();

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        workerPool.shutdown();
    }
}

在这里插入图片描述

newFixedThreadPool(加锁)

创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}

核心线程数量和总线程数量相等,都是传入的参数nThreads,所以只能创建核心线程,不能创建非核心线程。因为LinkedBlockingQueue的默认大小是Integer.MAX_VALUE,故如果核心线程空闲,则交给核心线程处理;如果核心线程不空闲,则入列等待,直到核心线程空闲。

与CachedThreadPool的区别

  • 因为 corePoolSize == maximumPoolSize ,所以FixedThreadPool只会创建核心线程。 而CachedThreadPool因为corePoolSize=0,所以只会创建非核心线程。
  • 在 getTask() 方法,如果队列里没有任务可取,线程会一直阻塞在 LinkedBlockingQueue.take() ,线程不会被回收。 CachedThreadPool会在60s后收回。
  • 由于线程不会被回收,会一直卡在阻塞,所以没有任务的情况下, FixedThreadPool占用资源更多
  • 都几乎不会触发拒绝策略,但是原理不同。FixedThreadPool是因为阻塞队列可以很大(最大为Integer最大值),故几乎不会触发拒绝策略;CachedThreadPool是因为线程池很大(最大为Integer最大值),几乎不会导致线程数量大于最大线程数,故几乎不会触发拒绝策略。

ExecutorService workerPool = Executors.newFixedThreadPool(1);

设置线程池中线程数量为1,此时就等同于newSingleThreadExecutor

在这里插入图片描述

package com.tianju.book.jpa.syn;

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

/**
 * 进行工厂的仿真
 */
public class FactorySimulation {

    public static void main(String[] args) {
        // 工人池,固定大小为5
        ExecutorService workerPool = Executors.newFixedThreadPool(5);

        for (int i= 1;i<10;i++){
            Worker worker = new Worker("worker" + i, 1);
            workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
        }

        Thread thread1 = new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                Worker worker = new Worker("TH1-worker" + i, i * 10);
                workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                Worker worker = new Worker("TH2-worker" + i, i * 10);
                workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
            }
        });

        thread1.start();
        thread2.start();

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        workerPool.shutdown();
    }
}

在这里插入图片描述

newCachedThreadPool

创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说VM)能够创建的最大线程大小。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

CacheThreadPool运行流程如下:

  1. 提交任务进线程池。
  2. 因为corePoolSize为0的关系,不创建核心线程,线程池最大为Integer.MAX_VALUE。
  3. 尝试将任务添加到SynchronousQueue队列。
  4. 如果SynchronousQueue入列成功,等待被当前运行的线程空闲后拉取执行。如果当前没有空闲线程,那么就创建一个非核心线程,然后从SynchronousQueue拉取任务并在当前线程执行。
  5. 如果SynchronousQueue已有任务在等待,入列操作将会阻塞。

当需要执行很多短时间的任务时,CacheThreadPool的线程复用率比较高, 会显著的提高性能。而且线程60s后会回收,意味着即使没有任务进来,CacheThreadPool并不会占用很多资源。

package com.tianju.book.jpa.syn;

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

/**
 * 进行工厂的仿真
 */
public class FactorySimulationCached {

    public static void main(String[] args) {
        // 工人池,固定大小为5 
        ExecutorService workerPool = Executors.newCachedThreadPool();
        Thread thread1 = new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                Worker worker = new Worker("TH1_worker" + i, i * 10);
                workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                Worker worker = new Worker("TH2_worker" + i, i * 10);
                workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
            }
        });

        thread1.start();
        thread2.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        workerPool.shutdown();
    }
}

在这里插入图片描述

newScheduledThreadPool(定时任务)

创建一个定长线程池,支持定时及周期性任务执行。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

在这里插入图片描述

package com.tianju.book.jpa.syn;

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

/**
 * 进行工厂的仿真
 */
public class FactorySimulationScheduled {

    public static void main(String[] args) {
        // 工人池,固定大小为5  创建大小为20的定时线程池
        ScheduledExecutorService workerPool = Executors.newScheduledThreadPool(20);
        workerPool.schedule(new Worker("worker1,延迟1秒",10) ,1, TimeUnit.SECONDS);
        workerPool.scheduleAtFixedRate(new Worker("worker2,延迟2秒,后续每3秒执行1次",10), 2,3,TimeUnit.SECONDS);
        workerPool.scheduleAtFixedRate(new Worker("worker2,延迟2秒,后续每5秒执行1次",10), 3,5,TimeUnit.SECONDS);

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        workerPool.shutdown();
    }
}

newSingleThreadScheduledExecutor

创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

在这里插入图片描述

package com.tianju.book.jpa.syn;

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

/**
 * 进行工厂的仿真
 */
public class FactorySimulationSingleS {

    public static void main(String[] args) {
        // 工人池,固定大小为5
        ScheduledExecutorService workerPool = Executors.newSingleThreadScheduledExecutor();
        Thread thread1 = new Thread(() -> {
            for (int i =1;i<10;i++){
                workerPool.scheduleAtFixedRate(
                        new Worker("worker1-"+i, 3),
                        1, 3, TimeUnit.SECONDS);
            }

        });
        Thread thread2 = new Thread(() -> {
            for (int i=1;i<10;i++){
                workerPool.scheduleAtFixedRate(
                        new Worker("worker2-"+i, 3),
                        1, 3, TimeUnit.SECONDS);
            }

        });
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        workerPool.shutdown();

    }
}


总结

1.创建多线程两种方法,继承thread,实现runnable,常用runnable;
2.线程池,管理线程,控制并发;
3.常用的线程池,newFixedThreadPool定容量;
4.newCachedThreadPool容量大,Integer.MAX_VALUE;
5.newScheduledThreadPool(定时任务),newSingleThreadScheduledExecutor单线程的线程池的定时任务;
6.newSingleThreadExecutor(单线程),单线程的线程池;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/915539.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Simulink仿真模块 - Clock

Clock&#xff1a;显示并提供仿真时间 库&#xff1a; Simulink / Sources 模型为&#xff1a; 说明 Clock 模块在每个仿真时间步输出当前仿真时间。此模块对需要仿真时间的其他模块非常有用。 当在离散系统中需要当前时间时&#xff0c;请使用Digital Clock模块。 实例 模块…

利用python批量掩膜提取遥感图像

&#xff08;1&#xff09; 前言 遥感影像的提取和分析在地理信息系统、环境监测、农业、城市规划等领域具有重要的应用价值。按掩膜提取遥感影像是一种常用的方法&#xff0c;它可以通过定义掩膜来选择感兴趣的区域&#xff0c;并排除其他干扰因素。 按掩膜提取遥感影像的方…

ssm网上服装销售系统源码和论文

ssm网上服装销售系统047 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 目前互联网上的网上销售系统每天以惊人的速度增加&#xff0c;网上购物越来越成为现代年轻人的首选&#xff0c;足不出门便能选购好自己…

H5商城公众号商城系统源码 积分兑换商城系统独立后台

网购商城系统源码 积分兑换商城系统源码 独立后台附教程 测试环境&#xff1a;NginxPHP7.0MySQL5.6thinkphp伪静态

胖小酱之广告

广告是一种宣传手段&#xff0c;通过一定形式的媒体&#xff0c;向公众传递信息的方式。广告可以分为广义和狭义两种&#xff0c;其中广义广告是指不以营利为目的的广告&#xff0c;如政府公告、社会团体声明等&#xff1b;狭义广告则是指以营利为目的的广告&#xff0c;通常是…

【JAVA基础】 IO详解

【JAVA基础】 IO详解 文章目录 【JAVA基础】 IO详解一、概述二、IO流的作用三、IO流的使用场景四、IO流的分类4.1 传输数据的单位分&#xff1a;4.2 数据传输的方向分&#xff1a;4.3 流的角色的不同分&#xff1a; 五、IO流体系六、字节输入流InputStream七、字节输出流 Outpu…

Python 从入门到实践第3版(中文版)正式版+编制版+电子版

Python编程&#xff1a;从入门到实践&#xff08;第3版&#xff09; ([美] 埃里克 • 马瑟斯&#xff08;Eric Matthes&#xff09;) 2023 (Z-Library)

【算法日志】动态规划刷题:01背包问题(day36)

代码随想录刷题60Day 前言 今天主要讨论背包问题中的01背包问题&#xff0c;这类问题的难点在于怎样对问题进行数学建模。一旦思考好问题的解决方式&#xff0c;剩下的步骤就比较简单了。 分割等和子集 本题可采用暴力回溯&#xff0c;但复杂度比较高&#xff0c;所以采取动态…

解决Win10运行软件程序提示【管理员已阻止你运行此应用】

一、问题描述 在Win10系统打开软件&#xff0c;弹窗提示【管理员已阻止你运行此应用】&#xff0c;如下图所示&#xff1a; 二、问题分析 是由于Windows系统的安全策略设置影响&#xff0c;保护你的电脑不被恶意程序破坏&#xff1b;只用修改系统的安全策略即可。 三、解决办法…

多目标优化算法知识点梳理

EA进化算法 MOEA多目标进化算法 1.MOEA的分类 1.1 按不同的进化机制分类 基于分解的MOEA&#xff1a;是比较早起所使用的方法&#xff1a;聚集函数法。将被优化的所有子目标组合或聚集为单个目标&#xff0c;从而将多目标优化问题转换为单目标优化问题。 基于支配关系的MOE…

ACME协议申请泛域名证书

ACME协议申请泛域名证书 注册域名 namecheap迁移DNS到cloudflare创建API令牌 区域选择域名生成证书安装证书参考 注册域名 namecheap 迁移DNS到cloudflare 创建API令牌 区域选择域名 复制此令牌&#xff0c;令牌只显示一次。 生成证书 export CF_Token"API令牌"a…

从零实现深度学习框架——Transformer从菜鸟到高手(二)

引言 &#x1f4a1;本文为&#x1f517;[从零实现深度学习框架]系列文章内部限免文章&#xff0c;更多限免文章见 &#x1f517;专栏目录。 本着“凡我不能创造的&#xff0c;我就不能理解”的思想&#xff0c;系列文章会基于纯Python和NumPy从零创建自己的类PyTorch深度学习框…

C++信息学奥赛2046:【例5.15】替换字母

这段代码的功能是对输入的字符串进行处理&#xff0c;将字符串中的字符 a 替换为字符 b 后输出结果。 #include<bits/stdc.h> using namespace std; int main() {string s; // 定义字符串变量s&#xff0c;用来存储输入的字符串char a, b; // 定义字符变量a和b&#xff…

港科夜闻|香港科大将与世界经济论坛合作举办大中华区首个全球青年领袖论坛领导力发展课程,与全球青年领袖共同探讨人工智能...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、​香港科大将与世界经济论坛合作举办大中华区首个"全球青年领袖论坛领导力发展课程",与全球青年领袖共同探讨人工智能。香港科大下月将与世界经济论坛合作&#xff0c;接待40位来自包括欧洲、拉丁美洲、非洲和…

采用typescript编写,实现ofd前端预览、验章

前言 浏览器内核已支持pdf文件的渲染&#xff0c;这极大的方便了pdf文件的阅读和推广。ofd文件作为国产板式标准&#xff0c;急需一套在浏览器中渲染方案。 本人研究ofd多年&#xff0c;分别采用qt、c# 开发了ofd阅读器。本人非前端开发人员&#xff0c;对js、typescript并不熟…

GNU-gcc编译选项-1

include目录 -I &#xff0c;比如: -I. -I ./Platform/include -I ./Platform/include/prototypes -I ./tpm/include -I ./tpm/include/prototypes -I ./Simulator/include -I ./Simulator/include/prototypes 编译选项 在GCC编译器中&#xff0c;-D是一个编译选项&…

京东CEO许冉的第一份成绩单 我们打分:80!

大数据产业创新服务媒体 ——聚焦数据 改变商业 2023年8月16日&#xff0c;京东发布了截至2023年6月30日的二季度财报及中期业绩。这也是京东集团CEO许冉由CFO升任CEO后交出的第一份成绩单。 在看成绩单之前&#xff0c;我们先回顾一下许冉上任时京东的状况。当时&#xff0c;…

群晖上用Docker安装OpenWrt

什么是 OpenWrt &#xff1f; OpenWrt 是一款基于 Linux 系统的开源路由器操作系统&#xff0c;可以将普通的 PC 或嵌入式设备转变成为一个功能强大的路由器。 老苏对没玩过的东西总是比较好奇&#xff0c;准备用 Docker 搭建一个 OpenWrt 来研究研究。 网上管这种玩法叫旁路路…

Xmake v2.8.2 发布,官方包仓库数量突破 1k

Xmake 是一个基于 Lua 的轻量级跨平台构建工具。 它非常的轻量&#xff0c;没有任何依赖&#xff0c;因为它内置了 Lua 运行时。 它使用 xmake.lua 维护项目构建&#xff0c;相比 makefile/CMakeLists.txt&#xff0c;配置语法更加简洁直观&#xff0c;对新手非常友好&#x…

ChatGPT只是玩具:生成式人工智能在不同行业的应用

源自&#xff1a;IT经理网 生成式人工智能的十一个行业用例 打开生成式 AI的正确姿势 声明:公众号转载的文章及图片出于非商业性的教育和科研目的供大家参考和探讨&#xff0c;并不意味着支持其观点或证实其内容的真实性。版权归原作者所有&#xff0c;如转载稿涉及版权等问题&…