Tomcat源码:ProtocolHandler与Endpoint

news2024/11/17 16:39:40

参考资料:

《Tomcat源码解析系列(十一)ProtocolHandler》

《Tomcat源码解析系列(十二)NioEndpoint》

前文:

《Tomcat源码:启动类Bootstrap与Catalina的加载》

《Tomcat源码:容器的生命周期管理与事件监听》

《Tomcat源码:StandardServer与StandardService》

《Tomcat源码:Container接口》 

《Tomcat源码:StandardEngine、StandardHost、StandardContext、StandardWrapper》

 《Tomcat源码:Pipeline与Valve》

《Tomcat源码:连接器与Executor、Connector》

前言

        前文中我们介绍了连接器与其入口Connector以及线程池Executor,而在Connector中则是通过protocolHandler的init与start启动了整个连接器,本文我们就来介绍下protocolHandler与其关键组件Endpoint的内容,以及线程池Executor是如何发挥其作用的。

目录

前言

一、ProtocolHandler

        1、构造方法

        1.1、Http11NioProtocol与其抽象父类

        1.2、 AbstractProtocol与成员变量的赋值       

        2、生命周期方法        

        2.1、init方法

        2.2、start方法 

二、Endpoint

        1、成员变量设置

        2、init方法

        2.1、bindOnInit

        2.2、initServerSocket 

        3、start方法

        3.1、startInternal

        3.2、getExecutor

        3.3、initializeConnectionLatch  

        3.4、 Poller与Acceptor


一、ProtocolHandler

        1、构造方法

        1.1、Http11NioProtocol与其抽象父类

        在 Connector 的构造方法中,用反射创建了一个 Http11NioProtocol 对象,下面我们进入Http11NioProtocol的源码进行分析。

        Http11NioProtocol构造方法里,第一步创建了NioEndpoint对象,该对象即我们上文中介绍的Endpoint组件,用来实现TCP/IP协议的(《Tomcat源码:连接器与Executor、Connector》)。第二步则是向上调用了父类AbstractHttp11JsseProtocol的构造方法。

public class Http11NioProtocol extends AbstractHttp11JsseProtocol<NioChannel> {
    public Http11NioProtocol() {
        this(new NioEndpoint());
    }
    public Http11NioProtocol(NioEndpoint endpoint) {
        super(endpoint);
    }
}

        回溯到抽象父类AbstractHttp11JsseProtocol和AbstractHttp11Protocol中的构造方法中发现继续往上调用。需要注意的是这里AbstractHttp11Protocol中为连接设置了超时时间,而这个超时时间正是通过endpoint对象实现的(endpoint实际上就是限制了传输层的时间)

public abstract class AbstractHttp11JsseProtocol<S> extends AbstractHttp11Protocol<S> {
    public AbstractHttp11JsseProtocol(AbstractJsseEndpoint<S, ?> endpoint) {
        super(endpoint);
    }
}

public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {
    public AbstractHttp11Protocol(AbstractEndpoint<S, ?> endpoint) {
        super(endpoint);
        // 设置超时时间
        setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    }
    public void setConnectionTimeout(int timeout) {
        endpoint.setConnectionTimeout(timeout);
    }
}

        1.2、 AbstractProtocol与成员变量的赋值       

        最后我们来到了抽象类 AbstractProtocol中,可以看到该类中有2个成员变量endpoint与handler,这里会将传过来的NioEndpoint对象赋值给endpoint。然后创建一个ConnectionHandler对象,赋值给handler,同时设置为NioEndpoint对象的属性,最后设置了Endpoint 对象的两个属性,setConnectionLinger 和 setTcpNoDelay 方法就是调用 Endpoint 对象的相关方法。

        这里从注释里可以看出endpoint是用来处理低级网络I/O的,且必须要和ProtocolHandler的具体实现相匹配,比如使用了NIO模式的ProtocolHandler,那么也需要NIO模式的Endpoint。

public abstract class AbstractProtocol<S> implements ProtocolHandler, MBeanRegistration {

    /**
     * Endpoint that provides low-level network I/O - must be matched to the ProtocolHandler implementation
     * (ProtocolHandler using NIO, requires NIO Endpoint etc.).
     */
     private final AbstractEndpoint<S, ?> endpoint;
     private Handler<S> handler;
     public AbstractProtocol(AbstractEndpoint<S, ?> endpoint) {
        this.endpoint = endpoint;
        ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
        setHandler(cHandler);
        getEndpoint().setHandler(cHandler);
        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
    }
    protected void setHandler(Handler<S> handler) {
        this.handler = handler;
    }

    protected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S> {
        private final AbstractProtocol<S> proto;
        public ConnectionHandler(AbstractProtocol<S> proto) {
            this.proto = proto;
        }
}

        2、生命周期方法        

        ProtocolHandler 的init 和 start 方法由子类AbstractProtocol与AbstractHttp11Protocol 实现/重载了。

        2.1、init方法

        AbstractHttp11Protocol中init方法有2部分是处理升级协议设置的,如果涉及到websocket或者HTTP2可能涉及,我们这里还是以主流的HTTP1.1的流程来分析,因此跳过下直接来看核心逻辑super.init(),调用抽象父类AbstractProtocol中的实现。

public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {
    public void init() throws Exception {
        // 升级协议设置
        for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
            configureUpgradeProtocol(upgradeProtocol);
        }
        super.init();
        // Http2相关配置
        for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
            if (upgradeProtocol instanceof Http2Protocol) {
                ((Http2Protocol) upgradeProtocol).setHttp11Protocol(this);
            }
        }
    }
}

        来到AbstractProtocol里中的init方法,我们发现其实就是调用成员endpoint的init方法。

public abstract class AbstractProtocol<S> implements ProtocolHandler, MBeanRegistration {
    public void init() throws Exception {
        // ...
        String endpointName = getName();
        endpoint.setName(endpointName.substring(1, endpointName.length() - 1));
        endpoint.setDomain(domain);
        endpoint.init();
    }
}

        2.2、start方法 

        start方法则是由AbstractProtocol类实现的,这里有2块重要的部分一个是调用endpoint的start方法,另一块则是启动异步线程调用AsyncTimeout的run方法。

public abstract class AbstractProtocol<S> implements ProtocolHandler, MBeanRegistration {
    // The timeout thread.
    private AsyncTimeout asyncTimeout = null;
    public void start() throws Exception {
        // ...
        endpoint.start();
        // Start timeout thread
        asyncTimeout = new AsyncTimeout();
        Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
        int priority = endpoint.getThreadPriority();
        if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
            priority = Thread.NORM_PRIORITY;
        }
        timeoutThread.setPriority(priority);
        timeoutThread.setDaemon(true);
        timeoutThread.start();
    }
}

        AsyncTimeout 是AbstractProtocol的内部类,结合上面的注释我们可以了解到这是一个用来处理超时线程的类,可以看出 startAsyncTimeout 方法的作用是定期调用 waitingProcessors 里的 Processor 对象的 timeoutAsync 方法来处理一些超时的请求。

public abstract class AbstractProtocol<S> implements ProtocolHandler, MBeanRegistration { 

   private final Set<Processor> waitingProcessors = Collections
            .newSetFromMap(new ConcurrentHashMap<Processor, Boolean>());
   protected class AsyncTimeout implements Runnable {
        private volatile boolean asyncTimeoutRunning = true;
        @Override
        public void run() {
            while (asyncTimeoutRunning) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
                long now = System.currentTimeMillis();
                for (Processor processor : waitingProcessors) {
                    processor.timeoutAsync(now);
                }
            }
        }
    }
}

        到这里我们大致了解了ProtocolHandler的初始化与init、start方法的基本内容,可以了解到ProtocolHandler内包含了endpoint、handler与adapter这几个组件,而生命周期方法实际上就是调用了endpoint的init与start方法。

 

二、Endpoint

        1、成员变量设置

        在上文中,ProtocolHandler的构造方法最终创建了一个NioEndpoint对象和一个ConnectionHandler对象,并将ConnectionHandler赋值给了NioEndpoint的成员变量handlr。

        从源码可以看出这个成员变量handlr是在NioEndpoint的抽象父类AbstractEndpoint中定义的。

public abstract class AbstractProtocol<S> implements ProtocolHandler, MBeanRegistration {
    public AbstractProtocol(AbstractEndpoint<S, ?> endpoint) {
        // ...
        getEndpoint().setHandler(cHandler);
    }
    protected AbstractEndpoint<S, ?> getEndpoint() {
        return endpoint;
    }
}

public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel>{}

public abstract class AbstractJsseEndpoint<S,U> extends AbstractEndpoint<S,U> {}

public abstract class AbstractEndpoint<S,U> {
    private Handler<S> handler = null;
    public void setHandler(Handler<S> handler ) { this.handler = handler; }
}

        2、init方法

        ProtocolHandler的init方法最终调用了NioEndpoint的inti方法,而该方法的实现在其抽象父类 AbstractEndpoint 里,然后又回溯到了AbstractEndpoint中,可以看到这里通过bindWithCleanup()方法实现核心逻辑。

        2.1、bindOnInit

        从bindOnInit的注解可以看出这里的意思是控制Endpoint是否在初始化时绑定端口,通过前文我们知道Endpoint是传输层的实现,因此需要创建ServerSocketChannel 并进行监听端口,而这个参数正是控制端口监听的时机。

public abstract class AbstractJsseEndpoint<S,U> extends AbstractEndpoint<S,U> {
    public void init() throws Exception {
        testServerCipherSuitesOrderSupport();
        super.init();
    }
}

public abstract class AbstractEndpoint<S,U> {
     /**
     * Controls when the Endpoint binds the port. <code>true</code>, the default
     * binds the port on {@link #init()} and unbinds it on {@link #destroy()}.
     * If set to <code>false</code> the port is bound on {@link #start()} and
     * unbound on {@link #stop()}.
     */
    private boolean bindOnInit = true;
    public void init() throws Exception {
        if (bindOnInit) {
            bindWithCleanup();
            bindState = BindState.BOUND_ON_INIT;
        }
        // ...
    }
}

        bindWithCleanup方法继续调用bind方法。

public abstract class AbstractEndpoint<S,U> {
    private void bindWithCleanup() throws Exception {
        try {
            bind();
        } catch (Throwable t) {
            // ...
        }
    }
}

         bind方法的实现通过子类AbstractJsseEndpoint完成,这里最重要的就是第一步initServerSocket();

public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> {
    /**
     * Initialize the endpoint.
    */
    @Override
    public void bind() throws Exception {
        initServerSocket();
        setStopLatch(new CountDownLatch(1));
        // Initialize SSL if needed
        initialiseSsl();
    }
}

        2.2、initServerSocket 

         initServerSocket方法内部逻辑可以看出来就是创建一个ServerSocketChannel 并设置了一些相关属性,然后绑定地址和端口进行监听。

public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> {
    /**
     * Server socket "pointer".
     */
    private volatile ServerSocketChannel serverSock = null;

    private boolean useInheritedChannel = false;
    public boolean getUseInheritedChannel() { return useInheritedChannel; }

    protected void initServerSocket() throws Exception {
        if (getUseInheritedChannel()) {
            Channel ic = System.inheritedChannel();
            if (ic instanceof ServerSocketChannel) {
                serverSock = (ServerSocketChannel) ic;
            }
            if (serverSock == null) {
                throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
            }
        } else {
            serverSock = ServerSocketChannel.open();
            socketProperties.setProperties(serverSock.socket());
            InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
            serverSock.socket().bind(addr,getAcceptCount());
        }
        serverSock.configureBlocking(true); //mimic APR behavior
    }

}

        可以看出Endpoint的init方法目的就是创建ServerSocketChannel并监听请求。

        3、start方法

        start方法由于AbstractEndpoint类实现,其内部调用startInternal方法。 

public abstract class AbstractEndpoint<S,U> {
    public final void start() throws Exception {
        if (bindState == BindState.UNBOUND) {
            bindWithCleanup();
            bindState = BindState.BOUND_ON_START;
        }
        startInternal();
    }
}

        3.1、startInternal

        startInternal方法由NioEndpoint实现,这里先创建了三个SynchronizedStack对象来保存缓存,分别赋值给 processorCache,eventCache和nioChannels,这三个属性都使用来复用的,分别复用SocketProcessorBase对象,PollerEvent对象和NioChannel对象。其中 processorCache 在 AbstractEndpoint 里声明,其他两个在 NioEndpoint 里声明。然后就是一些方法调用,下面我们简单介绍下这些方法的作用。

public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> {

    protected volatile boolean running = false; 
    // Cache for SocketProcessor objects
    protected SynchronizedStack<SocketProcessorBase<S>> processorCache;
    // Cache for poller events
    private SynchronizedStack<PollerEvent> eventCache;
    // Bytebuffer cache, each channel holds a set of buffers
    // (two, except for SSL holds four)
    private SynchronizedStack<NioChannel> nioChannels;

   public void startInternal() throws Exception {
        if (!running) {
            running = true;
            paused = false;
            if (socketProperties.getProcessorCache() != 0) {
                processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                        socketProperties.getProcessorCache());
            }
            if (socketProperties.getEventCache() != 0) {
                eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                        socketProperties.getEventCache());
            }
            if (socketProperties.getBufferPool() != 0) {
                nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                        socketProperties.getBufferPool());
            }
            // Create worker collection
            if (getExecutor() == null) {
                createExecutor();
            }
            initializeConnectionLatch();
            // Start poller thread
            poller = new Poller();
            Thread pollerThread = new Thread(poller, getName() + "-Poller");
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
            startAcceptorThread();
        }
    }

}

        3.2、getExecutor

        getExecutor这里会获取成员变量executor,这个变量的设置实际上是通过我们前文中介绍的ConnectorCreateRule(《Tomcat源码:连接器与Executor、Connector》)调用ProtocolHandler的setExecutor方法。

public class ConnectorCreateRule extends Rule {
    private static void setExecutor(Connector con, Executor ex) throws Exception {
        // 获取Connector中得ProtocolHandler,调用其setExecutor方法
        Method m = IntrospectionUtils.findMethod(con.getProtocolHandler().getClass(),"setExecutor",new Class[] {java.util.concurrent.Executor.class});
        if (m!=null) {
            m.invoke(con.getProtocolHandler(), new Object[] {ex});
        }else {
            log.warn(sm.getString("connector.noSetExecutor", con));
        }
    }
}

         ProtocolHandler的setExecutor方法内再调用endpoint.setExecutor(executor);为endpoint变量设置线程池,因此这里的getExecutor可以获取到在standardservice中创建的线程池。

public abstract class AbstractProtocol<S> implements ProtocolHandler, MBeanRegistration { 
   public void setExecutor(Executor executor) {
        endpoint.setExecutor(executor);
    }
}

public abstract class AbstractEndpoint<S,U> {
    protected volatile boolean internalExecutor = true;
    private Executor executor = null;
    public void setExecutor(Executor executor) {
        this.executor = executor;
        this.internalExecutor = (executor == null);
    }
    public Executor getExecutor() { return executor; }

}

        如果这里获取到的线程池为空则会自己创建一个,可以看到这里其实和我们之前介绍的StandardThreadExecutor中创建线程池的内容是一致的。

public abstract class AbstractEndpoint<S,U> {
    public void createExecutor() {
        internalExecutor = true;
        TaskQueue taskqueue = new TaskQueue();
        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
        taskqueue.setParent( (ThreadPoolExecutor) executor);
    }
}

        3.3、initializeConnectionLatch  

        initializeConnectionLatch 方法初始换了 connectionLimitLatch 属性,这个属性是用来限制 tomcat 的最大连接数的,可以看到这里默认大小是8*1024。

    protected LimitLatch initializeConnectionLatch() {
        if (maxConnections==-1) {
            return null;
        }
        if (connectionLimitLatch==null) {
            connectionLimitLatch = new LimitLatch(getMaxConnections());
        }
        return connectionLimitLatch;
    }


    private int maxConnections = 8*1024;

    public int getMaxConnections() { return this.maxConnections; }

        3.4、 Poller与Acceptor

        接下来启动了异步线程执行poller类的run方法。

    // Start poller thread
    poller = new Poller();
    Thread pollerThread = new Thread(poller, getName() + "-Poller");
    pollerThread.setPriority(threadPriority);
    pollerThread.setDaemon(true);
    pollerThread.start();

         最后是startAcceptorThread方法,可以看到这里创建了Acceptor对象并启动了异步线程执行run方法。

    protected void startAcceptorThread() {
        acceptor = new Acceptor<>(this);
        String threadName = getName() + "-Acceptor";
        acceptor.setThreadName(threadName);
        Thread t = new Thread(acceptor, threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        t.start();
    }

        Endpoint方法的最后两步启动了两个异步线程Acceptor与Poller ,这两个组件构成了 tomcat 的线程模型,是非常重要的组件。其中Acceptor线程用于处理客户端连接,而Poller处理这些连接通道上的读写事件。

        至此我们大致介绍完了Endpoint组件的基本内容,其中内容主要是创建ServerSocketChannel监听网络连接,创建/获取连接池,设置最大连接数,并启动线程模型Poller与Acceptor,他们是连接处理的关键,我们会在后续的文章中进行介绍。

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

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

相关文章

Linux下实现统计文件单词个数和出现次数

本文介绍的是在Linux下实现统计文件单词个数和出现次数&#xff0c;以及实践过程中遇到的gcc编译器不匹配问题 一、实现文件单词个数统计 #include <stdio.h>#define IN_Word 1 #define OUT_Word 0 #define INIT OUT_Wordint splite(char c){if ((c ) || (c\n) || (c\t…

上海丨阿里云 Serverless 技术实战营邀你来玩!

活动简介 本次沙龙深度探讨 “Serverless 在中国企业的落地和开发者实操” 主题&#xff0c;我们特别邀请了来自阿里云一线技术专家&#xff0c;分享当前 Serverless 趋势和落地实践过程中的挑战和机遇&#xff1b;带来数据库 Serverless 技术架构及应用实践&#xff1b;浅析云…

地铁之家—车辆段

城市轨道交通列车也有一个家——车辆段&#xff0c;它通常由停车区、维修区和清洗区等组成&#xff0c;用于停放、维修保养。 一、地铁车辆段和停车场有何不同&#xff1f; 停车场在管理上一般附属于主要车辆段&#xff0c;规模较小&#xff0c;功能上可以实现车辆的运用管理…

如何进行MySQL漏洞扫描

MySQL是一款广泛使用的关系型数据库管理系统&#xff0c;但由于其复杂的结构和功能&#xff0c;也存在不少安全漏洞&#xff0c;容易被黑客攻击。为了解决这些安全问题&#xff0c;进行MySQL漏洞扫描是必要的。那么MySQL怎么进行漏洞扫描?如何进行漏洞扫描?接下来就让小编带大…

ChatGPT为什么能生成图片?

有小伙伴说我想用ChatGPT生成图片怎么操作&#xff0c;ChatGPT怎么画图等 这里阐明一下&#xff0c;ChatGPT是不能够做到画图的 因为它是一种自然语言处理模型&#xff0c;主要用于处理文本和语言相关的任务&#xff0c;例如问答、对话、翻译等。但是&#xff0c;我们可以使用C…

技术干货|如何利用 ChunJun 实现数据离线同步?

ChunJun 是⼀款稳定、易⽤、⾼效、批流⼀体的数据集成框架&#xff0c;基于计算引擎 Flink 实现多种异构数据源之间的数据同步与计算。ChunJun 可以把不同来源、格式、特点性质的数据在逻辑上或物理上有机地集中&#xff0c;从⽽为企业提供全⾯的数据共享&#xff0c;目前已在上…

针对电子企业的仓储需求,提出WMS仓储管理系统解决方案

随着电子行业的快速发展&#xff0c;仓储管理已经成为电子企业日常运营中不可或缺的一环。然而&#xff0c;由于缺乏有效的仓储管理系统&#xff0c;电子企业经常面临库存不准确、库存滞销等问题。这就是电子企业仓储管理面临的严重问题&#xff0c;引出了需要提出一套有效的仓…

【每日一题Day211】LC1079活字印刷 | 回溯 计数dp

活字印刷【LC1079】 你有一套活字字模 tiles&#xff0c;其中每个字模上都刻有一个字母 tiles[i]。返回你可以印出的非空字母序列的数目。 **注意&#xff1a;**本题中&#xff0c;每个活字字模只能使用一次。 我反正是写的相当暴力 计数回溯 思路&#xff1a; 为了构成不同的…

win10设置notepad++默认打开txt - 两步解决

第一步&#xff1a;Notepad注册.txt 以管理员的方式打开notepad 步骤&#xff1a;打开设置 -> 首选项 -> 文件关联 双击.txt .txt移动到注册框即可 第二步 设置Notepad默认打开 按照以下步骤将Notepad设置为默认打开.txt文件&#xff1a; 右键单击任何一个.txt文件…

车规级MCU芯片

作为车辆控制的核心器件&#xff0c;MCU主要用于车身控制、驾驶控制、信息娱乐和驾驶辅助系统。 8位MCU &#xff1a;提供低端控制功能:风扇控制、空调控制、雨刷、天窗、 车窗升降、低端仪表盘、集线盒、座椅控制、门控模块。 16位MCU &#xff1a;提供中端控制功能:用于动力…

生物信息学——用好源代码的技巧与心法

如果你是一名科研人员&#xff0c;在研究的过程中需要用到代码&#xff0c;那么你可能不需要像专业码农那样从头到尾一句一句去写完整的&#xff0c;而是可以将网上的一段符合应用场景的现成代码拿过来直接用。 这听起来是不是很简单&#xff1f;然而实际上... 目前&#xff0c…

Tuxera NTFS2023苹果电脑专用磁盘读写软件

Tuxera NTFS for Mac是苹果上专门的NTFS磁盘读写工具&#xff0c;帮助Mac用户解决苹果操作系统读写U盘、硬盘等NTFS格式分区的磁盘的困难。其实&#xff0c;最早读写NTFS软件是Tuxera NTFS-3G&#xff0c;但是当时是开源的&#xff08;直接让用户免费使用的&#xff09;&#x…

ubuntu1804替换系统的cups后,启动cups时报错 undefined symbol:_cupsMessageSave。。。

开发环境&#xff1a; Ubuntu18.04 cups-2.2.7 最终要将cups-2.2.7替换为cups-.2.3.3 好&#xff0c;在编译完cups后&#xff0c;对系统的cups进行替换&#xff0c;&#xff0c;此操作已完成。。。。 接下来&#xff0c;启动cups&#xff0c;发现启动失败。。 紧接着执行 jo…

《Java 核心技术面试》课程笔记(九)

对比 Hashtable、HashMap、TreeMap 有什么不同&#xff1f; 典型回答 Hashtable、HashMap、TreeMap 都是最常见的⼀些 Map 实现&#xff0c;是以键值对的形式存储和操作数据的容器类型。Hashtable 是早期 Java 类库提供的⼀个哈希表实现&#xff0c;本身是同步的&#xff0c;…

GE H201TI 全系统自检和自诊断

Hydran 201Ti是一个小型在线预警发射器。它永久安装在变压器上&#xff0c;将为工作人员提供各种故障气体复合值的单一ppm读数&#xff0c;以提醒他们潜在的问题。 可以下载该值&#xff0c;并且可以将警报设置在预定水平&#xff0c;以提醒人员并能够监控发展中的故障状况。 …

作为一位php程序员应具要有那些能力

最近&#xff0c;随着信息技术的发展&#xff0c;更多的人开始关注PHP程序员的职业发展&#xff0c;并渴望成为一名高薪稳定就业的PHP程序员。但是&#xff0c;想要成为一名优秀的PHP程序员&#xff0c;并不仅仅需要掌握PHP的语言基础&#xff0c;还需要具备其他的技能和能力。…

本地代码提交到gitee

提交步骤 注意&#xff1a;该步骤需要使用git工具&#xff0c;请提前下载 参考文章1&#xff1a;如何将本地代码上传到 gitee 该博客包含了gitee创建仓库流程 参考文章2&#xff1a;Git push命令报hint: Updates were rejected because the remote contains work that you do问…

【论文阅读笔记】Federated Unlearning with Knowledge Distillation

个人阅读笔记&#xff0c;如有错误欢迎指出 Arxiv 2022 [2201.09441] Federated Unlearning with Knowledge Distillation (arxiv.org) 问题&#xff1a; 法律要求客户端有随时要求将其贡献从训练中消除的权利 让全局模型忘记特定客户的贡献的一种简单方法是从头开始对模型进…

【机器视觉4】双目立体视觉标定

双目立体视觉标定的目的是标定左、右两个摄像机之间的坐标转换关系。 双目立体视觉的标定过程&#xff1a;采用MATLAB图像处理和计算机视觉库中的 Stereo Camera Calibrator(SCC)来标定双目立体视觉系统中左、右摄像机并获得左右摄像机的内参矩阵 M 1 M_1 M1​、 M 2 M_2 M2​…

Midjourney|文心一格prompt教程[技巧篇]:生成多样性、增加艺术风格、图片二次修改、渐进优化、权重、灯光设置等17个技巧等你来学

Midjourney|文心一格prompt教程[技巧篇]&#xff1a;生成多样性、增加艺术风格、图片二次修改、渐进优化、权重、灯光设置等17个技巧等你来学 1.技巧一&#xff1a;临摹 我认为学习图片类的 prompt&#xff0c;跟学习画画是类似的&#xff0c;最好的学习方法不是直接用模板。…