java excutorthread_JAVA 线程池ThreadPoolExcutor原理探究

概论

线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络 sockets 等的数量。 例如,线程数一般取 cpu 数量 +2 比较合适,线程数过多会导致额外的线程切换开销。

Java 中的线程池是用 ThreadPoolExecutor 类来实现的. 本文就对该类的源码来分析一下这个类内部对于线程的创建, 管理以及后台任务的调度等方面的执行原理。

先看一下线程池的类图:

9ac1d906d669cd75a7755738f984051a.png

上图的目的主要是为了让大家知道线程池相关类之间的关系,至少赚个眼熟,以后看到不会有害怕的感觉。

Executor 框架接口

Executor 框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架,目的是提供一种将”任务提交”与”任务如何运行”分离开来的机制。

下面是ThreadPoolExeCutor类图。Executors其实是一个工具类,里面提供了好多静态方法,这些方法根据用户选择返回不同的线程实例。

f29ed82368327422afc12762d7f2bc14.png

从上图也可以看出来,ThreadPoolExeCutor 是线程池的核心。

J.U.C 中有三个 Executor 接口:

Executor:一个运行新任务的简单接口;ExecutorService:扩展了 Executor 接口。添加了一些用来管理执行器生命周期和任务生命周期的方法;ScheduledExecutorService:扩展了 ExecutorService。支持 Future 和定期执行任务。其实通过这些接口就可以看到一些设计思想,每个接口的名字和其任务是完全匹配的。不会因为 Executor 中只有一个方法,就将其放到其他接口中。这也是很重要的单一原则。

ThreadPoolExeCutor 分析

在去具体分析 ThreadPoolExeCutor 运行逻辑前,先看下面的流程图:

1dd2fd3f34a200413e6782c0be641871.png

该图是 ThreadPoolExeCutor 整个运行过程的一个概括,整个源码的核心逻辑总结起来就是:

创建线程:要知道如何去创建线程,控制线程数量,线程的存活与销毁;添加任务:任务添加后如何处理,是立刻执行,还是先保存;执行任务:如何获取任务,任务执行失败后如何处理?下面将进入源码分析,来深入理解 ThreadPoolExeCutor 的设计思想。

构造函数

先来看构造函数:

a22242e10f238ca60f5c6831712ecef4.png

corePoolSize 核心线程数:表示核心线程池的大小。当提交一个任务时,如果当前核心线程池的线程个数没有达到 corePoolSize,则会创建新的线程来执行所提交的任务,即使当前核心线程池有空闲的线程。如果当前核心线程池的线程个数已经达到了corePoolSize,则不再重新创建线程。如果调用了 prestartCoreThread() 或者 prestartAllCoreThreads(),线程池创建的时候所有的核心线程都会被创建并且启动。若 corePoolSize == 0,则任务执行完之后,没有任何请求进入时,销毁线程池的线程。若 corePoolSize > 0,即使本地任务执行完毕,核心线程也不会被销毁。corePoolSize 其实可以理解为可保留的空闲线程数。maximumPoolSize: 表示线程池能够容纳同时执行的最大线程数。如果当阻塞队列已满时,并且当前线程池线程个数没有超过 maximumPoolSize 的话,就会创建新的线程来执行任务。注意 maximumPoolSize >= 1 必须大于等于 1。maximumPoolSize == corePoolSize ,即是固定大小线程池。实际上最大容量是由 CAPACITY 控制。keepAliveTime: 线程空闲时间。当空闲时间达到 keepAliveTime值时,线程会被销毁,直到只剩下 corePoolSize 个线程为止,避免浪费内存和句柄资源。默认情况,当线程池的线程数 > corePoolSize 时,keepAliveTime 才会起作用。但当 ThreadPoolExecutor 的 allowCoreThreadTimeOut 变量设置为 true 时,核心线程超时后会被回收。unit:时间单位。为 keepAliveTime 指定时间单位。workQueue 缓存队列。当请求的线程数 > maximumPoolSize时,线程进入 BlockingQueue 阻塞队列。可以使用 ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue。threadFactory创建线程的工程类。可以通过指定线程工厂为每个创建出来的线程设置更有意义的名字,如果出现并发问题,也方便查找问题原因。handler 执行拒绝策略的对象。当线程池的阻塞队列已满和指定的线程都已经开启,说明当前线程池已经处于饱和状态了,那么就需要采用一种策略来处理这种情况。采用的策略有这几种:AbortPolicy: 直接拒绝所提交的任务,并抛出 RejectedExecutionException异常;CallerRunsPolicy:只用调用者所在的线程来执行任务;DiscardPolicy:不处理直接丢弃掉任务;DiscardOldestPolicy:丢弃掉阻塞队列中存放时间最久的任务,执行当前任务属性定义

看完构造函数之后,再来看下该类里面的变量,有助于进一步理解整个代码运行逻辑,下面是一些比较重要的变量:

d16cebe89f1ace4022c7ce7f4c14dfbf.png

这里需要对一些操作做些解释。

Integer.SIZE:对于不同平台,其位数不一样,目前常见的是 32 位;(1 << COUNT_BITS) -1:首先是将 1 左移 COUNT_BITS 位,也就是第 COUNT_BITS + 1 位是1,其余都是 0;-1 操作则是将后面前的 COUNT_BITS 位都变成 1。-1 << COUNT_BITS:-1 的原码是 10000000 00000000 00000000 00000001 ,反码是 111111111 11111111 11111111 11111110 ,补码 +1,然后左移 29 位是 11100000 00000000 00000000 00000000;这里转为十进制是负数。~CAPACITY :取反,最高三位是1;总结:这里巧妙利用 bit 操作来将线程数量和运行状态联系在一起,减少了变量的存在和内存的占用。其中五种状态的十进制排序:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED

线程池状态

线程池状态含义:

RUNNING:接受新任务并且处理阻塞队列里的任务;SHUTDOWN:拒绝新任务但是处理阻塞队列里的任务;STOP:拒绝新任务并且抛弃阻塞队列里的任务同时会中断正在处理的任务;TIDYING:所有任务都执行完(包含阻塞队列里面任务)当前线程池活动线程为 0,将要调用 terminated 方法TERMINATED:终止状态。terminated 方法调用完成以后的状态;线程池状态转换:

RUNNING -> SHUTDOWN:显式调用 shutdown() 方法,或者隐式调用了 finalize(),它里面调用了shutdown()方法。RUNNING or SHUTDOWN)-> STOP:显式 shutdownNow() 方法;SHUTDOWN -> TIDYING:当线程池和任务队列都为空的时候;STOP -> TIDYING:当线程池为空的时候;TIDYING -> TERMINATED:当 terminated() hook 方法执行完成时候;原码,反码,补码知识小剧场

1. 原码:原码就是符号位加上真值的绝对值, 即用第一位表示符号,其余位表示值. 比如如果是 8 位二进制:

[+1]原 = 0000 0001

[-1]原 = 1000 0001

负数原码第一位是符号位.

2. 反码:反码的表示方法是,正数的反码是其本身,负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.

[+1] = [0000 0001]原 = [0000 0001]反

[-1] = [1000 0001]原 = [1111 1110]反

3. 补码:补码的表示方法是,正数的补码就是其本身,负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后 +1. (即在反码的基础上 +1)

[+1] = [0000 0001]原 = [0000 0001]反 = [0000 0001]补

[-1] = [1000 0001]原 = [1111 1110]反 = [1111 1111]补

4. 总结在知道一个数原码的情况下:正数:反码,补码 就是本身自己负数:反码是高位符号位不变,其余位取反。补码:反码+1

5. 左移:当数值左、右移时,先将数值转化为其补码形式,移完后,再转换成对应的原码

左移:高位丢弃,低位补零

[+1] = [00000001]补

[0000 0001]补 << 1 = [0000 0010]补 = [0000 0010]原 = [+2]

[-1] = [1000 0001]原 = [1111 1111]补

[1111 1111]补 << 1 = [1111 1110]补 = [1000 0010]原 = [-2]

其中,再次提醒,负数的补码是反码+1;负数的反码是补码-1;

6. 右移:高位保持不变,低位丢弃

[+127] = [0111 1111]原 = [0111 1111]补

[0111 1111]补 >> 1 = [0011 1111]补 = [0011 1111]原 = [+63]

[-127] = [1111 1111]原 = [1000 0001]补

[1000 0001]补 >> 1 = [1100 0000]补 = [1100 0000]原 = [-64]

execute 方法分析

通过 ThreadPoolExecutor 创建线程池后,提交任务后执行过程是怎样的,下面来通过源码来看一看。execute 方法源码如下:

82f5b855fb222d4a478fe8879a53c0aa.png

execute 方法执行逻辑有这样几种情况:

如果当前运行的线程少于 corePoolSize,则会创建新的线程来执行新的任务;如果运行的线程个数等于或者大于 corePoolSize,则会将提交的任务存放到阻塞队列 workQueue 中;如果当前 workQueue 队列已满的话,则会创建新的线程来执行任务;如果线程个数已经超过了 maximumPoolSize,则会使用饱和策略 RejectedExecutionHandler 来进行处理。这里要注意一下 addWorker(null, false) 也就是创建一个线程,但并没有传入任务,因为任务已经被添加到 workQueue 中了,所以 worker 在执行的时候,会直接从 workQueue 中获取任务。所以,在 workerCountOf(recheck) == 0 时执行 addWorker(null, false) 也是为了保证线程池在 RUNNING 状态下必须要有一个线程来执行任务。

需要注意的是,线程池的设计思想就是使用了核心线程池 corePoolSize,阻塞队列 workQueue 和线程池 maximumPoolSize,这样的缓存策略来处理任务,实际上这样的设计思想在需要框架中都会使用。

需要注意线程和任务之间的区别,任务是保存在 workQueue 中的,线程是从线程池里面取的,由 CAPACITY 控制容量。

addWorker 方法分析

addWorker 方法的主要工作是在线程池中创建一个新的线程并执行,firstTask 参数用于指定新增的线程执行的第一个任务,core 参数为 true 表示在新增线程时会判断当前活动线程数是否少于 corePoolSize,false 表示新增线程前需要判断当前活动线程数是否少于 maximumPoolSize,代码如下:

1efe0e61836d642c37fae15495cf666c.png

6dfba1973c67b5aa4357b0f1b7a5bf84.png

2794755c99cff49a1a7e7da24cac9895.png

这里需要注意有以下几点:

在获取锁后重新检查线程池的状态,这是因为其他线程可可能在本方法获取锁前改变了线程池的状态,比如调用了shutdown方法。添加成功则启动任务执行。t.start()会调用 Worker 类中的 run 方法,Worker 本身实现了 Runnable 接口。原因在创建线程得时候,将 Worker 实例传入了 t 当中,可参见 Worker 类的构造函数。wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) 每次调用 addWorker 来添加线程会先判断当前线程数是否超过了CAPACITY,然后再去判断是否超 corePoolSize 或 maximumPoolSize,说明线程数实际上是由 CAPACITY 来控制的。内部类 Worker 分析

上面分析过程中,提到了一个 Worker 类,对于某些对源码不是很熟悉得同学可能有点不清楚,下面就来看看 Worker 的源码:

c8c036f6c6734da805fadfc6c32a40d9.png

a73615680d8469db8d38de3a7fe7c114.png

首先看到的是 Worker 继承了(AbstractQueuedSynchronizer) AQS,并实现了 Runnable 接口,说明 Worker 本身也是线程。然后看其构造函数可以发现,内部有两个属性变量分别是 Runnable 和 Thread 实例,该类其实就是对传进来得属性做了一个封装,并加入了获取锁的逻辑(继承了 AQS )。具体可参考文章:透过 ReentrantLock 分析 AQS 的实现原理

Worker 继承了 AQS,使用 AQS 来实现独占锁的功能。为什么不使用 ReentrantLock 来实现呢?可以看到 tryAcquire 方法,它是不允许重入的,而 ReentrantLock 是允许重入的:

lock 方法一旦获取了独占锁,表示当前线程正在执行任务中;如果正在执行任务,则不应该中断线程;如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断;线程池在执行 shutdown 方法或 tryTerminate 方法时会调用 interruptIdleWorkers 方法来中断空闲的线程,interruptIdleWorkers 方法会使用 tryLock 方法来判断线程池中的线程是否是空闲状态;之所以设置为不可重入,是因为我们不希望任务在调用像 setCorePoolSize 这样的线程池控制方法时重新获取锁。如果使用 ReentrantLock,它是可重入的,这样如果在任务中调用了如 setCorePoolSize 这类线程池控制的方法,会中断正在运行的线程,因为 size 小了,需要中断一些线程 。所以,Worker 继承自 AQS,用于判断线程是否空闲以及是否可以被中断。

此外,在构造方法中执行了 setState(-1);,把 state 变量设置为 -1,为什么这么做呢?是因为 AQS 中默认的 state 是 0,如果刚创建了一个 Worker 对象,还没有执行任务时,这时就不应该被中断,看一下 tryAquire 方法:

e1a080694630111c37b94f9a93131d40.png

正因为如此,在 runWorker 方法中会先调用 Worker 对象的 unlock 方法将 state 设置为 0。tryAcquire 方法是根据 state 是否是 0 来判断的,所以,setState(-1);

将 state 设置为 -1 是为了禁止在执行任务前对线程进行中断。

runWorker 方法分析

前面提到了内部类 Worker 的 run 方法调用了外部类 runWorker,下面来看下 runWork 的具体逻辑。

2d3cf765937e285b4dbf302dd0e7e346.png

50a2d4882c3565858de4aefdf6cb67ea.png

总结一下 runWorker 方法的执行过程:

while 循环不断地通过 getTask() 方法从阻塞队列中取任务;如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态;调用 task.run()执行任务;如果 task 为 null 则跳出循环,执行 processWorkerExit 方法;runWorker 方法执行完毕,也代表着 Worker 中的 run 方法执行完毕,销毁线程。这里的 beforeExecute 方法和 afterExecute 方法在 ThreadPoolExecutor 类中是空的,留给子类来实现。

completedAbruptly 变量来表示在执行任务过程中是否出现了异常,在 processWorkerExit 方法中会对该变量的值进行判断。

getTask 方法分析

getTask 方法是从阻塞队列里面获取任务,具体代码逻辑如下:

9930c880f0b3da901903721ca340004e.png

4ee2768114609e5c0b07c1e7a039047e.png

其实到这里后,你会发现在 ThreadPoolExcute 内部有几个重要的检验:

判断当前的运行状态,根据运行状态来做处理,如果当前都停止运行了,那很多操作也就没必要了;判断当前线程池的数量,然后将该数据和 corePoolSize 以及 maximumPoolSize 进行比较,然后再去决定下一步该做啥;首先是第一个 if 判断,当运行状态处于非 RUNNING 状态,此外 rs >= STOP(线程池是否正在 stop)或阻塞队列是否为空。则将 workerCount 减 1 并返回 null。为什么要减 1 呢,因为此处其实是去获取一个 task,但是发现处于停止状态了,也就是没必要再去获取运行任务了,那这个线程就没有存在的意义了。后续也会在 processWorkerExit 将该线程移除。

第二个 if 条件目的是控制线程池的有效线程数量。由上文中的分析可以知道,在执行 execute 方法时,如果当前线程池的线程数量超过了 corePoolSize 且小于 maximumPoolSize,并且 workQueue 已满时,则可以增加工作线程,但这时如果超时没有获取到任务,也就是 timedOut 为 true 的情况,说明 workQueue 已经为空了,也就说明了当前线程池中不需要那么多线程来执行任务了,可以把多于 corePoolSize 数量的线程销毁掉,保持线程数量在 corePoolSize 即可。

什么时候会销毁?当然是 runWorker 方法执行完之后,也就是 Worker 中的 run 方法执行完,由 JVM 自动回收。

getTask 方法返回 null 时,在 runWorker 方法中会跳出 while 循环,然后会执行 processWorkerExit 方法。

processWorkerExit 方法

下面在看 processWorkerExit 方法的具体逻辑:

22c20c22be16cc26f0fd2d1779d8180e.png

至此,processWorkerExit 执行完之后,工作线程被销毁,以上就是整个工作线程的生命周期。但是这有两点需要注意:

大家想想什么时候才会调用这个方法,任务干完了才会调用。那么没事做了,就需要看下是否有必要结束线程池,这时候就会调用 tryTerminate。如果此时线程处于 STOP 状态以下,那么就会判断核心线程数是否达到了规定的数量,没有的话,就会继续创建一个线程。tryTerminate方法

tryTerminate 方法根据线程池状态进行判断是否结束线程池,代码如下:

7f668bff22443e11aaed91cc93c7635d.png

interruptIdleWorkers(boolean onlyOne) 如果 ONLY_ONE = true 那么就的最多让一个空闲线程发生中断,ONLY_ONE = false 时是所有空闲线程都会发生中断。那线程什么时候会处于空闲状态呢?

一是线程数量很多,任务都完成了;二是线程在 getTask 方法中执行 workQueue.take() 时,如果不执行中断会一直阻塞。

所以每次在工作线程结束时调用 tryTerminate 方法来尝试中断一个空闲工作线程,避免在队列为空时取任务一直阻塞的情况。

shutdown方法

shutdown 方法要将线程池切换到 SHUTDOWN 状态,并调用 interruptIdleWorkers 方法请求中断所有空闲的 worker,最后调用 tryTerminate 尝试结束线程池。

4c43cb2c8ee9e973378b2e82078b887a.png

这里思考一个问题:在 runWorker 方法中,执行任务时对 Worker 对象 w 进行了 lock 操作,为什么要在执行任务的时候对每个工作线程都加锁呢?

下面仔细分析一下:

在 getTask 方法中,如果这时线程池的状态是 SHUTDOWN 并且 workQueue 为空,那么就应该返回 null 来结束这个工作线程,而使线程池进入 SHUTDOWN 状态需要调用shutdown 方法;shutdown 方法会调用 interruptIdleWorkers 来中断空闲的线程,interruptIdleWorkers 持有 mainLock,会遍历 workers 来逐个判断工作线程是否空闲。但 getTask 方法中没有mainLock;在 getTask 中,如果判断当前线程池状态是 RUNNING,并且阻塞队列为空,那么会调用 workQueue.take() 进行阻塞;如果在判断当前线程池状态是 RUNNING 后,这时调用了 shutdown 方法把状态改为了 SHUTDOWN,这时如果不进行中断,那么当前的工作线程在调用了 workQueue.take() 后会一直阻塞而不会被销毁,因为在 SHUTDOWN 状态下不允许再有新的任务添加到 workQueue 中,这样一来线程池永远都关闭不了了;由上可知,shutdown 方法与 getTask 方法(从队列中获取任务时)存在竞态条件;解决这一问题就需要用到线程的中断,也就是为什么要用 interruptIdleWorkers 方法。在调用 workQueue.take() 时,如果发现当前线程在执行之前或者执行期间是中断状态,则会抛出 InterruptedException,解除阻塞的状态;但是要中断工作线程,还要判断工作线程是否是空闲的,如果工作线程正在处理任务,就不应该发生中断;所以 Worker 继承自 AQS,在工作线程处理任务时会进行 lock,interruptIdleWorkers 在进行中断时会使用 tryLock 来判断该工作线程是否正在处理任务,如果 tryLock 返回 true,说明该工作线程当前未执行任务,这时才可以被中断。下面就来分析一下 interruptIdleWorkers 方法。

interruptIdleWorkers方法

6bb50c77094f0a477df81e9051374930.png

interruptIdleWorkers 遍历 workers 中所有的工作线程,若线程没有被中断 tryLock 成功,就中断该线程。

为什么需要持有 mainLock ?因为 workers 是 HashSet 类型的,不能保证线程安全。

shutdownNow方法

e9ab001893b66e3dfa3c241153d3e9b7.png

shutdownNow 方法与 shutdown 方法类似,不同的地方在于:

设置状态为 STOP;中断所有工作线程,无论是否是空闲的;取出阻塞队列中没有被执行的任务并返回。shutdownNow 方法执行完之后调用 tryTerminate 方法,该方法在上文已经分析过了,目的就是使线程池的状态设置为 TERMINATED。

线程池的监控

通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用

getTaskCount:线程池已经执行的和未执行的任务总数;getCompletedTaskCount:线程池已完成的任务数量,该值小于等于 taskCount;getLargestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过,也就是达到了maximumPoolSize;getPoolSize:线程池当前的线程数量;getActiveCount:当前线程池中正在执行任务的线程数量。通过这些方法,可以对线程池进行监控,在 ThreadPoolExecutor 类中提供了几个空方法,如 beforeExecute 方法,afterExecute 方法和 terminated 方法,可以扩展这些方法在执行前或执行后增加一些新的操作,例如统计线程池的执行任务的时间等,可以继承自 ThreadPoolExecutor 来进行扩展。

到此,关于 ThreadPoolExecutor 的内容就讲完了。

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

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

相关文章

websocket连接mysql_websocket 使用 spring 的service层 ,进而调用里面的 dao层 来操作数据库 ,包括redis、mysql等通用...

1.前言描述一下今天用websocket踩得坑 ---》空指针异常&#xff01;我想在websocket里面使用service 层的接口&#xff0c;从中获取数据库的一些信息 &#xff0c;使用 Autowired 注解 接口 &#xff0c;报错 空指针异常 &#xff01;&#xff01;&#xff01;查过资料才发…

世上最简单的mysql_最简单易懂的mysql安装教程

今天安装MySQL花了蛮长时间的&#xff0c;感觉坑还是挺多的&#xff0c;写遍文章总结一下。一、安装1.解压zip包到安装目录先从MySQL官网 下载mysql最新的免安装版压缩包解压之后变成这个样子&#xff1a;里面的文件是这个样子下面开始了&#xff0c;请注意2.配置文件在安装目录…

aix系统升级失败提示java_AIX系统补丁升级失败处理

问题描述&#xff1a;现网一台IBM P550小型机&#xff0c;初始版本通过oslevel –r命令检查为5300-02&#xff0c;在IBM官方网站下载5300-06补丁并升级到5300-06后系统报错&#xff0c;缺少sysmgt.websm.apps 5.3.0.60&#xff0c;sysmgt.websm.rte 5.3.0.60两个文件问题处理&a…

java arraylist char,Java基础学习笔记六 Java基础语法之类和ArrayList详解

引用数据类型引用数据类型分类&#xff0c;提到引用数据类型(类)&#xff0c;其实我们对它并不陌生&#xff0c;如使用过的Scanner类、Random类。我们可以把类的类型为两种&#xff1a;第一种&#xff0c;Java为我们提供好的类&#xff0c;如Scanner类&#xff0c;Random类等&a…

matlab 数值解 期权顶级啊,潮盈期权院高胜率交易技巧系列之二----期权交易策略及基于MATLAB统计套利介绍...

主题&#xff1a;高胜率交易技巧系列之二----期权交易策略及基于MATLAB统计套利介绍会场流程&#xff1a;13:30--14:00&#xff1a;参会嘉宾到场签名14:00--14:45&#xff1a;期权知识14:45--15:25&#xff1a;期权交易策略使用15:25--15:35&#xff1a;茶歇15:35--16:35&#…

php的用例图箭头怎么画,需求中如何画用例图 - china008的个人空间 - OSCHINA - 中文开源技术交流社区...

UML用例图用例图主要用来图示化系统的主事件流程&#xff0c;它主要用来描述客户的需求&#xff0c;即用户希望系统具备的完成一定功能的动作&#xff0c;通俗地理解用例就是软件的功能模块&#xff0c;所以是 设计系统分析阶段的起点&#xff0c;设计人员根据客户的需求来创建…

oracle学习数据,Oracle从入门到精通的学习笔记

本次知识点:1.认识SQL的介绍2.掌握scott用户的数据结构3.查询语句之简单查询1.SQL:SQL是指结构化查询语言,在80年代的时候,基本存在80多种数据库,每一种数据库都有自己的的操作命令,也就导致了程序员从一个数据库到另一个数据库的转化时变的极为麻烦,基本就要从新学习.在70年代…

强行更改linux服务器时间,加强Linux服务器安全的20项建议

一般情况下用 Linux 做桌面在默认配置下是很安全的&#xff0c;我在一定程度上同意这个说法(很值得商榷的话题)。不过 Linux 内置的安全模型和工具做得确实很到位&#xff0c;用户只需进行简单的调整和自定义就可以加强 Linux 服务器安全。与恶意用户做斗争对于所有 Linux 系统…

linux终端提示符含义,Linux:终端提示符 (prompt) 不如期生效原因

前言先来简单介绍下, prompt是什么鬼? 顾名思义就是提示符的意思, 看起来和我们遥远, 但实际上只要是每个接触shell的童鞋, 都有看到, 那就是我们在输命令时前面的那串提示符.例如:当然, 这个样式是可以修改的, 这就涉及到我们的PS1和PS2了, 有经验或者以前有设置过的童鞋估计…

skyeye linux qt,ARM仿真器SkyEye的安装及使用

SkyEye是一个开源软件(OpenSource Software)项目&#xff0c;中文名字是"天目"。SkyEye的目标是在通用的Linux和Windows平台上实现一个纯软件集成开发环境&#xff0c;模拟常见的嵌入式系统&#xff0c;可在SkyEye上运行μCLinux以及μC/OS-II等&#xff0c;以及各种…

2g 双核电脑 linux,9208)(奔腾双核E5200/2G/320G)电脑详细技术

处理器型&#xff1a;intel 酷睿2双核 p7350 intel 酷睿2双核 p7450 intel 酷睿2双核 t6600 intel 奔腾双核t4300 intel 奔腾双核 t4400 intel 赛扬双核 t1600 intel 赛扬双核 t3000操作系统&#xff1a;windowsvista home basic dos标配内存&#xff1a;1gb 2gb 硬盘容量&…

linux apache 大文件,Apache下error.log文件太大的处理方法

Apache下error.log文件太大的处理方法2019年05月03日| 萬仟网网络运营| 我要评论清除error.log、access.log并限制apache日志文件大小的方法 有个客户反映vps网站打不开&#xff0c;登录系统检查下&#xff0c;客户是win2003系统&#xff0c;发现放置网站文件的清除error.log、…

c语言程序设计实践课选题,c语言程序设计实践实验题目

c语言程序设计实践实验题目 绥化学院程序设计实践实验报告范例 参考1实验题目&#xff1a;循环结构程序设计实验目的&#xff1a;1.熟悉 VC6.0 的运行环境&#xff0c;掌握 C 程序的执行方法&#xff1b;2.掌握三种基本数据类型、部分运算符号和常用函数&#xff1b;3.掌握三种…

c语言上机指导答案清华,第一章自测练习答案清华大学c语言习题实验指导及课程设计...

第一章自测练习答案清华大学c语言习题实验指导及课程设计 第一章 自测练习参考答案 一&#xff0e;简答题 1&#xff0e;源程序是程序员创建的&#xff0c;目标程序是编译器创建的&#xff0c;可执行程序是连接器创建的。 2&#xff0e;步骤如下&#xff1a;1)说明程序需求&…

k6前级效果器怎么用_新手学习电吉他,效果器应选择单块还是综合?

作为一个新手来说&#xff0c;我建议一开始先选择综合型效果器。这里我们先简单介绍下综合效果器与单块效果器。综合效果器可以理解为多个单块的综合体&#xff0c;即包含了很多种效果。一块大综合效果器可能就有三四十种预置音色&#xff0c;有过载、失真、法滋、延迟、混响、…

android百度多渠道打包,Android多渠道打包方案的实践与优化

目前使用过的多渠道打包方式有两种 &#xff0c;一种是通过gradle打包&#xff0c;还有一种是美团的多渠道打包方案具体详情见这里1、Gradle打包1.1、在Androidmanifest.xml中添加android:name"UMENG_CHANNEL"android:value"${UMENG_CHANNEL_VALUE}" />…

一张图 综合交通 解决方案_航天智慧环保 | 重庆跳蹬河智慧水务管理系统解决方案—应用篇...

导读随着社会信息化趋势的不断增强&#xff0c;信息社会作为人类社会环境的一个重要方面&#xff0c;在社会中的地位和作用日益显著。在环境监测领域&#xff0c;对有效信息的把握以及正确的使用与水污染的防治和水环境的管理密切相关。航天智慧公司发挥航天系统工程优势&#…

oc 画一个圆弧_用SolidWorks画的塑料衣架,要用到不少让人头疼的曲线

此图是用SolidWorks2015建模&#xff0c;用KeyShot 8渲染。SW文件在2019年12月3日文件夹。零件一&#xff1a;1.在前视基准面上画样条曲线。2.在右视基准面上画样条曲线。(曲面放样的轮廓)3.新建基准面&#xff0c;参考样条曲线和紫色端点。4.在新基准面上画样条曲线。5.新建基…

红米android4.4.2,新版红米Note配置升级详解:系统其实是基于Android 4.4.2

新版红米Note今天中午&#xff0c;小米正式发布了4G版红米Note&#xff0c;由于该机早就出现在了工信部&#xff0c;因此较早一些的传闻显示该机在7月22日的发布会上就会和我们见面&#xff0c;但最终还是让我们多等了小半个月。和原版有所不同的是&#xff0c;4G版红米Note只有…

批量引号_RcoketMQ 批量发送和消息过滤

一、批量发送消息1、批量发送消息要求参考官方文档&#xff1a;http://rocketmq.apache.org/docs/batch-example/① 不能是延迟消息② 有相同的 Topic 和 waitStoreMsgOK(默认是 true&#xff0c;消息发送时&#xff0c;是否等待消息存储完成后再返回)③ 消息总大小不超过 1MB2…