手写Java线程池_超详细解说_绝对能运行_代码超详细注释

线程池

问题背景

只是单纯使用 new Thread(runnable).start(); 的方式创建线程, 将会导致严重的程序性能问题: 1.线程创建, 销毁需要消耗很大的系统资源; 2.虚拟机创建线程的数量是有限的; 2.线程调度切换也将使程序性能下降; 针对这些问题, 对线程数量进行管理, 有效地重复利用线程, 将会很好地提高程序性能.

线程池原理

使用队列创建一定数量的线程, 当有任务的时候, 使用队列中线程执行任务(如果任务过多, 就将其放入任务队列, 进入等待执行状态), 任务执行完就自动回收线程队列中的线程(任务过少或者任务数量小于线程数量, 超出的线程将会销毁, 做到线程队列具有伸缩性);

根据上面描述, 我们自己的线程池将具有一下特点:
1.内部使用队列来管理线程, 管理提交的任务.
2.控制线程数量, 做到线程队列具有良好的伸缩性.
3.当任务数过多, 或者任务队列已经饱和, 将使用任务拒绝策略, 告诉对应的任务提交者.
4.使用线程工厂定制线程队列中, 每个线程的名字, 状态, 是否为守护线程等等.

线程池类图结构

  1. 任务队列, 队列使用limit限制提交任务的大小, 实现RunnableQueue接口(RunnableQueue接口负责: 1.接收用户提交的任务; 2.获取任务队列中的任务; 3.查看任务队列大小), LinkedRunnableQueue实现RunnableQueue中的方法, 并且针对用户提交不同的任务以及线程池种类(ThreadPool)的不同, 决定是否执行拒绝策略(拒绝策略具有多个, 拒绝方式取决于用户自定义, 在线程池内部具有默认的拒绝策略实现);
    在这里插入图片描述

  2. 实现Runnable接口, 在run方法中获取RunnableQueue中的任务, 然后执行RunnQueue中的任务, InternalTask中的run方法是一个while循环循环结束条件取决于是否关闭该线程(关闭线程据需要设置flag变量, 当flage为false, 线程run方法结束, 自动结束生命), 而不是当前用户提交的任务是否执行完!!!; InternalTask主要是对RunnableQueue的一种封装; stop方法主要是设置线程flag(flag主要判断当前线程是否关闭)
    在这里插入图片描述

  3. 线程池原型:
    1.实现Runnable接口(此处注明, BasicThreadPool是继承Thread, 但是Thread内容太多了不能很好地在UML图中显示, 所以我就把他删除了, 只留下了实现Runnable接口), 因为线程池自身执行也需要一个线程, 所以继承Thread, 这样可以在BasicThreadPool的构造方法中执行start(), run方法中执行创建线程的操作(线程池内部执行任务的线程); 创建线程取决于线程池设置的最大线程数, 核心线程数, 初始化线程数, 用户提交的任务数;

    2.实现ThreadPool接口(该接口主要用于定义线程池的基本操作, 比如执行任务, 获取线程池的一些基本属性) ;

    3.内部具有2个内部类(ThreadTask负责对InternalTask进行封装, DefaultThreadFactory主要定义默认的线程创建方式), 不同的线程池中拥有不同的默认创建方式, 因此将线程创建方式设置为内部类;

    4.在BasicThreadPool中使用newThread方法创建线程(这些线程用于执行ThreadTask中的任务);

    5.线程池原型中具有2个队列, 第一个是刚才上面提的RunnQueue(负责执行的任务), 第二个是ThreadQueue(负责存储创建的每一个线程, 使用ArrayQueue实现, 这样很好地维护管理了线程, 做到资源重用)

    6.removeThread方法: 删除多余的线程, 当用户提交的任务数量小于线程池中创建的线程数量, 那么就删除一定数量的线程, 这样才不会浪费线程资源.

    7.在构造方法中设置基本属性, 以及当前线程池的拒绝策略.
    在这里插入图片描述

每个接口, 类的详细定义

ThreadPool(interface 定义线程池基本操作)

package com.concurrent.customthreadpool;/*** 线程池接口* @author regotto*/
public interface ThreadPool {/*** 执行提交的Runnable任务* @param runnable*/void execute(Runnable runnable);/*** 关闭线程池*/void shutdown();/*** 获得线程池初始化大小* @return initSize*/int getInitSize();/*** 获得线程池最大线程数* @return maxSize*/int getMaxSize();/*** 获取线程池核心线程数* @return coreSize*/int getCoreSize();/*** 获取线程池中用于缓存任务队列的大小* @return queueSize*/int getQueueSize();/*** 获取线程池中国活跃的线程数量* @return activeCount*/int getActiveCount();/*** 查看线程池是否shutdown* @return boolan*/boolean isShutdown();}

RunnableQueue(interface 任务队列)

package com.concurrent.customthreadpool;/*** 存放提交的Runnable, 使用BlockedQueue, 设置limit* @author regotto*/
public interface RunnableQueue {/*** 缓存提交到线程池中的任务* @param runnable*/void offer(Runnable runnable);/*** 从缓存中获取Runnable任务* 如果没有任务, 调用者线程挂起, 在某些特定的时候抛出中断异常* @throws InterruptedException* @return runnable*/Runnable take() throws InterruptedException;/*** 缓冲区大小* @return size*/int size();}

LinkedRunnableQueue(class 对RunnableQueue的封装, 用户提交任务, 线程执行任务, 此过程使用生产者-消费者模式实现)

package com.concurrent.customthreadpool;import java.util.LinkedList;/*** 线程池的内部线程队列, 缓冲区* @author regotto*/
public class LinkedRunnableQueue implements RunnableQueue{/*** limit: 限制当前runnableList中还能存放多少内容* denyPolicy: 拒绝策略* runnableList: 存放runnable的缓冲区* threadPool: 线程池*/private final int limit;private  final RunnableDenyPolicy denyPolicy;private final LinkedList<Runnable> runnableList = new LinkedList<>();private final ThreadPool threadPool;public LinkedRunnableQueue(int limit, RunnableDenyPolicy denyPolicy, ThreadPool threadPool){this.limit = limit;this.denyPolicy = denyPolicy;this.threadPool = threadPool;}@Overridepublic void offer(Runnable runnable) {synchronized (runnableList) {if (runnableList.size() >= limit) {//用户提交的任务大于限制条件, 执行对应的拒绝策略System.out.println(runnableList.size() + " >= " + limit + " execute deny policy");denyPolicy.reject(runnable, threadPool);} else {//添加任务到任务队列尾部, 有任务存在, 唤醒刚才wait的线程runnableList.addLast(runnable);runnableList.notifyAll();}}}@Overridepublic Runnable take() throws InterruptedException{synchronized (runnableList) {while (runnableList.isEmpty()) {try {//从RunnableQueue中取出任务, 如果任务为空, 使当前线程waitrunnableList.wait();} catch (InterruptedException e) {throw e;}}//移除任务缓冲区的第一个return runnableList.removeFirst();}}@Overridepublic int size() {synchronized (runnableList) {return runnableList.size();}}
}

InternalTask(class 对RunnableQueue中任务的执行)

package com.concurrent.customthreadpool;/*** 用于线程池内部, 获取runnableQueue中的runnable* @author regotto*/
public class InternalTask implements Runnable {private final RunnableQueue runnableQueue;private volatile boolean running = true;public InternalTask(RunnableQueue runnableQueue){this.runnableQueue = runnableQueue;}@Overridepublic void run() {//如果线程没有关闭, 就让该线程死循环, 处理每一个提交的任务while (running && !Thread.currentThread().isInterrupted()){try {//处于中断时候的线程不做处理//获取RunnableQueue中任务, 然后执行Runnable take = runnableQueue.take();System.out.println("runnableQueue.take(): " + take.toString());take.run();} catch (InterruptedException e) {running = false;break;}}}/*** 停止当前任务, 设置其running为false, 在shutdown中处理*/public void stop(){this.running = false;}}

RunnableDenyPolicy(interface 任务拒绝策略)

package com.concurrent.customthreadpool;/*** 当任务数提交超过缓冲区limit, 执行对应的任务拒绝策略* @author regotto*/
@FunctionalInterface
public interface RunnableDenyPolicy {/*** 对提交到threadPool的runnable是否执行reject* @param runnable* @param threadPool*/void reject(Runnable runnable, ThreadPool threadPool);/*** 该策略使用空方法直接丢弃任务*/class DiscardDenyPolicy implements RunnableDenyPolicy {@Overridepublic void reject(Runnable runnable, ThreadPool threadPool) {System.out.println(runnable + "不做处理");}}/*** 该策略抛出一个RunnableDenyException*/class AbortDenyPolicy implements RunnableDenyPolicy {@Overridepublic void reject(Runnable runnable, ThreadPool threadPool) {throw new RunnableDenyException("The" + runnable + "will be abort");}}/***该策略Runnable给提交者所在的线程中运行, 不加入到线程中*/class RunnerDenyPolicy implements RunnableDenyPolicy{@Overridepublic void reject(Runnable runnable, ThreadPool threadPool) {if (threadPool.isShutdown()) {runnable.run();}}}}

RunnableDenyException(class 处理RunnableDenyPolicy抛出的运行时异常)

package com.concurrent.customthreadpool;public class RunnableDenyException extends RuntimeException {public RunnableDenyException(String message) {super(message);}
}

ThreadFactory(interface 定义创建线程的接口)

package com.concurrent.customthreadpool;/*** 创建线程接口, 定制线程属于哪一个group, 是否为守护线程, 优先级, 名字等* @author regotto*/
public interface ThreadFactory {/*** 创建定制化线程* @param runnable* @return thread*/Thread createThread(Runnable runnable);}

BasicThreadPool(class 线程池的实现)

package com.concurrent.customthreadpool;import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;/*** 默认的自定义的线程池, 内部使用Queue进行维护* @author regotto*/
public class BasicThreadPool extends Thread implements ThreadPool{/**initSize: 初始化线程数* maxSize: 线程池最大线程数* coreSize: 线程核心数* activeCount: 当前活跃线程数* threadFactory: 线程工厂, 配置线程创建需要的参数* runnableQueue: 任务队列* isShutdown: 是否关闭线程池* threadQueue: 工作线程队列* DEFAULT_THREAD_FACTORY: 默认的线程工厂* keepAliveTime: 线程存活时间*/private final int initSize;private final int maxSize;private final int coreSize;private int activeCount;private final ThreadFactory threadFactory;private final RunnableQueue runnableQueue;private volatile boolean isShutdown = false;private Queue<ThreadTask> threadQueue = new ArrayDeque<>();private final static RunnableDenyPolicy DEFAULT_DENY_POLICY = new RunnableDenyPolicy.DiscardDenyPolicy();private final static ThreadFactory DEFAULT_THREAD_FACTORY = new DefaultThreadFactory();private final static long DEFAULT_KEEP_ALIVE_TIME = 10;private final long keepAliveTime;private final TimeUnit timeUnit;public BasicThreadPool(int initSize, int maxSize, int coreSize, int queueSize) {this(initSize, maxSize, coreSize, DEFAULT_THREAD_FACTORY, queueSize, DEFAULT_DENY_POLICY,DEFAULT_KEEP_ALIVE_TIME, TimeUnit.SECONDS);}public BasicThreadPool(int initSize, int maxSize, int coreSize, ThreadFactory threadFactory, int queueSize,RunnableDenyPolicy denyPolicy, long keepAliveTime, TimeUnit timeUnit) {this.initSize = initSize;this.maxSize = maxSize;this.coreSize = coreSize;this.threadFactory = threadFactory;this.runnableQueue = new LinkedRunnableQueue(queueSize, denyPolicy, this);this.keepAliveTime = keepAliveTime;this.timeUnit = timeUnit;this.init();}/*** 初始化线程池, 创建initThread*/private void init() {start();for (int i = 0; i < initSize; ++i) {newThread();}}/*** 创建线程添加到线程队列, 然后用该线程执行ThreadTask任务(层层封装, 封装用户提交的任务)*/private void newThread() {InternalTask internalTask = new InternalTask(runnableQueue);//使用自定义的线程工厂创建线程Thread thread = this.threadFactory.createThread(internalTask);ThreadTask threadTask = new ThreadTask(thread, internalTask);System.out.println(threadTask.thread.getName() + "被添加");//添加到线程队列threadQueue.offer(threadTask);this.activeCount++;//被添加后的线程执行startthread.start();}@Overridepublic void execute(Runnable runnable) {if (this.isShutdown) {throw new IllegalStateException("The thread pool id destroy");}//将用户提交的任务放到runnableQueue中, 等待线程队列中线程执行this.runnableQueue.offer(runnable);}private void removeThread() {//ArrayDeque的remove就是removeFirstThreadTask threadTask = threadQueue.remove();//设置当前线程flag, 在InternalTask中跳出循环自动结束线程生命threadTask.internalTask.stop();this.activeCount--;}@Overridepublic void run() {while (!isShutdown && !isInterrupted()) {try {timeUnit.sleep(keepAliveTime);} catch (InterruptedException e) {isShutdown = true;break;}synchronized (this) {if (isShutdown) {break;}//当前队列中有任务还没有处理, 且activeCount < coreSizeif (runnableQueue.size() > 0 && activeCount < coreSize) {//此处i曾写做i=0,导致多创建了一个线程,在没有任务的时候该线程一直保持wait//因为关闭pool,该线程没有add到threadQueue,导致Interrupt失败,最终导致线程一直运行中for (int i = initSize; i < coreSize; ++i) {newThread();}//防止后面的if判断创建线程数超过coreSize, 在coreSize还没有满的时候, 只执行当前的ifcontinue;}//当上面if中创建的线程数不足的时候, 就扩大线程池线程数, 直到maxSizeif (runnableQueue.size() > 0 && activeCount < maxSize) {for (int i = coreSize; i < maxSize; ++i) {newThread();}}//当没有任务, 但是activeCount线程数超出coreSize大小, 回收超出coreSize的线程if (runnableQueue.size() == 0 && activeCount > coreSize) {for (int i = coreSize; i < activeCount; ++i) {removeThread();}}}}}@Overridepublic void shutdown() {synchronized (this) {if (!isShutdown) {isShutdown = true;System.out.println("threadQueue size:" + threadQueue.size());threadQueue.forEach(threadTask -> {//调用internalTask中stop, 设置当前线程运行标志为falsethreadTask.internalTask.stop();//设置线程中断状态threadTask.thread.interrupt();System.out.println(threadTask.thread.getName());});System.out.println("threadQueue中线程已经关闭");//当前线程池自己也要关闭this.interrupt();}}}@Overridepublic int getInitSize() {if (isShutdown) {throw new IllegalStateException("The thread pool is destroy");}return this.initSize;}@Overridepublic int getMaxSize() {if (isShutdown) {throw new IllegalStateException("The thread pool is destroy");}return this.maxSize;}@Overridepublic int getCoreSize() {if (isShutdown) {throw new IllegalStateException("The thread pool is destroy");}return this.coreSize;}@Overridepublic int getQueueSize() {if (isShutdown) {throw new IllegalStateException("The thread pool is destroy");}return this.runnableQueue.size();}@Overridepublic int getActiveCount() {if (isShutdown) {throw new IllegalStateException("The thread pool is destroy");}return this.activeCount;}@Overridepublic boolean isShutdown() {return this.isShutdown;}/*** 内部类, 定义自己默认的线程工厂*/private static class DefaultThreadFactory implements ThreadFactory {private static final AtomicInteger GROUP_COUNTER = new AtomicInteger(1);//设置线程组private static final ThreadGroup GROUP = new ThreadGroup("MyThreadPool-" + GROUP_COUNTER.getAndDecrement());private static final AtomicInteger COUNTER = new AtomicInteger(0);@Overridepublic Thread createThread(Runnable runnable) {//创建定制化的线程return new Thread(GROUP, runnable, " thread-pool-" + COUNTER.getAndDecrement());}}/*** 封装InternalTask, 与每次创建的线程绑定在一起*/private class ThreadTask {Thread thread;InternalTask internalTask;ThreadTask(Thread thread, InternalTask internalTask){this.thread = thread;this.internalTask = internalTask;}}
}

ThreadPoolTest(class 测试类)

package com.concurrent.customthreadpool;import java.util.concurrent.TimeUnit;/*** 用于测试自定的线程池* @author regotto*/
public class ThreadPoolTest {public static void main(String[] args) {//初始化线程数:2, 最大线程数:6, 核心线程数:4, 任务队列大小:1000final BasicThreadPool threadPool = new BasicThreadPool(2, 6, 4, 1000);//创建20个任务提交进行执行for (int i = 0; i < 20; ++i) {threadPool.execute(() -> {try {TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "is running and done.");} catch (InterruptedException e) {e.printStackTrace();}});}//此处用于测试线程池运行时基本信息状态
//        for (int j = 0; j < 1000; ++j) {
//            System.out.println("getActiveCount: " + threadPool.getActiveCount());
//            System.out.println("getQueueSize: " + threadPool.getQueueSize());
//            System.out.println("getCoreSize: " + threadPool.getCoreSize());
//            System.out.println("getMaxSize: " + threadPool.getMaxSize());
//            try {
//                TimeUnit.SECONDS.sleep(3);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }try {TimeUnit.SECONDS.sleep(25);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("shutdown");//测试线程池shutdown功能threadPool.shutdown();}
}

总结

  1. 不要让BasicThreadPool继承Thread, 这样会导致直接调用BasicThreadPool中start方法, 导致线程池崩溃, 应该将Thread设置成组合, 隐藏start();
  2. 修改拒绝策略, 做到可以让用户自我实现拒绝方式, 此处拒绝策略可以使用策略模式;
  3. 使用工厂模式定义ThreadPool;
  4. 对每个参数进行合法验证;
  5. 在自己写的过程中, 出现参数传递出错的问题, 导致线程一直wait, 如果遇到这种问题, 使用jconsole工具来查看线程状态, 定位出错的位置, 这样能很快解决遇到的问题.

上面有错, 还请指出, 如果认为我写的还不错, 还请点个赞, 多多支持一下, O(∩_∩)O~~

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

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

相关文章

分享一个非常 nice 的工具

最近有个问题&#xff0c;我需要经常使用远程连接工具&#xff0c;原因很简单&#xff0c;我需要控制另外一台电脑&#xff0c;我刚开始使用的是 teamviewer 这个软件&#xff0c;刚开始用的时间是非常爽的&#xff0c;不过有一天他给我来了个提示&#xff0c;说我的软件被商用…

Java并发性和多线程介绍目录

http://ifeve.com/java-concurrency-thread-directory/转载于:https://www.cnblogs.com/hanfeihanfei/p/6840359.html

js 正则是否包含某些字符串_JS 判断字符串中是否包含某个字符串(方法总结)...

我是想在js中判断字符串是否包含某个中文&#xff0c;将方法记录起来&#xff0c;这些方法也适用于数字、字母。实践是检验真理的唯一标准&#xff0c;还是要多多测试啊。String对象的方法方法一&#xff1a;indexOf()var groupName"小白A组";alert(groupName.indexO…

推荐周立功先生的一本书

1. 这篇文章主要是推荐周工的一本书&#xff0c;大家在学习嵌入式的时候&#xff0c;很多人不明白嵌入式系统和单片机的区别&#xff0c;又感觉自己对嵌入式有所了解&#xff0c;知道什么是嵌入式&#xff0c;文章里的很多见解我觉得对很多人都非常有帮助&#xff0c;今晚上周工…

图管够!灌篮高手、女儿国…阿里日_这帮程序员太会玩了!

5月10日是阿里一年一度的阿里日&#xff0c;这对阿里人来说&#xff0c;是个非常特别的日子。 那什么是阿里日呢&#xff1f;看看官方介绍&#xff1a; 它起源于2005年4月20日&#xff0c;是为了纪念2003年5月的“非典”时期阿里人的激情和信念。因此阿里巴巴决定&#xff0c;今…

复习Object类_日期时间类_System类_StringBuilder_包装类以及各类的细节

Object类_日期时间类_System类_StringBuilder_包装类以及各类的细节 主要内容 Object类Date类DateFormat类Calendar类System类StringBuilder类包装类 第一章 Object类 1.1 概述 java.lang.Object类是Java语言中的根类&#xff0c;即所有类的父类。它中描述的所有方法子类都…

sql 整改措施 注入_SQL注入的漏洞及解决方案

一、sql注入漏洞1. SQL注入漏洞SQL注入攻击(SQL Injection)&#xff0c;简称为注入攻击&#xff0c;SQL注入&#xff0c;被广泛用于非法获取网站控制权。这是在应用程序的数据库层中发生的安全漏洞。在设计程序中&#xff0c;忽略了对输入字符串中包含的SQL命令的检查&#xff…

check-lxdialog.sh: line 3: $'\r': command not found

问题 make all make[1]: Entering directory /cygdrive/d/AiThinkerIDE_V0.5/cygwin/home/aithinker/project/esp-idf/tools/kconfig /cygdrive/d/AiThinkerIDE_V0.5/cygwin/home/aithinker/project/esp-idf/tools/kconfig/lxdialog/check-lxdialog.sh: line 3: $\r: command…

Qt setMargin()和setSpacing() 的含义

mainLayoutnewQVBoxLayout(this); mainLayout->setMargin(30); //表示控件与窗体的左右边距 mainLayout->setSpacing(40); //表示各个控件之间的上下间距转载于:https://www.cnblogs.com/invisible2/p/6843159.html

复习Collection_迭代器使用细节_泛型_综合案例

Collection_迭代器使用细节_泛型_综合案例 主要内容 Collection集合迭代器增强for泛型 第一章 Collection集合 1.1 集合概述 集合&#xff1a;集合是java中提供的一种容器&#xff0c;可以用来存储多个数据。 集合和数组既然都是容器&#xff0c;他们之间的区别: 数组的…

esp32 coredump分析

写文章的原因 网上很多人写了 esp32 的coredump 分析&#xff0c;好吧&#xff0c;我看了下&#xff0c;都是从网上摘抄的&#xff0c;根本跑不通&#xff0c;我想写一个给大家有用的。 官网文章 https://esp-idf-zh.readthedocs.io/zh_CN/latest/api-guides/core_dump.html…

iphone mac地址是否随机_iOS8随机MAC地址功能:需要满足前提要求

苹果今年WWDC大会上透露过 iOS8 当中将具备一个连接 Wi-Fi 时随机生成 MAC 伪地址的功能&#xff0c;用来防范一些提供 Wi-Fi 的不法商家获取用户的隐私数据挪作它用。近日&#xff0c;一家名为 AirTight Networks 的移动安全技术研究公司发布了一份针对该功能的调查报告&#…

Kubernetes dashboard集成heapster

图形化展示度量指标的实现需要集成k8s的另外一个Addons组件&#xff1a; Heapster 。 Heapster原生支持K8s(v1.0.6及以后版本)和 CoreOS &#xff0c;并且支持多种存储后端&#xff0c;比如&#xff1a; InfluxDB 、 ElasticSearch 、 Kafka 。 下载源包和images文件 下载地址h…

复习JavaFile类_递归_综合案例

复习File类_递归 主要内容 File类递归 第一章 File类 1.1 概述 java.io.File 类是文件和目录路径名的抽象表示&#xff0c;主要用于文件和目录的创建、查找和删除等操作, 这里需要注意一点File不能用于对文件内容进行操作, 对文件内容进行操作需要使用FileInputStream与Fi…

ESP32 分区介绍

软件设计分区表 如上图是正常运行需要的分区&#xff0c;一个系统的正常运行是需要这么多分区的。 分区表&#xff0c;我们软件默认配置的分区表如下 # Espressif ESP32 Partition Table # Name, Type, SubType, Offset, Size, Flags nvs,data,nvs,0x9000,16K, otadata,data,…

python坦克大战_Life is short,you need Python——Python实现坦克大战(一)

先展示一下效果搓搓小手手&#xff0c;坦克大战即将开始……https://www.zhihu.com/video/1140743290784817152一、游戏引擎的安装安装方式有两种&#xff1a;1.pip安装 windows R --> cmd --> pip install pygame2.pycharm安装 File -->setting -->project -->…

【java设计模式】【行为模式Behavioral Pattern】迭代器模式Iterator Pattern

1 package com.tn.pattern;2 3 public class Client {4 public static void main(String[] args) {5 Object[] objs{"飞","雪","连","天","射","白","鹿","笑","书",&…

复习Java字节流_字符流使用及案例

字节流_字符流 主要内容 IO流字节流字符流异常处理Properties 第一章 IO概述 1.1 什么是IO 生活中&#xff0c;你肯定经历过这样的场景。当你编辑一个文本文件&#xff0c;忘记了ctrls &#xff0c;可能文件就白白编辑了。当你电脑上插入一个U盘&#xff0c;可以把一个视频…

ESP32 coredump 分析

1. 上次写了一个 ESP32 入门&#xff0c;我想有必要再写这篇文章&#xff0c;这次主要是分析 coredump 的&#xff0c;这就像 Android 和 Linux 系统的死机分析&#xff0c;有意思&#xff0c;也有难度。我们写代码的时候&#xff0c;不可避免的会遇到一些 coredump 的问题&am…

pycharm变量存_pycharm不为人知的功能们

一、远程调试这个功能真的好用&#xff0c;现改现测参考的博客写的挺详细的&#xff0c;补充一下个人的注意事项1.要选sftp连接&#xff0c;否则连接不上(没有具体查找原因&#xff0c;sftp挺好用的就用着)&#xff0c;端口是222.Connection中的Root path指定根目录&#xff0c…