【线程】Java多线程代码案例(2)

【线程】Java多线程代码案例(2)

      • 一、定时器的实现
        • 1.1Java标准库定时器
        • 1.2 定时器的实现
      • 二、线程池的实现
        • 2.1 线程池
        • 2.2 Java标准库中的线程池
        • 2.3 线程池的实现

一、定时器的实现

1.1Java标准库定时器
import java.util.Timer;
import java.util.TimerTask;

public class ThreadDemo5 {
    public static void main(String[] args) throws InterruptedException {
        Timer timer =new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("1000");
            }
        },1000);

        System.out.println("hello main");
    }
}
1.2 定时器的实现

首先考虑,定时器中都需要都需要实现哪些元素呢?

  1. 需要有一个线程,负责掐时间
  2. 还需要有一个队列,能够保存所有添加进来的任务,这个队列要带有阻塞功能
    因为这个任务,要先执行时间小的,再执行时间大的。此处我们可以实现一个优先级队列。那么时间小的任务就始终排在第一位,我们只需要关注队首元素是否到时间,如果队首没有到时间,那么后续其他元素,也一定没有到时间。

首先定义任务类,包含要执行的任务和时间

class MyTimerTask implements Comparable<MyTimerTask>{
    //执行时间
    private long time;
    //持有一个Runnable
    private Runnable runnable;

    public MyTimerTask(Runnable runnable,long delay){
        this.time=System.currentTimeMillis()+delay;
        this.runnable=runnable;
    }

    //实际要执行的任务
    public void run(){
        runnable.run();
    }

    public long getTime() {
        return time;
    }

    @Override
    //因为要加入优先级队列,必须能比较
    public int compareTo(MyTimerTask o) {
        return (int)(this.time-o.time);
    }
}

定义计时器

class MyTimer{
	//持有一个线程负责计时
	private Thread t=null;
	//优先级队列
	private PriorityQueue<MyTimerTask> queue =new PriorityQueue<>();
	//前面实现阻塞队列的逻辑,加锁
	private Object locker =new Object();
	//添加任务
	public void schedule(Runnable runnable,long delay){}
	//构造方法
	//注意执行任务并不需要我们写一个方法在main()函数中调用
	//这个是到时间自动执行的
	public MyTimer(){
		t=new Thread(()->{
			while(true){
				//到时间执行任务的逻辑
			}
		});
	}
}

那接下来我们就来分别实现这里的schedule方法和构造函数中执行任务的逻辑:
schedule():

public void schedule(Runnable runnable,long delay){
	//入队列和出队列都需要打包成“原子性”的操作,加锁实现
	synchronized(locker){
		//新建任务
		MyTimerTask task=new MyTimerTask(runnable,delay);
		//加入队列
		queue.offer(task);
		//参考前面阻塞队列的实现,当队列为空时wait(),加入元素后notify()
		locker.notify();
	}
}

构造方法:

public MyTimer(){
	t=new Thread(()->{
		while(true){
			try{
				synchronized(locker){
				while(queue.isEmpty()){
					//阻塞直到加入新的任务后被notify()唤醒
					locker.wait();
				}
				//查看队首元素
				//peek不会将元素弹出
				MyTimerTask task=queue.peek;
				if(System.currentTimeMillis() >= task.getTime()){
					queue.poll();
					task.run();
				}else{
					//阻塞,释放锁(允许继续添加任务)
					//设置最大阻塞时间,阻塞到这个时间到了
					locker.wait(task.getTime()-System.currentTimeMillis());
				}
			}catch (InterruptedException e) {
               break;
            }
		}
	});	
	//启动线程
	t.start();
}

写到这里,就大功告成了,我们在main()函数中试验看一下运行结果:

public class ThreadDemo5{
    public static void main(String[] args) {
        MyTimer timer=new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(3000);
            }
        },3000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(2000);
            }
        },2000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(1000);
            }
        },1000);
        
        Thread.sleep(4000);
        timer.cancel();
    }
}

这里我们再加一个方法,我们希望任务执行完成后,能够主动结束这个线程:

public void cancel(){
	t.interrupt();
}

这里需要考虑线程被提前唤醒抛出的异常,因此在构造方法中将捕获异常的操作改为break;
在这里插入图片描述
计时器完整代码:

import java.util.PriorityQueue;

class MyTimerTask implements Comparable<MyTimerTask>{
    //执行时间
    private long time;
    //持有一个Runnable
    private Runnable runnable;

    public MyTimerTask(Runnable runnable,long delay){
        this.time=System.currentTimeMillis()+delay;
        this.runnable=runnable;
    }

    //实际要执行的任务
    public void run(){
        runnable.run();
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time-o.time);
    }
}

class MyTimer{
    //持有一个线程负责计时
    private Thread t=null;
    //任务队列——>优先级队列
    private PriorityQueue<MyTimerTask> queue =new PriorityQueue<>();
    //锁对象
    private Object locker=new Object();

    public void schedule(Runnable runnable,long delay){
        synchronized (locker) {
            //新建任务
            MyTimerTask task = new MyTimerTask(runnable, delay);
            //加入队列
            queue.offer(task);
            locker.notify();
        }
    }
    public void cancel(){
        t.interrupt();
    }
    public MyTimer(){
        t = new Thread(() -> {
            while (true) {
                try {
                    synchronized (locker) {
                        while (queue.isEmpty()) {
                            //阻塞
                            locker.wait();
                        }
                        //查看队首元素
                        MyTimerTask task = queue.peek();
                        if (System.currentTimeMillis() >= task.getTime()) {
                            queue.poll();
                            task.run();
                        } else {
                            //阻塞
                            locker.wait(task.getTime()-System.currentTimeMillis());
                        }
                    }
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        t.start();
    }
}
public class ThreadDemo5{
    public static void main(String[] args) throws InterruptedException {
        MyTimer timer=new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(3000);
            }
        },3000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(2000);
            }
        },2000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(1000);
            }
        },1000);

        Thread.sleep(4000);
        timer.cancel();
    }
}

二、线程池的实现

2.1 线程池

最初我们提到线程这个概念,其实是一个“轻量级进程”。他的优势在于无需频繁地向系统申请/释放内存,提高了效率。但是随着线程的增多,频繁地创建/销毁线程也是一个很大的开销。解决方案有两种:

  1. 轻量级线程(协程),Java 21中引入了虚拟线程,就是这个东西。协程主要在Go语言中有较好的运用。
  2. 其次就是引入线程池的概念,无需频繁创建/销毁线程,而是一次性的创建好许多线程,每次直接取用,用完了放回线程池中。

为什么从线程池里取线程,会比从系统中申请更高效。
本质上在于去线程池里取线程,是一个用户态的操作,而向系统申请线程是一个内核态的操作。
在这里插入图片描述
还是以去银行取钱为例,向系统申请线程,就相当于找工作人员,在柜台取钱(工作人员收到请求后可能不会立即给你取钱),相对低效;而从线程池中取用线程,则相当于从ATM机里面取钱(从ATM机里面取钱是可以立即取到的),相对高效。

2.2 Java标准库中的线程池

在这里插入图片描述
这里我们可以细看一下这里的参数:

  1. corePoolSize(核心线程数)
    一个线程池里,最少要有多少个线程,相当于正式工,不会被销毁。
  2. maximumPoolSize(最大线程数)
    一个线程池里,最多要有多少个线程,相当于临时工,一段时间不干活就被销毁。
  3. keepAliveTime
    临时工允许的空闲时间,超过这个时间,就被销毁。
  4. unit
    keepAliveTime的时间单位
  5. BlockingQueue workQueue
    传递任务的阻塞队列
  6. threadFactory
    创建线程的工厂,参与具体的创建线程的工作。
    这里涉及到工厂模式,试想这样的代码能否运行:
class Point{
	//笛卡尔坐标系
	public point(double x,double y){...}
	//极坐标系
	public point(double r,double a){...}
}

像这样的代码是无法运行的。因为他们具有相同的方法名和参数列表,无法完成重载。那如果确实想完成这样的操作,该怎么做呢?

class Point{
	public static Point makePointByXY(double x, double y){
		Point p=new Point();
		p.setX(x);
		p.setY(y);
		return p;
	}
	public static Point makePointByRA(double r,double a){
		Point p=new Point();
		p.setR(r);
		p.setA(a);
		return p;
	}
}
Point p=Point.makePointByXY(x,y);
Point p=Point.makePointByRA(r,a);

总的来说,通过静态方法封装new操作,在方法内部设定不同的属性完成对象的初始化,构造对象的过程,就是工厂模式。

  1. RejectedExecutionHandler handler
    拒绝策略。如果这里的阻塞队列满了,此时要添加任务,就需要有一个应对策略。
策略含义备注
AbortPolicy()超过负荷,抛出异常所有任务都不做了
CallerRunsPolicy()调用者负责处理多出来的任务所有任务都要做,新加的任务由添加任务的线程做
DiscardOldestPolicy()丢弃队列中最老的任务不做最老的任务
DiscardPolicy()丢弃新来的任务不做最新的任务

由于ThreadPoolExecutor本身用起来比较复杂,因此标准库还提供了一个版本,把ThreadPoolExecutor给封装了一下。Executors 工厂类,通过这个类来创建不同的线程池对象(内部把ThreadPoolExecutor创建好了并且设置了不同的参数)
大致有这么几种方法:

方法用途
newScheduleThreadExecutor()创建定时器线程,延时执行任务
newSingleThreadExecutor()只包含单个线程的线程池
newCachedThreadExecutor()线程数目能够动态扩容
newFixedThreadExecutor()线程数目固定
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadDemo6 {
    public static void main(String[] args) {
        ExecutorService service=Executors.newFixedThreadPool(4);
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
    }
}

那么,对于一个多线程任务,创建多少个线程合适呢?

  1. 如果任务都是CPU密集型的(大部分时间在CPU上执行),此时线程数不应超过逻辑核心数;
  2. 如果任务都是IO密集型的(大部分时间在等待IO),此时线程数可以远远超过逻辑核心数;
  3. 由于实际的任务都是两种任务混合型的,一般通过实验的方式来得到最合适的线程数。
2.3 线程池的实现

我们可以实现一个简单的线程池(固定线程数目的线程池),要完成以下任务:

  1. 提供构造方法,指定创建多少个线程;
  2. 在构造方法中,创建线程;
  3. 有一个阻塞队列,能够执行要执行的任务;
  4. 提供submit()方法,添加新的任务
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class MyThreadPoolExecutor{
    private List<Thread> threadList=new ArrayList<>();

    //阻塞队列
    private BlockingQueue<Runnable> queue=new ArrayBlockingQueue<>(10);

    public MyThreadPoolExecutor(int n){
        for(int i=0;i<n;i++){
            Thread t=new Thread(()-> {
                while (true) {
                    try {
                        //take操作也带有阻塞
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            t.start();
            threadList.add(t);
        }
    }

    public void submit(Runnable runnable) throws InterruptedException {
        //put操作带有阻塞功能
        queue.put(runnable);
    }
}
public class ThreadDemo6 {
    public static void main(String[] args) throws InterruptedException {
       MyThreadPoolExecutor executor=new MyThreadPoolExecutor(4);
       for(int i=0;i<1000;i++){
           int n=i;
           executor.submit(new Runnable() {
               @Override
               public void run() {
                   System.out.println("执行任务:"+n+",当前线程:"+
                           Thread.currentThread().getName());
               }
           });
       }
    }
}

运行结果
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pyspark实现基于协同过滤的电影推荐系统

最近在学一门大数据的课&#xff0c;课程要求很开放&#xff0c;任意做一个大数据相关的项目即可&#xff0c;不知道为什么我就想到推荐算法&#xff0c;一直到着手要做之前还没有新的更好的来代替&#xff0c;那就这个吧。 推荐算法 推荐算法的发展由来已久&#xff0c;但和…

log4c库使用

log4c库 介绍 log4c 是一个 C 语言实现的日志库&#xff0c;它是 log4j&#xff08;Java 语言的日志框架&#xff09;的 C 语言版本&#xff0c;旨在为 C 语言应用程序提供灵活、可配置的日志功能。log4c 提供了丰富的日志功能&#xff0c;包括日志级别、日志输出目标、日志格…

Llmcad: Fast and scalable on-device large language model inference

题目&#xff1a;Llmcad: Fast and scalable on-device large language model inference 发表于2023.09 链接&#xff1a;https://arxiv.org/pdf/2309.04255 声称是第一篇speculative decoding边缘设备的论文&#xff08;不一定是绝对的第一篇&#xff09;&#xff0c;不开源…

Leetcode 每日一题 36.有效的数独

目录 问题描述 输入输出格式 算法思路 过题图片 代码实现 题目链接 复杂度分析 问题描述 给定一个 9x9 的数独棋盘&#xff0c;我们需要判断棋盘上已填入的数字是否有效。根据数独的规则&#xff0c;有效性需要满足以下条件&#xff1a; 数字 1-9 在每一行只能出现一次…

深入浅出UART驱动开发与调试:从基础调试到虚拟驱动实现

往期内容 本专栏往期内容&#xff1a;Uart子系统 UART串口硬件介绍深入理解TTY体系&#xff1a;设备节点与驱动程序框架详解Linux串口应用编程&#xff1a;从UART到GPS模块及字符设备驱动 解UART 子系统&#xff1a;Linux Kernel 4.9.88 中的核心结构体与设计详解IMX 平台UART驱…

韦东山stm32hal库--定时器喂狗模型按键消抖原理+实操详细步骤

一.定时器按键消抖的原理: 按键消抖的原因: 当我们按下按键的后, 端口从高电平变成低电平, 理想的情况是, 按下, 只发生一次中断, 中断程序只记录一个数据. 但是我们使用的是金属弹片, 实际的情况就是如上图所示, 可能会发生多次中断,难道我们要记录3/4次数据吗? 答:按键按下…

Web前端学习_CSS盒子模型

content padding border margin <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>CSS盒子模型</title><style></style> </head> <body> <div class"demo&quo…

将自定义 AWS S3 快照存储库连接到 Elastic Cloud

作者&#xff1a;来自 Elastic Annie Hansen, Stef Nestor 在本博客中&#xff0c;我们将介绍如何通过 Elasticsearch 的快照将我们已提交的集群数据备份到 AWS S3 存储桶中。在 Elastic Cloud&#xff08;企业版&#xff09;中&#xff0c;Elastic 在其 found-snapshots 存储…

部署 Prometheus

实验环境 IP地址服务192.168.88.10Prometheus服务端, Consul, Grafana, Node-Exporter192.168.88.77MySQL, Node-Exporter192.168.88.30Nginx&#xff0c;Node-Exporter 一、Prometheus Server 端安装和相关配置 【Prometheus1.sh】 &#xff08;1&#xff09;上传 prometh…

第29天 MCU入门

目录 MCU介绍 MCU的组成与作用 电子产品项目开发流程 硬件开发流程 常用元器件初步了解 硬件原理图与PCB板 常见电源符号和名称 电阻 电阻的分类 贴片电阻的封装说明&#xff1a; 色环电阻的计算 贴片电阻阻值计算 上拉电阻与下拉电阻 电容 电容的读数 二极管 LED 灯电路 钳位作…

汽车免拆诊断案例 | 2017款捷豹F-PACE车发动机偶尔怠速不稳

故障现象  一辆2017款捷豹F-PACE车&#xff0c;搭载2.0 L GTDi发动机&#xff0c;累计行驶里程约为16万km。车主反映&#xff0c;车辆组合仪表上发动机故障灯点亮&#xff08;图1&#xff09;&#xff0c;且发动机偶尔怠速不稳。 图1 发动机故障灯点亮 故障诊断 接车后试车…

Cobalt Strike 4.8 用户指南-第十一节 C2扩展

11.1、概述 Beacon 的 HTTP 指标由 Malleable Command and Control &#xff08;Malleable C2&#xff09; 配置文件控制。Malleable C2 配置文件是一个简单的程序&#xff0c;它指定如何转换数据并将其存储在事务中。转换和存储数据的同一程序&#xff08;向后解释&#xff0…

上传镜像docker hub登不上和docker desktop的etx4.vhdx占用空间很大等解决办法

平时使用docker一般都在Linux服务器上&#xff0c;但这次需要将镜像上传到docker hub上&#xff0c;但是服务器上一直无法登录本人的账号&#xff0c;&#xff08;这里的问题应该docker 网络配置中没有开代理的问题&#xff0c;因服务器上有其他用户使用&#xff0c;不可能直接…

[BUUCTF]ciscn_2019_n_8

题目 解题 先连接看看有什么信息 返回whats your name 没有其他信息 看程序基本信息 32位 拉到ida32查看 打开发现如下 由上述代码可知&#xff0c;需要将数组0-12装满&#xff0c;装什么都可以&#xff0c;将var[13]17才能执行system("/bin/sh") payload fro…

orangepi _全志H616

1. 全志H616简介 1.1. 为什么学&#xff1a; 学习目标依然是Linux系统&#xff0c;平台是ARM架构 蜂巢快递柜&#xff0c;配送机器人&#xff0c;这些应用场景用C51,STM32单片机无法实现 &#xff08;UI界面&#xff0c;提高用户的体验感&#xff09;第三方介入库的局限性&a…

信息收集之网站架构类型和目录扫描(一)

目录 前言 1.查看域名的基本信息 2.常见的网站架构类型 3.目录扫描 前言 最近也是到了期末周了,比较空闲,把信息收集的一些方式和思路简单总结一下,顺便学习一些新的工具和一些未接触到的知识面. 1.查看域名的基本信息 新学了一个工具,kali中的whois也可以进行查看,当然在…

消息中间件用途介绍

1. 解耦&#xff08;Decoupling&#xff09;&#xff1a; • 消息中间件能够将消息的生产者&#xff08;Producer&#xff09;和消费者&#xff08;Consumer&#xff09;分离开来&#xff0c;使它们不必直接相互依赖。这种设计降低了系统的耦合度&#xff0c;提升了系统的可扩展…

【Maven】Nexus私服

6. Maven的私服 6.1 什么是私服 Maven 私服是一种特殊的远程仓库&#xff0c;它是架设在局域网内的仓库服务&#xff0c;用来代理位于外部的远程仓库&#xff08;中央仓库、其他远程公共仓库&#xff09;。一些无法从外部仓库下载到的构件&#xff0c;如项目组其他人员开发的…

学习ASP.NET Core的身份认证(基于Cookie的身份认证3)

用户通过验证后调用HttpContext.SignInAsync函数将用户的身份信息保存在认证Cookie中,以便后续的请求可以验证用户的身份,该函数原型如下所示&#xff0c;其中properties参数的主要属性已在前篇文章中学习&#xff0c;本文学习scheme和principal的意义及用法。 public static …

【mac】终端左边太长处理,自定义显示名称(terminal路径显示特别长)

1、打开终端 2、步骤 &#xff08;1&#xff09;修改~/.zshrc文件 nano ~/.zshrc&#xff08;2&#xff09;添加或修改PS1&#xff0c;我是自定义了名字为“macminiPro” export PS1"macminiPro$ "&#xff08;3&#xff09;使用 nano: Ctrl o &#xff08;字母…