面试-线程池的成长之路

转载自   面试-线程池的成长之路

背景

相信大家在面试过程中遇到面试官问线程的很多,线程过后就是线程池了。从易到难,都是这么个过程,还有就是确实很多人在工作中接触线程池比较少,最多的也就是创建一个然后往里面提交线程,对于一些经验很丰富的面试官来说,一下就可以问出很多线程池相关的问题,与其被问的晕头转向,还不如好好学习。此时不努力更待何时。

什么是线程池?

线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。

如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

如果用生活中的列子来说明,我们可以把线程池当做一个客服团队,如果同时有1000个人打电话进行咨询,按照正常的逻辑那就是需要1000个客服接听电话,服务客户。现实往往需要考虑到很多层面的东西,比如:资源够不够,招这么多人需要费用比较多。正常的做法就是招100个人成立一个客服中心,当有电话进来后分配没有接听的客服进行服务,如果超出了100个人同时咨询的话,提示客户等待,稍后处理,等有客服空出来就可以继续服务下一个客户,这样才能达到一个资源的合理利用,实现效益的最大化。

Java中的线程池种类

1. newSingleThreadExecutor

创建方式:

ExecutorService pool = Executors.newSingleThreadExecutor();

一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

使用方式:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {public static void main(String[] args) {ExecutorService pool = Executors.newSingleThreadExecutor();for (int i = 0; i < 10; i++) {pool.execute(() -> {System.out.println(Thread.currentThread().getName() + "\t开始发车啦....");});}}
}

输出结果如下:

pool-1-thread-1    开始发车啦....
pool-1-thread-1    开始发车啦....
pool-1-thread-1    开始发车啦....
pool-1-thread-1    开始发车啦....
pool-1-thread-1    开始发车啦....
pool-1-thread-1    开始发车啦....
pool-1-thread-1    开始发车啦....
pool-1-thread-1    开始发车啦....
pool-1-thread-1    开始发车啦....
pool-1-thread-1    开始发车啦....

从输出的结果我们可以看出,一直只有一个线程在运行。

2.newFixedThreadPool

创建方式:

ExecutorService pool = Executors.newFixedThreadPool(10);

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

使用方式:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {public static void main(String[] args) {ExecutorService pool = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {pool.execute(() -> {System.out.println(Thread.currentThread().getName() + "\t开始发车啦....");});}}
}

输出结果如下:

pool-1-thread-1    开始发车啦....
pool-1-thread-4    开始发车啦....
pool-1-thread-3    开始发车啦....
pool-1-thread-2    开始发车啦....
pool-1-thread-6    开始发车啦....
pool-1-thread-7    开始发车啦....
pool-1-thread-5    开始发车啦....
pool-1-thread-8    开始发车啦....
pool-1-thread-9    开始发车啦....
pool-1-thread-10 开始发车啦....

3. newCachedThreadPool

创建方式:

ExecutorService pool = Executors.newCachedThreadPool();

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程,当任务数增加时,此线程池又添加新线程来处理任务。

使用方式如上2所示。

4.newScheduledThreadPool

创建方式:

ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);

此线程池支持定时以及周期性执行任务的需求。

使用方式:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPool {public static void main(String[] args) {ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);for (int i = 0; i < 10; i++) {pool.schedule(() -> {System.out.println(Thread.currentThread().getName() + "\t开始发车啦....");}, 10, TimeUnit.SECONDS);}}
}

上面演示的是延迟10秒执行任务,如果想要执行周期性的任务可以用下面的方式,每秒执行一次

//pool.scheduleWithFixedDelay也可以
pool.scheduleAtFixedRate(() -> {System.out.println(Thread.currentThread().getName() + "\t开始发车啦....");
}, 1, 1, TimeUnit.SECONDS);

5.newWorkStealingPool

newWorkStealingPool是jdk1.8才有的,会根据所需的并行层次来动态创建和关闭线程,通过使用多个队列减少竞争,底层用的ForkJoinPool来实现的。ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。

说说线程池的拒绝策略

当请求任务不断的过来,而系统此时又处理不过来的时候,我们需要采取的策略是拒绝服务。RejectedExecutionHandler接口提供了拒绝任务处理的自定义方法的机会。在ThreadPoolExecutor中已经包含四种处理策略。

  • AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。

public static class AbortPolicy implements RejectedExecutionHandler {public AbortPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException("Task " + r.toString() +" rejected from " +e.toString());}
}
  • CallerRunsPolicy 策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {public CallerRunsPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {r.run();}}
}
  • DiscardOleddestPolicy策略: 该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {public DiscardOldestPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {e.getQueue().poll();e.execute(r);}}
}
  • DiscardPolicy策略:该策略默默的丢弃无法处理的任务,不予任何处理。
public static class DiscardPolicy implements RejectedExecutionHandler {public DiscardPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}
}

除了JDK默认为什么提供的四种拒绝策略,我们可以根据自己的业务需求去自定义拒绝策略,自定义的方式很简单,直接实现RejectedExecutionHandler接口即可

比如Spring integration中就有一个自定义的拒绝策略CallerBlocksPolicy,将任务插入到队列中,直到队列中有空闲并插入成功的时候,否则将根据最大等待时间一直阻塞,直到超时。

package org.springframework.integration.util;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class CallerBlocksPolicy implements RejectedExecutionHandler {private static final Log logger = LogFactory.getLog(CallerBlocksPolicy.class);private final long maxWait;/*** @param maxWait The maximum time to wait for a queue slot to be* available, in milliseconds.*/public CallerBlocksPolicy(long maxWait) {this.maxWait = maxWait;}@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {if (!executor.isShutdown()) {try {BlockingQueue<Runnable> queue = executor.getQueue();if (logger.isDebugEnabled()) {logger.debug("Attempting to queue task execution for " + this.maxWait + " milliseconds");}if (!queue.offer(r, this.maxWait, TimeUnit.MILLISECONDS)) {throw new RejectedExecutionException("Max wait time expired to queue task");}if (logger.isDebugEnabled()) {logger.debug("Task execution queued");}}catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RejectedExecutionException("Interrupted", e);}}else {throw new RejectedExecutionException("Executor has been shut down");}}
}

定义好之后如何使用呢?光定义没用的呀,一定要用到线程池中呀,可以通过下面的方式自定义线程池,指定拒绝策略。

BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 100, 10, TimeUnit.SECONDS, workQueue, new CallerBlocksPolicy());

execute和submit的区别?

在前面的讲解中,我们执行任务是用的execute方法,除了execute方法,还有一个submit方法也可以执行我们提交的任务。

这两个方法有什么区别呢?分别适用于在什么场景下呢?我们来做一个简单的分析。

execute适用于不需要关注返回值的场景,只需要将线程丢到线程池中去执行就可以了

public class ThreadPool {public static void main(String[] args) {ExecutorService pool = Executors.newFixedThreadPool(10);pool.execute(() -> {System.out.println(Thread.currentThread().getName() + "\t开始发车啦....");});}
}

submit方法适用于需要关注返回值的场景,submit方法的定义如下:

public interface ExecutorService extends Executor {...<T> Future<T> submit(Callable<T> task);<T> Future<T> submit(Runnable task, T result);Future<?> submit(Runnable task);...
}

其子类AbstractExecutorService实现了submit方法,可以看到无论参数是Callable还是Runnable,最终都会被封装成RunnableFuture,然后再调用execute执行。

    /*** @throws RejectedExecutionException {@inheritDoc}* @throws NullPointerException       {@inheritDoc}*/public Future<?> submit(Runnable task) {if (task == null) throw new NullPointerException();RunnableFuture<Void> ftask = newTaskFor(task, null);execute(ftask);return ftask;}/*** @throws RejectedExecutionException {@inheritDoc}* @throws NullPointerException       {@inheritDoc}*/public <T> Future<T> submit(Runnable task, T result) {if (task == null) throw new NullPointerException();RunnableFuture<T> ftask = newTaskFor(task, result);execute(ftask);return ftask;}/*** @throws RejectedExecutionException {@inheritDoc}* @throws NullPointerException       {@inheritDoc}*/public <T> Future<T> submit(Callable<T> task) {if (task == null) throw new NullPointerException();RunnableFuture<T> ftask = newTaskFor(task);execute(ftask);return ftask;}

下面我们来看看这三个方法分别如何去使用:

submit(Callable task);

public class ThreadPool {public static void main(String[] args) throws Exception {ExecutorService pool = Executors.newFixedThreadPool(10);Future<String> future = pool.submit(new Callable<String>() {@Overridepublic String call() throws Exception {return "Hello";}});String result = future.get();System.out.println(result);}
}

submit(Runnable task, T result);

public class ThreadPool {public static void main(String[] args) throws Exception {ExecutorService pool = Executors.newFixedThreadPool(10);Data data = new Data();Future<Data> future = pool.submit(new MyRunnable(data), data);String result = future.get().getName();System.out.println(result);}
}
class Data {String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
class MyRunnable implements Runnable {private Data data;public MyRunnable(Data data) {this.data = data;}@Overridepublic void run() {data.setName("yinjihuan");}
}

Future submit(Runnable task);
直接submit一个Runnable是拿不到返回值的,返回值就是null.

五种线程池的使用场景

  • newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。
  • newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。
  • newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。
  • newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
  • newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。

线程池的关闭

关闭线程池可以调用shutdownNow和shutdown两个方法来实现

shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表

public class ThreadPool {public static void main(String[] args) throws Exception {ExecutorService pool = Executors.newFixedThreadPool(1);for (int i = 0; i < 5; i++) {System.err.println(i);pool.execute(() -> {try {Thread.sleep(30000);System.out.println("--");} catch (Exception e) {e.printStackTrace();}});}Thread.sleep(1000);List<Runnable> runs = pool.shutdownNow();}
}

上面的代码模拟了立即取消的场景,往线程池里添加5个线程任务,然后sleep一段时间,线程池只有一个线程,如果此时调用shutdownNow后应该需要中断一个正在执行的任务和返回4个还未执行的任务,控制台输出下面的内容:

[fs.ThreadPool$$Lambda$1/990368553@682a0b20, 
fs.ThreadPool$$Lambda$1/990368553@682a0b20, 
fs.ThreadPool$$Lambda$1/990368553@682a0b20, 
fs.ThreadPool$$Lambda$1/990368553@682a0b20]
java.lang.InterruptedException: sleep interruptedat java.lang.Thread.sleep(Native Method)at fs.ThreadPool.lambda$0(ThreadPool.java:15)at fs.ThreadPool$$Lambda$1/990368553.run(Unknown Source)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)at java.lang.Thread.run(Thread.java:745)

shutdown:当我们调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务

public class ThreadPool {public static void main(String[] args) throws Exception {ExecutorService pool = Executors.newFixedThreadPool(1);for (int i = 0; i < 5; i++) {System.err.println(i);pool.execute(() -> {try {Thread.sleep(30000);System.out.println("--");} catch (Exception e) {e.printStackTrace();}});}Thread.sleep(1000);pool.shutdown();pool.execute(() -> {try {Thread.sleep(30000);System.out.println("--");} catch (Exception e) {e.printStackTrace();}});}
}

上面的代码模拟了正在运行的状态,然后调用shutdown,接着再往里面添加任务,肯定是拒绝添加的,请看输出结果:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task fs.ThreadPool$$Lambda$2/1747585824@3d075dc0 rejected from java.util.concurrent.ThreadPoolExecutor@214c265e[Shutting down, pool size = 1, active threads = 1, queued tasks = 4, completed tasks = 0]at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)at fs.ThreadPool.main(ThreadPool.java:24)

还有一些业务场景下需要知道线程池中的任务是否全部执行完成,当我们关闭线程池之后,可以用isTerminated来判断所有的线程是否执行完成,千万不要用isShutdown,isShutdown只是返回你是否调用过shutdown的结果。

public class ThreadPool {public static void main(String[] args) throws Exception {ExecutorService pool = Executors.newFixedThreadPool(1);for (int i = 0; i < 5; i++) {System.err.println(i);pool.execute(() -> {try {Thread.sleep(3000);System.out.println("--");} catch (Exception e) {e.printStackTrace();}});}Thread.sleep(1000);pool.shutdown();while(true){  if(pool.isTerminated()){  System.out.println("所有的子线程都结束了!");  break;  }  Thread.sleep(1000);    }  }
}

自定义线程池

在实际的使用过程中,大部分我们都是用Executors去创建线程池直接使用,如果有一些其他的需求,比如指定线程池的拒绝策略,阻塞队列的类型,线程名称的前缀等等,我们可以采用自定义线程池的方式来解决。

如果只是简单的想要改变线程名称的前缀的话可以自定义ThreadFactory来实现,在Executors.new…中有一个ThreadFactory的参数,如果没有指定则用的是DefaultThreadFactory。

自定义线程池核心在于创建一个ThreadPoolExecutor对象,指定参数

下面我们看下ThreadPoolExecutor构造函数的定义:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) ;
  • corePoolSize

线程池大小,决定着新提交的任务是新开线程去执行还是放到任务队列中,也是线程池的最最核心的参数。一般线程池开始时是没有线程的,只有当任务来了并且线程数量小于corePoolSize才会创建线程。

  • maximumPoolSize

最大线程数,线程池能创建的最大线程数量。

  • keepAliveTime

在线程数量超过corePoolSize后,多余空闲线程的最大存活时间。

  • unit

时间单位

  • workQueue

存放来不及处理的任务的队列,是一个BlockingQueue。

  • threadFactory

生产线程的工厂类,可以定义线程名,优先级等。

  • handler

拒绝策略,当任务来不及处理的时候,如何处理, 前面有讲解。

了解上面的参数信息后我们就可以定义自己的线程池了,我这边用ArrayBlockingQueue替换了LinkedBlockingQueue,指定了队列的大小,当任务超出队列大小之后使用CallerRunsPolicy拒绝策略处理。

这样做的好处是严格控制了队列的大小,不会出现一直往里面添加任务的情况,有的时候任务处理的比较慢,任务数量过多会占用大量内存,导致内存溢出。

当然你也可以在提交到线程池的入口进行控制,比如用CountDownLatch, Semaphore等。

/*** 自定义线程池<br>* 默认的newFixedThreadPool里的LinkedBlockingQueue是一个无边界队列,如果不断的往里加任务,最终会导致内存的不可控<br>* 增加了有边界的队列,使用了CallerRunsPolicy拒绝策略* @author yinjihuan**/
public class FangjiaThreadPoolExecutor {private static ExecutorService executorService = newFixedThreadPool(50);private static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(10000), new DefaultThreadFactory(), new CallerRunsPolicy());}public static void execute(Runnable command) {executorService.execute(command);}public static void shutdown() {executorService.shutdown();}static class DefaultThreadFactory implements ThreadFactory {private static final AtomicInteger poolNumber = new AtomicInteger(1);private final ThreadGroup group;private final AtomicInteger threadNumber = new AtomicInteger(1);private final String namePrefix;DefaultThreadFactory() {SecurityManager s = System.getSecurityManager();group = (s != null) ? s.getThreadGroup() :Thread.currentThread().getThreadGroup();namePrefix = "FSH-pool-" +poolNumber.getAndIncrement() +"-thread-";}public Thread newThread(Runnable r) {Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0);if (t.isDaemon())t.setDaemon(false);if (t.getPriority() != Thread.NORM_PRIORITY)t.setPriority(Thread.NORM_PRIORITY);return t;}}
}

 

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

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

相关文章

jzoj1265-Round Numbers【数位统计】

正题 大意 求一个闭区间内二进制0的个数大于等于1的个数的数的数量。 解题思路 我们将二进制考虑成01串 我们先不考虑有前导零的情况&#xff1a; 在kk个空位中放i" role="presentation" style="position: relative;">ii个0&#xff0c;别的都…

asp.net core MVC 过滤器之ActionFilter过滤器(二)

简介 Action过滤器将在controller的Action执行之前和之后执行相应的方法。 实现一个自定义Action过滤器 自定义一个全局异常过滤器需要实现IActionFilter接口 public class ActionFilter : IActionFilter { public void OnActionExecuted(ActionExecutedContext context) …

【最全最详细】publiccms使用教程

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂拉取项目&#xff08;项目部署阶段&#xff09;1.首先需要从gitee中拉取项目&#xff0c;地址为&#xff1a;public cms项目地址 &#xff0c;在idea中点击文件--》新建--》来自版本控制的项…

NIO、Netty

https://bright-boy.gitee.io/technical-notes/#/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/netty

学习 Spring Boot 知识看这一篇就够了

转载自 学习 Spring Boot 知识看这一篇就够了 从2016年因为工作原因开始研究 Spring Boot &#xff0c;先后写了很多关于 Spring Boot 的文章&#xff0c;发表在技术社区、我的博客和我的公号内。粗略的统计了一下总共的文章加起来大概有六十多篇了&#xff0c;其中一部分是在…

jzoj1266,P1879-[USACO06NOV]玉米田Corn Fields【状态压缩,dp】

正题 评测记录&#xff1a;https://www.luogu.org/recordnew/lists?uid52918&pidP1879 大意 有n*m的矩阵&#xff0c;有些地方可以放&#xff0c;有些不可以放&#xff0c;不可以相邻的放&#xff0c;求放置方法总数。 解题思路 先用二进制表示每一行的是否可以放置的状…

越努力越幸运,三年了!!!

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂”今天非常的开心&#xff0c;因为考了三年的驾照终于考完了。在大厅候考的时候&#xff0c;从10.40等到了1.40,坐在下面能看到LED上有自己的名字&#xff0c;但是前面等待的有200多人………

为什么我们要使用Async、Await关键字

前不久&#xff0c;在工作中由于默认&#xff08;xihuan&#xff09;使用Async、Await关键字受到了很多质问&#xff0c;所以由此引发这篇博文“为什么我们要用Async/Await关键字”&#xff0c;请听下面分解&#xff1a; Async/Await关键字 Visual Studio&#xff08;.net fra…

go web

模块&#xff1a;go mod init webDemo 设置web服务器 方法一&#xff1a; http.ListenAndServe("localhost:8080", nil)方法二&#xff1a; server : http.Server{Addr: "localhost:8080",Handler: nil,} server.ListenAndServe()例 package mainimpor…

一次惊险的跳槽面试经历(阿里/美团/头条/网易/有赞...)

转载自 一次惊险的跳槽面试经历&#xff08;阿里/美团/头条/网易/有赞...) 每次说因为生活成本的时候面试官都会很惊奇&#xff0c;难道有我们这里贵&#xff1f;好想直接给出下面这张图&#xff0c;厦门的房价真的好贵好贵好贵。。。 面试过程 有兴趣加入阿里的欢迎发简历…

利用bladex+avue实现一对多的关系

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂”今天&#xff0c;记录一篇技术文章吧&#xff0c;也是解决了好久才解决掉的&#xff08;说来也惭愧&#xff09;。涉及技术前端&#xff1a;vue&#xff0c;element ui后端框架&#xff…

jzoj1267-路障【最短路,SPFA】

正题 大意 一张无向图&#xff0c;求次短路。 解题思路 我们先求出最短路并且求出点1和点n到每个点的距离&#xff0c;然后枚举边&#xff0c;将第一个点离点1的距离&#xff0c;和第二个点离点n的距离加上边权如果不是最短路就记录&#xff0c;然后取最小值。 IOI赛制不需要…

.NET中的高性能应用

本文要点 .NET自4.0以来得到了大幅的性能提升&#xff0c;很值得重新考虑一下基于旧版本.NET框架所做的假定。在讨论性能时垃圾回收是个重复出现的主题&#xff0c;它带来了许多CLR和语言的提升&#xff0c;比如引用返回和ValueTask在内存分配上更细粒度度量的性能分析API会成…

go 数组 切片 映射

文章目录数组切片映射数组 package mainimport "fmt"func main() {fmt.Println("array coding........")//var array [5]int//array[2] 2//var array [5]int{0, 1, 2, 3, 4}//array : [5]int{0, 1, 2, 3, 4}array : [...]int{0, 1, 2, 3, 4}//array : […

利用bladex+avue实现下拉数据源展示

“ 大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;????雄雄的小课堂???? ”昨天给大家整理了下如何使用bladex实现多表查询的方法&#xff0c;今天我们趁热打铁&#xff0c;顺便看看下拉列表的实现。 需求 我们经常会有这样的需求&#xff0c;…

汇编语言(一)之反转字符串输出

BASED ADDRESSING反转输出 程序运行&#xff1a; 代码&#xff1a; datas segmentstring db BASED ADDRESSING$ count dw $-string-1 ;计算string的长度&#xff0c;$为当前地址&#xff0c;-1为去掉字符串结束符$srcsTip db SRC string:$ dstsTip db 0…

2018/7/17-纪中某C组题【jzoj4024,jzoj4025,jzoj2136,jzoj2137】

前言 今天第一道题调了一会就好了&#xff0c;然后一直在调第二道题&#xff0c;然后也没调出来。赛后之后发现第2道题我想的是正解&#xff0c;结果依旧调了很久。其他都还好。 今日分数 Rankperson分数4zyc27014xxy18018hjq15018xjq15018lw15023hzb12023蒟蒻12029lrz110 正…

前后端分离项目部署上线详细教程

“ 大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;????雄雄的小课堂???? ”今天&#xff0c;给大家分享的是&#xff0c;SpringbootVue项目如何部署上线的详细步骤。 代码编辑器 前端&#xff1a;Webstorm 2021.1.2 后端&#xff1a;IntelliJ …

.Net Core2.0下使用Dapper遇到的问题

今天成功把.Net Framework下使用Dapper进行封装的ORM成功迁移到.Net Core 2.0上&#xff0c;在迁移的过程中也遇到一些很有意思的问题&#xff0c;值得和大家分享一下。下面我会还原迁移的每一个过程&#xff0c;以及在此过程中遇到的问题和处理这些问题的方法。 一、迁移前的…

go 方法集

go 接口 方法集 1、如果使用 指针接收者 来实现一个接口&#xff0c;那么只有指向那个类型的指针才能实现对应的接口 2、如果使用 值接收者 go来实现一个接口&#xff0c;那么那个类型的值和指针接收者都能够实现对应的接口 go 嵌入类型 内部类型实现接口&#xff0c;内部类…