java并发编程——线程池的工作原理与源码解读

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

线程池的简单介绍

基于多核CPU的发展,使得多线程开发日趋流行。然而线程的创建和销毁,都涉及到系统调用,比较消耗系统资源,所以就引入了线程池技术,避免频繁的线程创建和销毁。

在Java用有一个Executors工具类,可以为我们创建一个线程池,其本质就是new了一个ThreadPoolExecutor对象。

建议使用较为方便的 Executors 工厂方法来创建线程池。

  • Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)
  • Executors.newFixedThreadPool(int)(固定大小线程池)
  • Executors.newSingleThreadExecutor()(单个后台线程)。
  • Executors.newScheduledThreadPool() (支持计划任务的线程池)

ThreadPoolExecutor工作原理介绍

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}
  1. corePoolSize:线程池的核心线程数,说白了就是,即便是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。
  2. maximumPoolSize:最大线程数,不管你提交多少任务,线程池里最多工作线程数就是maximumPoolSize。
  3. keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程退出。
  4. unit:这个用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS。
  5. workQueue:一个阻塞队列,提交的任务将会被放到这个队列里。
  6. threadFactory:线程工厂,用来创建线程,主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。
  7. handler:拒绝策略,当线程池里线程被耗尽,且队列也满了的时候会调用。

线程池的执行流程图

任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,如果小于则创建线程来执行提交的任务,否则将任务放入workQueue队列,如果workQueue满了,则判断当前线程数量是否小于maximumPoolSize,如果小于则创建线程执行任务,否则就会调用handler,以表示线程池拒绝接收任务。

线程池使用介绍

newScheduledThreadPool的使用示例

public class SchedulePoolDemo {public static void main(String[] args){ScheduledExecutorService service = Executors.newScheduledThreadPool(10);//如果前面的任务没有完成, 调度也不会启动service.scheduleAtFixedRate(()->{try {Thread.sleep(2000);// 每两秒打印一次.System.out.println(System.currentTimeMillis()/1000);} catch (InterruptedException e) {e.printStackTrace();}}, 0, 2, TimeUnit.SECONDS);}
}

潜在宕机风险

使用Executors来创建要注意潜在宕机风险.其返回的线程池对象的弊端如下:

  • FixedThreadPool和SingleThreadPoolPool : 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM.
  • CachedThreadPool和ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM.

综上所述, 在可能有大量请求的线程池场景中, 更推荐自定义ThreadPoolExecutor来创建线程池, 具体构造函数配置如下:

线程池大小配置

一般根据任务类型进行区分, 假设CPU为N核

  • CPU密集型任务需要减少线程数量, 降低线程之间切换造成的开销, 可配置线程池大小为N + 1.
  • IO密集型任务则可以加大线程数量, 可配置线程池大小为 N * 2.
  • 混合型任务则可以拆分为CPU密集型与IO密集型, 独立配置.

自定义阻塞队列BlockingQueue

主要存放等待执行的线程, ThreadPoolExecutor中支持自定义该队列来实现不同的排队队列.

  • ArrayBlockingQueue:先进先出队列,创建时指定大小, 有界;
  • LinkedBlockingQueue:使用链表实现的先进先出队列,默认大小为Integer.MAX_VALUE;
  • SynchronousQueue:不保存提交的任务, 数据也不会缓存到队列中, 用于生产者和消费者互等对方, 一起离开.
  • PriorityBlockingQueue: 支持优先级的队列

回调接口

线程池提供了一些回调方法, 具体使用如下所示.

ExecutorService service = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>()) {@Overrideprotected void beforeExecute(Thread t, Runnable r) {System.out.println("准备执行任务: " + r.toString());}@Overrideprotected void afterExecute(Runnable r, Throwable t) {System.out.println("结束任务: " + r.toString());}@Overrideprotected void terminated() {System.out.println("线程池退出");}};

可以在回调接口中, 对线程池的状态进行监控, 例如任务执行的最长时间, 平均时间, 最短时间等等, 还有一些其他的属性如下:

  • taskCount:线程池需要执行的任务数量.
  • completedTaskCount:线程池在运行过程中已完成的任务数量.小于或等于taskCount.
  • largestPoolSize:线程池曾经创建过的最大线程数量.通过这个数据可以知道线程池是否满过.如等于线程池的最大大小,则表示线程池曾经满了.
  • getPoolSize:线程池的线程数量.如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减.
  • getActiveCount:获取活动的线程数.

自定义拒绝策略

线程池满负荷运转后, 因为时间空间的问题, 可能需要拒绝掉部分任务的执行.

jdk提供了RejectedExecutionHandler接口, 并内置了几种线程拒绝策略

  • AbortPolicy: 直接拒绝策略, 抛出异常.
  • CallerRunsPolicy: 调用者自己执行任务策略.
  • DiscardOldestPolicy: 舍弃最老的未执行任务策略. 使用方式也很简单, 直接传参给ThreadPool
ExecutorService service = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("reject task: " + r.toString());}});

自定义ThreadFactory

线程工厂用于创建池里的线程. 例如在工厂中都给线程setDaemon(true), 这样程序退出的时候, 线程自动退出.或者统一指定线程优先级, 设置名称等等.

class NamedThreadFactory implements ThreadFactory {private static final AtomicInteger threadIndex = new AtomicInteger(0);private final String baseName;private final boolean daemon;public NamedThreadFactory(String baseName) {this(baseName, true);}public NamedThreadFactory(String baseName, boolean daemon) {this.baseName = baseName;this.daemon = daemon;}public Thread newThread(Runnable runnable) {Thread thread = new Thread(runnable, this.baseName + "-" + threadIndex.getAndIncrement());thread.setDaemon(this.daemon);return thread;}
}

关闭线程池

跟直接new Thread不一样, 局部变量的线程池, 需要手动关闭, 不然会导致线程泄漏问题.默认提供两种方式关闭线程池.- shutdown: 等所有任务, 包括阻塞队列中的执行完, 才会终止, 但是不会接受新任务.
- shutdownNow: 立即终止线程池, 打断正在执行的任务, 清空队列.

ThreadPoolExecutor源码分析

ThreadPoolExecutor中ctl属性介绍

ctl是ThreadPoolExecutor的一个重要属性,它记录着ThreadPoolExecutor的线程数量和线程状态。

//Integer有32位,其中前三位用于记录线程状态,后29位用于记录线程的数量.
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//表示用于记录线程数量的位数
private static final int COUNT_BITS = Integer.SIZE - 3;
//将1左移COUNT_BITS位再减1,表示能表示的最大线程数
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
//用ctl前三位分别表示线程池的状态
//(前三位为111)接受新任务并且处理已经进入队列的任务
private static final int RUNNING    = -1 << COUNT_BITS;
//(前三位为000)不接受新任务,但是处理已经进入队列的任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//(前三位001)不接受新任务,不处理已经进入队列的任务,并且中断正在执行的任务
private static final int STOP       =  1 << COUNT_BITS;
//(前三位010)所有任务执行完成,workerCount为0。线程转到了状态TIDYING会执行terminated()钩子方法
private static final int TIDYING    =  2 << COUNT_BITS;
//(前三位011)任务已经执行完成
private static final int TERMINATED =  3 << COUNT_BITS;
//状态值就是只关心前三位的值,所以把后29位清0
private static int runStateOf(int c)     { return c & ~CAPACITY; }//线程数量就是只关心后29位的值,所以把前3位清0
private static int workerCountOf(int c)  { return c & CAPACITY; }//两个数相或
private static int ctlOf(int rs, int wc) { return rs | wc; }

execute()方法解析

 public void execute(Runnable command) {if (command == null) throw new NullPointerException();int c = ctl.get();//判断当前活跃线程数是否小于corePoolSizeif (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))//调用addWorker创建线程执行任务return;c = ctl.get();}//如果不小于corePoolSize,则将任务添加到workQueue队列。if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();//再次获取ctl的状态//如果不在运行状态了,那么就从队列中移除任务if (! isRunning(recheck) && remove(command))reject(command);//如果在运行阶段,但是Worker数量为0,调用addWorker方法else if (workerCountOf(recheck) == 0)addWorker(null, false);}//尝试创建非核心线程如果创建失败就会调用reject拒绝接受任务。else if (!addWorker(command, false))reject(command);}
//调用handler的rejectedExecution(command,this)方法。handler是RejectedExecutionHandler接口,默认实现是AbortPolicy
final void reject(Runnable command) {handler.rejectedExecution(command, this);
}

addWorker()方法解析

addWorker方法用于创建线程,并且通过core参数表示该线程是否是核心线程,如果返回true则表示创建成功,否则失败。addWorker的代码如下所示:

private boolean addWorker(Runnable firstTask, boolean core) {retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);//得到线程池的运行状态// rs>=SHUTDOWN为false,即线程池处于RUNNING状态.// rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()这个条件为true,也就意味着三个条件同时满足,即线程池状态为SHUTDOWN且firstTask为null且队列不为空,这种情况为处理队列中剩余任务。上面提到过当处于SHUTDOWN状态时,不接受新任务,但是会处理完队列里面的任务。如果firstTask不为null,那么就属于添加新任务;如果firstTask为null,并且队列为空,那么就不需要再处理了。if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;for (;;) {int wc = workerCountOf(c);if (wc >= CAPACITY ||//如果创建的是非核心线程(core=false)时,则需要判断当前线程数wc>=maximumPoolSize,如果返回false,创建非核心线程失败。//如果创建的是核心线程(core=true)时,则需要判断当前线程数wc>=corePoolSize,如果返回false,创建核心线程失败。wc >= (core ? corePoolSize : maximumPoolSize))return false;if (compareAndIncrementWorkerCount(c))//worker+1执行成功,那么跳出外循环break retry;c = ctl.get();if (runStateOf(c) != rs)//再次判断当前状态,如果新获取的状态和当前状态不一致,则再次进入外循环continue retry;// else CAS failed due to workerCount change; retry inner loop}}/*
一旦跳出外循环,表示可以创建创建线程,这里具体是Worker对象,Worker实现了Runnable接口并且继承AbstractQueueSynchronizer,内部维持一个Runnable的队列。try块中主要就是创建Worker对象,然后将其保存到workers中,workers是一个HashSet,表示工作线程的集合。然后如果添加成功,则开启Worker所在的线程。如果开启线程失败,则调用addWorkerFailed方法,addWokerFailed用于回滚worker线程的创建。
*/boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {//以firstTask作为Worker的第一个任务创建Workerw = new Worker(firstTask);final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();//对整个线程池加锁try {int rs = runStateOf(ctl.get());if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {if (t.isAlive()) // precheck that t is startablethrow new IllegalThreadStateException();workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {t.start();//启动启动这个线程workerStarted = true;}}} finally {if (! workerStarted)addWorkerFailed(w);}return workerStarted;}

addWorkerFailed()方法解析

 private void addWorkerFailed(Worker w) {//对整个线程成绩加锁final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {//移除Worker对象if (w != null)workers.remove(w);//减小worker数量decrementWorkerCount();//检查termination状态tryTerminate();} finally {mainLock.unlock();}}

addWorkerFailed首先从workers集合中移除线程,然后将wokerCount减1,最后检查终结。

tryTerminate()方法解析

tryTerminate()方法用于检查是否有必要将线程池状态转移到TERMINATED。

final void tryTerminate() {for (;;) {int c = ctl.get();/*状态判断,如果有符合以下条件之一。则跳出循环(1)线程池处于RUNNING状态(2)线程池状态处于TIDYING状态(3)线程池状态处于SHUTDOWN状态并且队列不为空
如果不满足上述的情况,那么目前状态属于SHUTDOWN切队列为空,或者状态属于STOP,那么调用interruptIdleWorkers方法停止一个Worker线程,然后退出。*/if (isRunning(c) ||runStateAtLeast(c, TIDYING) ||(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))return;if (workerCountOf(c) != 0) { // Eligible to terminateinterruptIdleWorkers(ONLY_ONE);return;}
/*
如果没有退出循环的话,那么就首先将状态设置成TIDYING,然后调用terminated方法,最后设置状态为TERMINATED。terminated方法是个空实现,用于当线程池终结时处理一些事情。
*/final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {try {terminated();} finally {ctl.set(ctlOf(TERMINATED, 0));termination.signalAll();}return;}} finally {mainLock.unlock();}// else retry on failed CAS}}

构造函数Worker(firstTask)解析

Worker继承自AbstractQueuedSynchronizer并实现Runnbale接口。AbstractQueuedSynchronizer提供了一个实现阻塞锁和其他同步工具,比如信号量、事件等依赖于等待队列的框架。Worker的构造方法中会使用threadFactory构造线程变量并持有run方法调用了runWorker方法,将线程委托给主循环线程。

Worker(Runnable firstTask) {setState(-1);this.firstTask = firstTask;//设置该线程的this.thread = getThreadFactory().newThread(this);//创建一个线程
}//当我我们启动一个线程时就会触发Worker中的此方法
public void run() {runWorker(this);
}

runWorker()方法解析

final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;//首次任务是创建Worker时添加的任务w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {//线程调用runWoker,会while循环调用getTask方法从workerQueue里读取任务,然后执行任务。只要getTask方法不返回null,此线程就不会退出。while (task != null || (task = getTask()) != null) {w.lock();//对Worker加锁//如果线程池停止了,那么中断线程if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {beforeExecute(wt, task);//空实现,任务运行之前的操作Throwable thrown = null;try {task.run();//执行任务} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {afterExecute(task, thrown);//空实现,任务运行之后的操作}} finally {task = null;//任务执行完毕后,将task设置为nullw.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}}

getTask()方法解析

private Runnable getTask() {boolean timedOut = false; // Did the last poll() time out?for (;;) {int c = ctl.get();int rs = runStateOf(c);//必要时检查队列是否为空if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}int wc = workerCountOf(c);//判断是否允许超时,wc>corePoolSize则是判断当前线程数是否大于corePoolSize。boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))return null;continue;}try {//如果当前线程数大于corePoolSize,//则会调用workQueue的poll方法获取任务,超时时间是keepAliveTime。//如果超过keepAliveTime时长,poll返回了null,//上边提到的while循序就会退出,线程也就执行完了。//如果当前线程数小于corePoolSize,//则会调用workQueue的take方法阻塞当前线程,不会退出Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r != null)return r;timedOut = true;} catch (InterruptedException retry) {timedOut = false;}}}

参考地址:

  • http://www.cnblogs.com/qingquanzi/p/8146638.html
  • https://blog.csdn.net/qq_19431333/article/details/59030892
  • https://www.cnblogs.com/xdecode/p/9119794.html

转载于:https://my.oschina.net/cqqcqqok/blog/2049249

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

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

相关文章

php pcre回溯攻击,php preg_match pcre回溯绕过

原理需要知识:正则NFA回溯原理&#xff0c;php的pcre.backtrack_limit设置。正则NFA回溯原理正则表达式是一个可以被"有限状态自动机"接受的语言类。"有限状态自动机",拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移…

电驴更新地址

emule是通过ED2K网络和KAD网络寻找、连接其他emule客户端的&#xff0c;所以服务器列表和KAD节点文件是emule的必需文件。 有些新手由于下载官方原版emule压缩包或其他未集成这些必需文件的emule压缩包&#xff0c;从而出现“连接不上ED2K与KAD”问题。所以学会下载更新服务器…

Vue CLI 3 可以使用 TypeScript 生成新工程

TypeScript 支持 在 Vue 2.5.0 中&#xff0c;我们大大改进了类型声明以更好地使用默认的基于对象的 API。同时此版本也引入了一些其它变化&#xff0c;需要开发者作出相应的升级。阅读博客文章了解更多详情。 发布为 NPM 包的官方声明文件 静态类型系统能帮助你有效防止许多潜…

手机端本地图片或者拍照的上传功能

原文连接 https://blog.csdn.net/m0_37852904/article/details/78550136 ---------------------------------------------------------- 最近刚好在做手机端的图片上传功能&#xff0c;便记录下 html&#xff1a; <input type"file" class"hide" i…

php scandir sftp,CentOS 下使用SFTP实现网站自动生成FTP账号,实现Chroot功能

背景 手上有一个这样的系统&#xff1a;后台可以直接新建项目(网站)&#xff0c;只需输入项目名称、访问域名(二级)以及其他一些额外信息&#xff0c;就可自动生成一个模板网站。大致原理是&#xff1a;提交这些信息的时候&#xff0c;后台会给项目新建一个目录&#xff0c;并把…

IOS内购详解

介绍 最近开发的一款APP上架被驳回了&#xff0c;理由是&#xff1a; 上架的APP是培训类&#xff0c;里面金牌视频课程需要购买&#xff0c;Android端使用支付宝&#xff0c;微信支付。 苹果规定 数字化内容、App功能以及服务等&#xff0c;需要使用内购 真实世界中的服务(…

汇编中的函数调用与递归

栈帧的结构 倘若我们要想搞清楚过程的实现&#xff0c;就必须先知道栈帧的结构是如何构成的。栈帧其实可以认为是程序栈的一段&#xff0c;而程序栈又是存储器的一段&#xff0c;因此栈帧说到底还是存储器的一段。那么既然是一段&#xff0c;肯定有两个端点&#xff0c;这个不需…

php 相亲 段子,精彩的男女幽默段子

精彩的男女幽默段子。撒嬌老婆洗完澡對老公撒嬌說&#xff1a;老公&#xff0c;抱我到床上去吧。老公看了看老婆&#xff0c;冷冷的回答道&#xff1a;我還是把床搬過來吧&#xff01;所以&#xff0c;撒嬌還是要看體型&#xff01;單身老公說&#xff1a;老婆&#xff0c;你不…

Redmine数据库备份及搬家

Bitnami Redmine的备份分2种方式&#xff1a; 1.导出数据库 2.整个目录搬家 不管是哪种都想停掉服务&#xff0c;redmine相关的服务有以下5个&#xff1a; redmineApache   redmineMySQL   redmineSubversion   redmineThin1   redmineThin2 可以打开windows服务控制面…

且看BCH开启的“信用本位”时代

​​​ 且看BCH开启的“信用本位”时代 比特币向来被称为“金本位”的互联网实验&#xff0c;由于中本聪先生的天才发明&#xff0c;POW机制给予了比特币与黄金同样的生产模式。所以&#xff0c;时至今日&#xff0c;BCE依然自称为“数字黄金”。 只可惜&#xff0c;“一叶障目…

oracle设置临时表空间,Oracle临时表空间查看、添加临时表空间数据文件、修改默认临时表空间 方法!...

--查表空间使用率情况(含临时表空间)SELECT d.tablespace_name "Name", d.status "Status",TO_CHAR (NVL (a.BYTES / 1024 / 1024, 0), 99,999,990.90) "Size (M)",TO_CHAR (NVL (a.BYTES - NVL (f.BYTES, 0), 0) / 1024 / 1024,99999999.99) US…

Redmine项目管理工具安装

Redmine免费开源的项目管理工具 下载 一键安装工具 https://bitnami.com/stack/redmine/installer 安装 Redmine一键安装工具集成了php服务&#xff0c;mysql服务。尽管安装就好。 安装完成后&#xff0c;在开始菜单&#xff0c;找到-----Bitnami Redmine Stack--------Bi…

Oracle创建假脱机文件,oracle – 在sqlplus中假脱机csv文件时的标头格式

我需要使用sqlplus从Oracle中的表中调整csv.以下是所需的格式&#xff1a;"HOST_SITE_TX_ID","SITE_ID","SITETX_TX_ID","SITETX_HELP_ID""664436565","16","2195301","0""664700792&qu…

方便微信公众号等手机网页调试插件eruda和vConsole

原文地址&#xff1a;https://blog.csdn.net/qq_39234840/article/details/80951710 ---------------------------------------------------------- 调试插件一&#xff1a;eruda&#xff08;推荐&#xff0c;因为比vConsole功能多&#xff09; <script src"//cdn.js…

HDU 3530Subsequence(单调队列)

题意 题目链接 给出$n$个数&#xff0c;找出最长的区间&#xff0c;使得区间中最大数$-$最小数 $> m$ 且$< k$ Sol 考虑维护两个单调队列。 一个维护$1 - i$的最大值&#xff0c;一个维护$1 - i$的最小值。 至于两个限制条件。 $<k$可以通过调整队首来满足 $>a$可以…

oracle权限培训,Java培训-ORACLE数据库学习【2】用户权限

查询用户拥有的权限&#xff1a;1.查看所有用户&#xff1a;select *from dba_users;select *from all_users;select *from user_users; 2.查看用户或角色系统权限(直接赋值给用户或角色的系统权限)&#xff1a;select *from dba_sys_privs;select *from user_sys_privs; 3.查看…

linux 中文件夹的文件按照时间倒序或者升序排列

1&#xff0c;按照时间升序 命令:ls -lrt 详细解释: -l use a long listing format 以长列表方式显示&#xff08;详细信息方式&#xff09; -t sort by modification time 按修改时间排序&#xff08;最新的在最前面&#xff09; -r reverse order while sorti…

PHP中关于时间(戳)、时区、本地时间、UTC时间等的梳理

PHP中关于时间&#xff08;戳&#xff09;、时区、本地时间、UTC时间等的梳理 在PHP开发中&#xff0c;我们经常会在时间问题上被搞糊涂&#xff0c;比如我们希望显示一个北京时间&#xff0c;但是当我们使用date函数进行输出时&#xff0c;却发现少了8个小时。几乎所有的php猿…

WebServiceStudio.exe测试webservice接口工具

WebServiceStudio.exe测试webservice接口工具 下载链接 https://pan.baidu.com/s/1gf8ajS3 打开工具WebServiceStudio&#xff0c;如下填写地址&#xff0c;点击【Get】按钮 会显示出需要传参的地方&#xff0c;在value中填写xml参数 输入完value值后&#xff0c;点击【Invok…

oracle最大实例数,【ORA-16196】一个实例在其生命周期里最多只能装载和打开一个数据库...

如果使用“alter database open;”命令打开一个曾经被“alter database close;”命令关闭的数据库时&#xff0c;您将会收到如下的报错信息&#xff1a;"ORA-16196: database has been previously opened and closed"这个报错的原因是什么呢&#xff1f;原因是&#…