返回顶部
首页 > 资讯 > 精选 >Druid核心源码分析DruidDataSource
  • 502
分享到

Druid核心源码分析DruidDataSource

2023-07-05 19:07:18 502人浏览 八月长安
摘要

这篇“Druid核心源码分析DruidDataSource”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Druid核心源码分

这篇“Druid核心源码分析DruidDataSource”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Druid核心源码分析DruidDataSource”文章吧。

配置读取

druid连接池支持的所有连接参数可在类com.alibaba.druid.pool.DruidDataSourceFactory中查看。

配置读取代码:

 public void configFromPropety(Properties properties) {        //这方法太长,自己看源码去吧,就是读读属性。。。。    }

整体代码比较简单,就是把配置内容,读取到dataSource。

连接池初始化

首先是简单的判断,加

if (inited) {            //已经被初始化好了,直接return            return;        }        // bug fixed for dead lock, for issue #2980        DruidDriver.getInstance();                // public DruidAbstractDataSource(boolean lockFair){        //        lock = new ReentrantLock(lockFair);        //        //        notEmpty = lock.newCondition();        //        empty = lock.newCondition();        //    }        final ReentrantLock lock = this.lock;        try {            lock.lockInterruptibly();        } catch (InterruptedException e) {            throw new sqlException("interrupt", e);        }

之后会更新一些JMX的监控指标:

//一些jmx监控指标                this.connectionIdSeedUpdater.addAndGet(this, delta);                this.statementIdSeedUpdater.addAndGet(this, delta);                this.resultSetIdSeedUpdater.addAndGet(this, delta);                this.transactionIdSeedUpdater.addAndGet(this, delta);

druid的监控指标都是通过jmx实现的。

解析连接串:

 if (this.jdbcUrl != null) {                //解析连接串                this.jdbcUrl = this.jdbcUrl.trim();                initFromWrapDriverUrl();            }

initFromWrapDriverUrl方法,除了从jdbc url中解析出连接和驱动信息,后面还把filters的名字,解析成了对应的filter类。

  private void initFromWrapDriverUrl() throws SQLException {        if (!jdbcUrl.startsWith(DruidDriver.DEFAULT_PREFIX)) {            return;        }        DataSourceProxyConfig config = DruidDriver.parseConfig(jdbcUrl, null);        this.driverClass = config.getRawDriverClassName();        LOG.error("error url : '" + jdbcUrl + "', it should be : '" + config.getRawUrl() + "'");        this.jdbcUrl = config.getRawUrl();        if (this.name == null) {            this.name = config.getName();        }        for (Filter filter : config.getFilters()) {            addFilter(filter);        }    }

之后在init方法里面,会进行filters的初始化:

 //初始化filter 属性            for (Filter filter : filters) {                filter.init(this);            }

之后解析数据库类型:

 if (this.dbTypeName == null || this.dbTypeName.length() == 0) {                this.dbTypeName = JdbcUtils.getDbType(jdbcUrl, null);            }

注意枚举值: com.alibaba.druid.DbType,这个里面包含了目前durid连接池支持的所有数据源 类型,另外,druid还额外提供了一些驱动类,例如:

 elastic_search  (1 << 25), // com.alibaba.xdriver.elastic.jdbc.ElasticDriver

clickhouse还提供了负载均衡的驱动类:

com.alibaba.druid.support.clickhouse.BalancedClickhouseDriver

在回到init方法,之后是一堆参数解析,不再说,跳过了。 之后是通过SPI加载自定义的filter:

  private void initFromSPIServiceLoader() {        if (loadSpifilterSkip) {            return;        }        if (autoFilters == null) {            List<Filter> filters = new ArrayList<Filter>();            ServiceLoader<Filter> autoFilterLoader = ServiceLoader.load(Filter.class);            for (Filter filter : autoFilterLoader) {                AutoLoad autoLoad = filter.getClass().getAnnotation(AutoLoad.class);                if (autoLoad != null && autoLoad.value()) {                    filters.add(filter);                }            }            autoFilters = filters;        }        for (Filter filter : autoFilters) {            if (LOG.isInfoEnabled()) {                LOG.info("load filter from spi :" + filter.getClass().getName());            }            addFilter(filter);        }    }

注意自定义的filter,要使用com.alibaba.druid.filter.AutoLoad

解析驱动:

  protected void resolveDriver() throws SQLException {        if (this.driver == null) {            if (this.driverClass == null || this.driverClass.isEmpty()) {                this.driverClass = JdbcUtils.getDriverClassName(this.jdbcUrl);            }            if (MockDriver.class.getName().equals(driverClass)) {                driver = MockDriver.instance;            } else if ("com.alibaba.druid.support.clickhouse.BalancedClickhouseDriver".equals(driverClass)) {                Properties info = new Properties();                info.put("user", username);                info.put("passWord", password);                info.putAll(connectProperties);                driver = new BalancedClickhouseDriver(jdbcUrl, info);            } else {                if (jdbcUrl == null && (driverClass == null || driverClass.length() == 0)) {                    throw new SQLException("url not set");                }                driver = JdbcUtils.createDriver(driverClassLoader, driverClass);            }        } else {            if (this.driverClass == null) {                this.driverClass = driver.getClass().getName();            }        }    }

其中durid自己的mock驱动和clickhouse的负载均衡的驱动,特殊判断了下,其他走的都是class forname.

之后是exception sorter和checker的一些东西,跟主线剧情关系不大,skip.

之后是一些初始化JdbcDataSourceStat,没啥东西。

之后是核心:

  connections = new DruidConnectionHolder[maxActive];  //连接数组            evictConnections = new DruidConnectionHolder[maxActive]; //销毁的连接数组            keepAliveConnections = new DruidConnectionHolder[maxActive]; //保持活跃可用的数组

dataSource的连接,都被包装在类DruidConnectionHolder中,之后是一个同步去初始化连接还是异步去初始化的连接,总之,是去初始化 连接的过程:

if (createScheduler != null && asyncInit) {                for (int i = 0; i < initialSize; ++i) {                    submitCreateTask(true);                }            } else if (!asyncInit) {                // init connections                while (poolinGCount < initialSize) {                    try {                        PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();                        DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);                        connections[poolingCount++] = holder;                    } catch (SQLException ex) {                        LOG.error("init datasource error, url: " + this.getUrl(), ex);                        if (initExceptionThrow) {                            connectError = ex;                            break;                        } else {                            Thread.sleep(3000);                        }                    }                }                if (poolingCount > 0) {                    poolingPeak = poolingCount;                    poolingPeakTime = System.currentTimeMillis();                }            }

初始化的连接个数为连接串里面配置的initialSize.

核心初始化方法com.alibaba.druid.pool.DruidAbstractDataSource#createPhysicalConnection(),在这方法里面,会拿用户名密码,之后执行真正的获取connection:

 public Connection createPhysicalConnection(String url, Properties info) throws SQLException {        Connection conn;        if (getProxyFilters().size() == 0) {            conn = getDriver().connect(url, info);        } else {            conn = new FilterChainImpl(this).connection_connect(info);        }        createCountUpdater.incrementAndGet(this);        return conn;    }

注意,如果配置了filters,则所有操作,都会在操作前执行filter处理链。

 public ConnectionProxy connection_connect(Properties info) throws SQLException {        if (this.pos &lt; filterSize) {            return nextFilter()                    .connection_connect(this, info);        }        Driver driver = dataSource.getRawDriver();        String url = dataSource.getRawJdbcUrl();        Connection nativeConnection = driver.connect(url, info);        if (nativeConnection == null) {            return null;        }        return new ConnectionProxyImpl(dataSource, nativeConnection, info, dataSource.createConnectionId());    }

再回到主流程init方法,connections数组初始化完成之后, 开启额外线程

     createAndLogThread();  //打印连接信息            createAndStartCreatorThread(); //创建连接线程            createAndStartDestroyThread(); //销毁连接线程

先看注释,具体里面的内容后面单独拉出来讲。

之后:

 initedLatch.await(); //初始化 latch -1            init = true;  //标记已经初始化完成            initedTime = new Date(); //时间            reGISterMbean(); //为datasource 注册jmx监控指标

最后的最后,如果配置了keepAlive:

if (keepAlive) {                // async fill to minIdle                if (createScheduler != null) {                    for (int i = 0; i &lt; minIdle; ++i) {                        submitCreateTask(true);                    }                } else {                    this.emptySignal();                }            }

这时候,会根据配置的活跃连接数minIdle,去给datasource的连接,做个保持活跃连接个数,具体后面再说。

连接池使用的核心逻辑

首先,使用数组作为连接的容器,对于真实连接的加入和移除,使用lock就行同步,另外,在加入和移除连接时候,对比生产消费模型,通过lock上的条件,来通知是否可以获取或者加入连接。

 public DruidAbstractDataSource(boolean lockFair){        lock = new ReentrantLock(lockFair);        notEmpty = lock.newCondition();  //非空,有连接        empty = lock.newCondition(); //空的    }

另外,默认的fairlock为false

  public DruidDataSource(){        this(false);    }    public DruidDataSource(boolean fairLock){        super(fairLock);        configFromPropety(System.getProperties());    }

创建连接

在线程com.alibaba.druid.pool.DruidDataSource.CreateConnectionThread中:

 if (emptyWait) {                        // 必须存在线程等待,才创建连接                        if (poolingCount >= notEmptyWaitThreadCount //                                && (!(keepAlive && activeCount + poolingCount < minIdle))                                && !isFailContinuous()                        ) {                            empty.await();                        }                        // 防止创建超过maxActive数量的连接                        if (activeCount + poolingCount >= maxActive) {                            empty.await();                            continue;                        }                    }

必须存在线程等待获取连接时候,才能创建连接,并且要保持总的连接数,不能超过配置的最大连接。

创建完连接之后,执行 notEmpty.signalAll();通知消费者。

获取连接

外层代码:

 @Override    public DruidPooledConnection getConnection() throws SQLException {        return getConnection(maxWait);    }    public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {        init();        if (filters.size() > 0) {            FilterChainImpl filterChain = new FilterChainImpl(this);            return filterChain.dataSource_connect(this, maxWaitMillis);        } else {            return getConnectionDirect(maxWaitMillis);        }    }

忽略掉filter chain,其实最后执行的还是com.alibaba.druid.pool.DruidDataSource#getConnectionDirect

方法内部:

   poolableConnection = getConnectionInternal(maxWaitMillis);
  • 1 , 连接不足,需要直接去创建新的,跟我们初始化一样

  • 2,从connections里面拿

 if (maxWait &gt; 0) {                    holder = pollLast(nanos);                } else {                    holder = takeLast();                }

其中,maxWait默认为-1,配置在init里面:

 String property = properties.getProperty("druid.maxWait");            if (property != null && property.length() > 0) {                try {                    int value = Integer.parseInt(property);                    this.setMaxWait(value);                } catch (NumberFORMatException e) {                    LOG.error("illegal property 'druid.maxWait'", e);                }            }

这个用于配置拿连接时候,是否在这个时间上进行等待,默认是否,即一直等到拿到连接为止。

直接看下阻塞拿的过程:

 DruidConnectionHolder takeLast() throws InterruptedException, SQLException {        try {            //没连接了            while (poolingCount == 0) {                //暗示下创建线程没连接了                emptySignal(); // send signal to CreateThread create connection                if (failFast &amp;&amp; isFailContinuous()) {                    throw new DataSourceNotAvailableException(createError);                }                notEmptyWaitThreadCount++;                if (notEmptyWaitThreadCount &gt; notEmptyWaitThreadPeak) {                    notEmptyWaitThreadPeak = notEmptyWaitThreadCount;                }                try {                    //傻等着创建或者回收,能给整出来点儿连接                    notEmpty.await(); // signal by recycle or creator                } finally {                    notEmptyWaitThreadCount--;                }                notEmptyWaitCount++;                if (!enable) {                    connectErrorCountUpdater.incrementAndGet(this);                    if (disableException != null) {                        throw disableException;                    }                    throw new DataSourceDisableException();                }            }        } catch (InterruptedException ie) {            notEmpty.signal(); // propagate to non-interrupted thread            notEmptySignalCount++;            throw ie;        }        //拿数组的最后一个连接        decrementPoolingCount();        DruidConnectionHolder last = connections[poolingCount];        connections[poolingCount] = null;        return last;    }

连接回收

 protected void createAndStartDestroyThread() {        destroyTask = new DestroyTask();//自定义配置销毁 ,适用于连接数非常多的 情况        if (destroyScheduler != null) {            long period = timeBetweenEvictionRunsMillis;            if (period &lt;= 0) {                period = 1000;            }            destroySchedulerFuture = destroyScheduler.scheduleAtFixedRate(destroyTask, period, period,                                                                          TimeUnit.MILLISECONDS);            initedLatch.countDown();            return;        }        String threadName = "Druid-ConnectionPool-Destroy-" + System.identityHashCode(this);        //单线程销毁         destroyConnectionThread = new DestroyConnectionThread(threadName);        destroyConnectionThread.start();    }

实际的销毁:

 public class DestroyTask implements Runnable {        public DestroyTask() {        }        @Override        public void run() {            shrink(true, keepAlive);            if (isRemoveAbandoned()) {                removeAbandoned();            }        }    }

最终 执行的还是 shrink方法。

   public void shrink(boolean checkTime, boolean keepAlive) {        try {            lock.lockInterruptibly();        } catch (InterruptedException e) {            return;        }        boolean needFill = false;        int evictCount = 0;        int keepAliveCount = 0;        int fatalErrorIncrement = fatalErrorCount - fatalErrorCountLastShrink;        fatalErrorCountLastShrink = fatalErrorCount;        try {            if (!inited) {                return;            }            final int checkCount = poolingCount - minIdle; //需要检测连接的数量            final long currentTimeMillis = System.currentTimeMillis();            for (int i = 0; i < poolingCount; ++i) { //检测目前connections数组中的连接                DruidConnectionHolder connection = connections[i];                if ((onFatalError || fatalErrorIncrement > 0) && (lastFatalErrorTimeMillis > connection.connectTimeMillis))  {                    keepAliveConnections[keepAliveCount++] = connection;                    continue;                }                if (checkTime) {                    //是否设置了物理连接的超时时间phyTimoutMills。假如设置了该时间,                    // 判断连接时间存活时间是否已经超过phyTimeoutMills,是则放入evictConnections中                    if (phyTimeoutMillis > 0) {                        long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;                        if (phyConnectTimeMillis > phyTimeoutMillis) {                            evictConnections[evictCount++] = connection;                            continue;                        }                    }                    long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis;//获取连接空闲时间                    //如果某条连接空闲时间小于minEvictableIdleTimeMillis,则不用继续检查剩下的连接了                    if (idleMillis < minEvictableIdleTimeMillis                            && idleMillis < keepAliveBetweenTimeMillis                    ) {                        break;                    }                    if (idleMillis >= minEvictableIdleTimeMillis) {                        // check checkTime is silly code                        //检测检查了几个连接了                        if (checkTime && i < checkCount) {                            //超时了                            evictConnections[evictCount++] = connection;                            continue;                        } else if (idleMillis > maxEvictableIdleTimeMillis) {                            //超时了                            evictConnections[evictCount++] = connection;                            continue;                        }                    }                    if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) {                        //配置了keepAlive,并且在存活时间内,放到keepAlive数组                        keepAliveConnections[keepAliveCount++] = connection;                    }                } else {                    //不需要检查时间的,直接移除                    if (i < checkCount) {                        evictConnections[evictCount++] = connection;                    } else {                        break;                    }                }            }            int removeCount = evictCount + keepAliveCount; //移除了几个            //由于使用connections连接时候,都是取后面的,后面 的是最新的连接,只考虑前面过期就行,所以只需要挪动前面的连接            if (removeCount > 0) {                System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);                Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);                poolingCount -= removeCount;            }            keepAliveCheckCount += keepAliveCount;            if (keepAlive && poolingCount + activeCount < minIdle) {                //不够核心的活跃连接时候,需要去创建啦                needFill = true;            }        } finally {            lock.unlock();        }        if (evictCount > 0) {            for (int i = 0; i < evictCount; ++i) {                //销毁连接                DruidConnectionHolder item = evictConnections[i];                Connection connection = item.getConnection();                JdbcUtils.close(connection);                destroyCountUpdater.incrementAndGet(this);            }            Arrays.fill(evictConnections, null);        }        if (keepAliveCount > 0) {            // keep order            for (int i = keepAliveCount - 1; i >= 0; --i) {                DruidConnectionHolder holer = keepAliveConnections[i];                Connection connection = holer.getConnection();                holer.incremenTKEepAliveCheckCount();                boolean validate = false;                try {                    this.validateConnection(connection);                    validate = true;                } catch (Throwable error) {                    if (LOG.isDebugEnabled()) {                        LOG.debug("keepAliveErr", error);                    }                    // skip                }                boolean discard = !validate; //没通过validate                if (validate) {                    //通过keep alive检查,更新时间                    holer.lastKeepTimeMillis = System.currentTimeMillis();                    //这里还会尝试放回connections数组                    boolean putOk = put(holer, 0L, true);                    if (!putOk) {                        //没放入,标记要丢弃了                        discard = true;                    }                }                if (discard) {                    try {                        connection.close();                    } catch (Exception e) {                        // skip                    }                    lock.lock();                    try {                        discardCount++;                        if (activeCount + poolingCount <= minIdle) {                            //发信号让创建线程去创建                            emptySignal();                        }                    } finally {                        lock.unlock();                    }                }            }            this.getDataSourceStat().addKeepAliveCheckCount(keepAliveCount);            Arrays.fill(keepAliveConnections, null);        }        if (needFill) {            //又要去创建了            lock.lock();            try {                int fillCount = minIdle - (activeCount + poolingCount + createTaskCount);                for (int i = 0; i < fillCount; ++i) {                    emptySignal();                }            } finally {                lock.unlock();            }        } else if (onFatalError || fatalErrorIncrement > 0) {            lock.lock();            try {                emptySignal();            } finally {                lock.unlock();            }        }    }

工具数组evictConnections,keepAliveConnections 用完即被置空,老工具人了。

一波操作下来,完成了对connections数组的大清洗。

以上就是关于“Druid核心源码分析DruidDataSource”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网精选频道。

--结束END--

本文标题: Druid核心源码分析DruidDataSource

本文链接: https://lsjlt.com/news/354601.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Druid核心源码分析DruidDataSource
    这篇“Druid核心源码分析DruidDataSource”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Druid核心源码分...
    99+
    2023-07-05
  • Spring AOP核心功能源码分析
    这篇“Spring AOP核心功能源码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Spring A...
    99+
    2023-07-05
  • RocketMQNameServer核心源码解析
    目录带着问题 往下看 (namesrv)nameserver 启动的逻辑nameserver 功能nameserver 问题解答我们在写组件的时候 怎么管理version遍历 Fie...
    99+
    2024-04-02
  • JAVA核心知识之ConcurrentHashMap源码分析
    1 前言 ConcurrentHashMap是基于Hash表的Map接口实现,键与值均不允许为NULL,他是一个线程安全的Map。同时他也是一个无序的Map,不同时间进行遍历可能会得...
    99+
    2024-04-02
  • Java SpringBoot核心源码的示例分析
    本篇文章给大家分享的是有关Java SpringBoot核心源码的示例分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。SpringBoot源码主线分析我们要分析一个...
    99+
    2023-06-22
  • kafka核心消费逻辑源码分析
    本篇内容主要讲解“kafka核心消费逻辑源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“kafka核心消费逻辑源码分析”吧!消费逻辑框架搭建好之后着手开发下kafka的核心消费逻辑,流式图...
    99+
    2023-07-06
  • 源码分析Vue3响应式核心之reactive
    目录一、Reactive源码1、reactive2、接着看工厂方法createReactiveObject二、baseHandlers1、baseHandlersvue3响应式核心文...
    99+
    2023-05-17
    Vue3响应式核心reactive Vue3响应式 reactive Vue3 reactive
  • 源码分析Vue3响应式核心之effect
    目录一、effect用法1、基本用法2、lazy属性为true3、options中包含onTrack二、源码分析1、effect方法的实现2、ReactiveEffect函数源码三、...
    99+
    2023-05-17
    Vue3响应式核心effect Vue3响应式effect Vue3 effect
  • java线程池核心API源码详细分析
    目录概述源码分析ExecutorExecutorServiceScheduledExecutorServiceThreadPoolExecutorScheduledThreadPoo...
    99+
    2024-04-02
  • java编程Reference核心原理示例源码分析
    带着问题,看源码针对性会更强一点、印象会更深刻、并且效果也会更好。所以我先卖个关子,提两个问题(没准下次跳槽时就被问到)。 我们可以用ByteBuffer的allocateDirec...
    99+
    2024-04-02
  • 简易vuex4核心原理及实现源码分析
    目录前言Vuex 核心原理使用方式vuex 运行流程核心原理实现一个简易版的 vuex实现 store 的派发和注册、响应式、injectKey实现 getters、mutation...
    99+
    2023-01-12
    vuex4核心原理 vuex4 简易版
  • Android内核wake_up源码分析
    今天小编给大家分享一下Android内核wake_up源码分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。内核中通常用法:...
    99+
    2023-07-05
  • Flask核心机制--上下文源码剖析
    一、前言   了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很多博文有关于对flask上下文管理...
    99+
    2023-01-30
    上下文 源码 机制
  • flutter图片组件核心类源码解析
    目录导语问题Image的核心类图及其关系网络图片的加载过程网络图片数据的回调和展示过程补上图片内存缓存的源码分析如何支持图片的磁盘缓存总结导语 在使用flutter 自带图片组件的过...
    99+
    2023-05-16
    flutter图片组件核心类 flutter图片组件源码解析
  • Backbone前端框架核心及源码解析
    目录一、 什么是Backbone二、 核心架构三、 部分源码解析四、 不足(对比react、vue)五、为什么选择Backbone一、 什么是Backbone 在前端的发展道路中,...
    99+
    2023-02-07
    Backbone前端框架 Backbone 框架
  • Java SpringBoot核心源码详解
    目录SpringBoot源码主线分析1.SpringBoot启动的入口2.run方法3.SpringApplication构造器4.run方法总结SpringBoot源码主线分析 我...
    99+
    2024-04-02
  • Spring源码剖析1:初探Spring IOC核心流程
    本文大致地介绍了IOC容器的初始化过程,只列出了比较重要的过程和代码,可以从中看出IOC容器执行的大致流程。接下来的文章会更加深入剖析Bean容器如何解析xml,注册和初始化bean,以及如何获取bean实例等详细的过程。转自:http:/...
    99+
    2023-06-02
  • 如何分析Linux内核源码do_fork
    本篇文章为大家展示了如何分析Linux内核源码do_fork,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。我们都知道进程是Linux内核中最为重要的一个抽象概念,那么我们平时在fork一个进程时,该...
    99+
    2023-06-16
  • vue核心面试题实例代码分析
    这篇文章主要介绍了vue核心面试题实例代码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇vue核心面试题实例代码分析文章都会有所收获,下面我们一起来看看吧。1-为什么要在列表中绑定key,有什么作用&nbs...
    99+
    2023-07-04
  • 如何解读Java HashMap核心源码
    这期内容当中小编将会给大家带来有关如何解读Java HashMap核心源码,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。对HashMap实现的源码进行简单的分析。 所使用的HashMap源码的版本信息如下...
    99+
    2023-06-17
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作