(转)ThreadPoolExecutor最佳实践--如何选择队列

转自: https://blog.hufeifei.cn/2018/08/12/Java/ThreadPoolExecutor%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5--%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E9%98%9F%E5%88%97/ 

 

前一篇文章《如何选择线程数》讲了如何决定线程池中线程个数,这篇文章讨论“如何选择工作队列”。

再次强调一下,ThreadPoolExecutor最核心的四点:

1、当有任务提交的时候,会创建核心线程去执行任务(即使有核心线程空闲);

2、当核心线程数达到corePoolSize时,后续提交的都会进BlockingQueue中排队;

3、当BlockingQueue满了(offer失败),就会创建临时线程(临时线程空闲超过一定时间后,会被销毁);

4、当线程总数达到maximumPoolSize时,后续提交的任务都会被RejectedExecutionHandler拒绝。

1、BlockingQueue

线程池中工作队列由BlockingQueue实现类提供功能,BlockingQueue定义了这么几组方法:

Summary of BlockingQueue methods
 Throws exceptionSpecial valueBlocksTimes out
Insertadd(e)offer(e)put(e)offer(e, time, unit)
Removeremove()poll()take()poll(time, unit)
Examineelement()peek()not applicablenot applicable

阻塞队列是最典型的“生产者消费者”模型:

  • 生产者调用put()方法将生产的元素入队,消费者调用take()方法;
  • 当队列满了,生产者调用的put()方法会阻塞,直到队列有空间可入队;
  • 当队列为空,消费者调用的get()方法会阻塞,直到队列有元素可消费;

生产者消费者模型

但是需要十分注意的是:ThreadPoolExecutor提交任务时使用offer方法(不阻塞),工作线程从队列取任务使用take方法(阻塞)。正是因为ThreadPoolExecutor使用了不阻塞的offer方法,所以当队列容量已满,线程池会去创建新的临时线程;同样因为工作线程使用take()方法取任务,所以当没有任务可取的时候线程池的线程将会空闲阻塞。

事实上,工作线程的超时销毁是调用offer(e, time, unit)实现的。

2、JDK提供的阻塞队列实现

JDK中提供了以下几个BlockingQueue实现类:

 

2.1、ArrayBlockingQueue

这是一个由数组实现容量固定的有界阻塞队列。这个队列的实现非常简单:

Copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void enqueue(E x) {final Object[] items = this.items;items[putIndex] = x; // 入队if (++putIndex == items.length) // 如果指针到了末尾putIndex = 0; // 下一个入队的位置变为0count++;notEmpty.signal(); // 提醒消费者线程消费
}
private E dequeue() {final Object[] items = this.items;@SuppressWarnings("unchecked")E x = (E) items[takeIndex];items[takeIndex] = null; // 出队置空if (++takeIndex == items.length) // 如果指针到了末尾takeIndex = 0; // 下一个出队的位置变为0count--;if (itrs != null)itrs.elementDequeued();notFull.signal(); // 提醒生产者线程生产return x;
}

通过简单的指针循环实现了一个环形队列:

环形队列

下面有一张维基百科关于环形缓冲区的的动画,虽然动画描述内容与ArrayBlockingQueue实现有所差异,但贵在生动形象(着实找不到更好的动画了)。

环形缓冲区

ArrayBlockingQueue主要复杂在迭代,允许迭代中修改队列(删除元素时会更新迭代器),并不会抛出ConcurrentModificationException;好在大多数场景中我们不会迭代阻塞队列。

2.2、SynchronousQueue

这是一个非常有意思的集合,更准确的说它并不是一个集合容器,因为它没有容量。你可以“偷偷地”把它看作new ArrayBlockingQueue(0),之所以用”偷偷地”这么龌龊的词,首先是因为ArrayBlockingQueuecapacity<1时会抛异常,其次ArrayBlockingQueue(0)并不能实现SynchronousQueue这么强大的功能。

正如SynchronousQueue的名字所描述一样——“同步队列”,它专门用于生产者线程与消费者线程之间的同步

  • 因为它任何时候都是空的,所以消费者线程调用take()方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回。
  • 同样的,你也可以认为任何时候都是满的,所以生产者线程调用put()方法的时候就会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。

另外还有几点需要注意:

  • SynchronousQueue不能遍历,因为它没有元素可以遍历;
  • 所有的阻塞队列都不允许插入null元素,因为当生产者生产了一个null的时候,消费者调用poll()返回null,无法判断是生产者生产了一个null元素,还是队列本身就是空。

CachedThreadPool使用的就是同步队列

Copy

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

因为SynchronousQueue无容量的特性,所以CachedThreadPool不会对任务进行排队,如果线程池中没有空闲线程,CachedThreadPool会立即创建一个新线程来接收这个任务。

所以使用CachedThreadPool要注意避免提交长时间阻塞的任务,可能会由于线程数过多而导致内存溢出(OutOfOutOfMemoryError)。

2.3、LinkedBlockingQueue

这是一个由单链表实现默认无界的阻塞队列。LinkedBlockingQueue提供了一个可选有界的构造函数,而在未指明容量时,容量默认为Integer.MAX_VALUE。

按照官方文档的说法LinkedBlockingQueue是一种可选有界(optionally-bounded)阻塞队列

SingleThreadPool和FixedThreadPool使用的就是LinkedBlockingQueue

Copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory);
}
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory));
}

因为FixedThreadPool使用无界的LinkedBlockingQueue,所以当没有线程空闲时,新提交的任务都会提交到阻塞队列中,由于队列永远也不会满,FixedThreadPool永远也不会创建新的临时线程。

但是需要注意的是,不要往FixedThreadPool提交过多的任务,因为所有未处理的任务都会到LinkedBlockingQueue中排队,队列中任务过多也可能会导致内存溢出。虽然这个过程会比较缓慢,因为队列中的请求所占用的资源比线程占用的资源要少得多。

2.4、其他队列

DelayQueue和PriorityBlockingQueue底层都是使用二叉堆实现优先级阻塞队列

区别在于:

  • 前者要求队列中的元素实现Delayed接口,通过执行时延从队列中提取任务,时间没到任务取不出来;
  • 后者对元素没有要求,可以实现Comparable接口也可以提供Comparator来对队列中的元素进行比较,跟时间没有任何关系,仅仅是按照优先级取任务。

当我们提交的任务有优先顺序时可以考虑选用这两种队列

事实上ScheduledThreadPoolExecutor内部实现了一个类似于DelayQueue的队列。

除了这两个,BlockingQueue还有两个子接口BlockingDeque(双端阻塞队列),TransferQueue(传输队列)

并且两个接口都有自己唯一的实现类:

 

  • LinkedBlockingDeque:使用双向队列实现的双端阻塞队列,双端意味着可以像普通队列一样FIFO(先进先出),可以以像栈一样FILO(先进后出)
  • LinkedTransferQueue:它是ConcurrentLinkedQueue、LinkedBlockingQueue和SynchronousQueue的结合体,但是把它用在ThreadPoolExecutor中,和无限制的LinkedBlockingQueue行为一致。

 

3、让生产者阻塞的线程池

前面说到CachedThreadPool和FixedThreadPool都有可能导致内存溢出,前者是由于线程数过多,后者是由于队列任务过多。而究其根本就是因为任务生产速度远大于线程池处理任务的速度。

所以有一个想法就是让生产任务的线程在任务处理不过来的时候休息一会儿——也就是阻塞住任务生产者。

但是前面提到过ThreadPoolExecutor内部将任务提交到队列时,使用的是不阻塞的offer方法。

我提供的第一种方式是:重写offer方法把它变成阻塞式。

3.1、重写BlockingQueue的offer

这种处理方式是将原来非阻塞的offer覆盖,使用阻塞的put方法实现。

Copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class ThreadPoolTest {private static class Task implements Runnable {private int taskId;Task(int taskId) {this.taskId = taskId;}@Override public void run() {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException ignore) {}System.out.println("task " + taskId + " end");}}public static void main(String[] args) {ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(2) {@Override public boolean offer(Runnable runnable) {try {super.put(runnable); // 使用阻塞的put重写offer方法} catch (InterruptedException e) {e.printStackTrace();}return true;}});threadPool.submit(new Task(1));System.out.println("task 1 submitted");threadPool.submit(new Task(2));System.out.println("task 2 submitted");threadPool.submit(new Task(3));System.out.println("task 3 submitted");threadPool.submit(new Task(4));System.out.println("task 4 submitted");threadPool.submit(new Task(5));System.out.println("task 5 submitted");threadPool.submit(new Task(6));System.out.println("task 6 submitted");threadPool.shutdown();}}

执行的过程中会发现Task5要等到线程池中的一个任务执行完成后,才能提交成功。

这种方式把BlockingQueue的行为修改了,这时线程池的maximumPoolSize形同虚设,因为ThreadPoolExecutor调用offer入队失败返回false后才会创建临时线程。现在offer改成了阻塞式的,实际上永远是返回true,所以永远都不会创建临时线程,maximumPoolSize的限制也就没有什么意义了。

3.2、重写拒绝策略

在介绍第二种方式之前,先简单介绍JDK中提供了四种拒绝策略:

 

  • AbortPolicy——抛出RejectedExecutionException异常的方式拒绝任务。
  • DiscardPolicy——什么都不干,静默地丢弃任务
  • DiscardOldestPolicy——把队列中最老的任务拿出来扔掉
  • CallerRunsPolicy——在任务提交的线程把任务给执行了

ThreadPoolExecutor默认使用AbortPolicy

DiscardPolicy和DiscardOldestPolicy两种策略看上去都不怎么靠谱,除非真有这种特别的需求,比如客户端应用中网络请求拥堵(服务端宕机或网络不通畅)的话可以选择抛弃最老的请求,大多数情况还是使用默认的拒绝策略。

我们的第二种做法就是写一个自己的RejectedExecutionHandler。这种方式相对“温柔”一些,在线程池提交任务的最后一步——被线程池拒绝的任务,可以在拒绝后调用队列的put()方法,让任务的提交者阻塞,直到队列中任务被被线程池执行后,队列有了多余空间,调用方才返回。

Copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class ThreadPoolTest {private static class Task implements Runnable {private int taskId;Task(int taskId) {this.taskId = taskId;}@Overridepublic void run() {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException ignore) {}System.out.println("task " + taskId + " end");}}private static class BlockCallerPolicy implements RejectedExecutionHandler {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {try {executor.getQueue().put(r);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), new BlockCallerPolicy());threadPool.submit(new Task(1));System.out.println("task 1 submitted");threadPool.submit(new Task(2));System.out.println("task 2 submitted");threadPool.submit(new Task(3));System.out.println("task 3 submitted");threadPool.submit(new Task(4));System.out.println("task 4 submitted");threadPool.submit(new Task(5));System.out.println("task 5 submitted");threadPool.submit(new Task(6));System.out.println("task 6 submitted");threadPool.shutdown();}}

使用这种方式的好处是线程池仍可以设置maximumPoolSize,当任务入队失败仍可以创建临时线程执行任务,只有当线程总数大于maximumPoolSize时,任务才会被拒绝。

4、Tomcat中的线程池

作为一个最常用的Java应用服务器之一,Tomcat中线程池还是值得我们借鉴学习的。

注意下面代码来自Tomcat8.5.27,版本不同实现可能略有差异

org.apache.catelina.core.StandardThreadExecutor

Copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class StandardThreadExecutor extends LifecycleMBeanBaseimplements Executor, ResizableExecutor {// Tomcat线程池默认的配置protected int threadPriority = Thread.NORM_PRIORITY;protected boolean daemon = true;protected String namePrefix = "tomcat-exec-";protected int maxThreads = 200;protected int minSpareThreads = 25;protected int maxIdleTime = 60000;...protected boolean prestartminSpareThreads = false;protected int maxQueueSize = Integer.MAX_VALUE;protected void startInternal() throws LifecycleException {// 任务队列:这里你看到的是一个无界队列,但是队列里面进行了特殊处理taskqueue = new TaskQueue(maxQueueSize);TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());// 创建线程池,这里的ThreadPoolExecutor是Tomcat继承自JDK的ThreadPoolExecutorexecutor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), // 核心线程数与最大线程数maxIdleTime, TimeUnit.MILLISECONDS, // 默认6万毫秒的超时时间,也就是一分钟taskqueue, tf); // 玄机在任务队列的设置executor.setThreadRenewalDelay(threadRenewalDelay);if (prestartminSpareThreads) {executor.prestartAllCoreThreads(); // 预热所有的核心线程}taskqueue.setParent(executor);setState(LifecycleState.STARTING);}...
}

org.apache.tomcat.util.threads.ThreadPoolExecutor

Copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {private final AtomicInteger submittedCount = new AtomicInteger(0);private final AtomicLong lastContextStoppedTime = new AtomicLong(0L);private final AtomicLong lastTimeThreadKilledItself = new AtomicLong(0L);@Overrideprotected void afterExecute(Runnable r, Throwable t) {submittedCount.decrementAndGet(); // 执行完成后提交数量减一if (t == null) {// 如果有必要抛个异常让线程终止stopCurrentThreadIfNeeded();}}@Overridepublic void execute(Runnable command) {execute(command,0,TimeUnit.MILLISECONDS);}public void execute(Runnable command, long timeout, TimeUnit unit) {submittedCount.incrementAndGet(); // 提交时数量加一try {super.execute(command);} catch (RejectedExecutionException rx) {if (super.getQueue() instanceof TaskQueue) {final TaskQueue queue = (TaskQueue)super.getQueue();try {// 如果任务被拒绝,则强制入队if (!queue.force(command, timeout, unit)) {// 由于TaskQueue默认无界,所以默认强制入队会成功submittedCount.decrementAndGet();throw new RejectedExecutionException("Queue capacity is full.");}} catch (InterruptedException x) {submittedCount.decrementAndGet(); // 任务被拒绝,任务数减一throw new RejectedExecutionException(x);}} else {submittedCount.decrementAndGet(); // 任务被拒绝,任务数减一throw rx;}}}
}

org.apache.tomcat.util.threads.TaskQueue

Copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class TaskQueue extends LinkedBlockingQueue<Runnable> {private volatile ThreadPoolExecutor parent = null;public boolean force(Runnable o) {if ( parent==null || parent.isShutdown() )throw new RejectedExecutionException("Executor not running, can't force a command into the queue");// 因为LinkedBlockingQueue无界,所以调用offer强制入队return super.offer(o);}public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {if ( parent==null || parent.isShutdown() )throw new RejectedExecutionException("Executor not running, can't force a command into the queue");return super.offer(o,timeout,unit);}@Overridepublic boolean offer(Runnable o) {// 不是上面Tomcat中定义地ThreadPoolExecutor,不做任何检查if (parent==null) return super.offer(o);// 线程数达到最大线程数,尝试入队if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);// 提交的任务数小于线程数,也就是有空余线程,入队让空闲线程取任务if (parent.getSubmittedCount() < parent.getPoolSize()) return super.offer(o);// 走到这说明线程池没有空闲线程// 这里返回false,改变了LinkedBlockingQueue默认的行为// 使得Tomcat可以创建临时线程if (parent.getPoolSize() < parent.getMaximumPoolSize()) return false;// 到这里说明临时线程也没有空闲,只能排队了return super.offer(o);}
}

Tomcat的线程池扩展了JDK线程池的功能,主要体现在两点:

  • Tomcat的ThreadPoolExecutor使用的TaskQueue,是无界的LinkedBlockingQueue,但是通过taskQueue的offer方法覆盖了LinkedBlockingQueue的offer方法,改写了规则,使得线程池能在任务较多的情况下增长线程池数量——JDK是先排队再涨线程池,Tomcat则是先涨线程池再排队。
  • Tomcat的ThreadPoolExecutor改写了execute方法,当任务被reject时,捕获异常,并强制入队。

参考链接:

支持生产阻塞的线程池 :http://ifeve.com/blocking-threadpool-executor/

Disruptor框架:http://lmax-exchange.github.io/disruptor/files/Disruptor-1.0.pdf

线程池调整的重要性:https://blog.bramp.net/post/2015/12/17/the-importance-of-tuning-your-thread-pools/

线程池调整的重要性(译):http://www.importnew.com/17633.html

SynchronousQueue与TransferQueue的区别:https://stackoverflow.com/questions/7317579/difference-between-blockingqueue-and-transferqueue/7317650

Tomcat配置线程池:https://tomcat.apache.org/tomcat-8.5-doc/config/executor.html

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

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

相关文章

HDU1576(欧几里得算法)

Problem Descption 要求(A/B)%9973&#xff0c;但由于A很大&#xff0c;我们只给出n(nA%9973)(我们给定的A必能被B整除&#xff0c;且gcd(B,9973) 1)。 Input 数据的第一行是一个T&#xff0c;表示有T组数据。 每组数据有两个数n(0 < n < 9973)和B(1 < B < 10^…

为什么byte取值-128~127??

转载自 为什么byte取值-128~127??java设计byte类型为1个字节&#xff0c;1个字节占8位&#xff0c;即8bit&#xff0c;这是常识。另外&#xff0c;计算机系统中是用补码来存储的&#xff0c;首位为0表示正数&#xff0c;首位为1表示负数&#xff0c;所以有以下结论&#xff1…

(转)threadPoolExecutor 中的 shutdown() 、 shutdownNow() 、 awaitTermination() 的用法和区别

最近在看并发编程&#xff0c;在使用到ThreadPoolExecutor时&#xff0c;对它的三个关闭方法&#xff08;shutdown()、shutdownNow()、awaitTermination()&#xff09;产生了兴趣&#xff0c;同时又感到迷惑。查了些资料&#xff0c;自己写了测试代码&#xff0c;总算有了个比较…

HDU2049(错列排序)

国庆期间,省城HZ刚刚举行了一场盛大的集体婚礼,为了使婚礼进行的丰富一些,司仪临时想出了有一个有意思的节目,叫做"考新郎",具体的操作是这样的: 首先,给每位新娘打扮得几乎一模一样,并盖上大大的红盖头随机坐成一排; 然后,让各位新郎寻找自己的新娘.每人只准找一个,…

厉害了,Servlet3的异步处理机制

转载自 厉害了&#xff0c;Servlet3的异步处理机制Servlet3发布好几年了&#xff0c;又有多少人知道它的新特性呢&#xff1f;下面简单介绍下。 主要增加了以下特性&#xff1a; 1、异步处理支持 2、可插性支持 3、注解支持&#xff0c;零配置&#xff0c;可不用配置web.xml ..…

(转)Elasticsearch 聚合查询、桶聚合、管道聚合及复合查询

转自&#xff1a; https://blog.csdn.net/zx711166/article/details/81906881 聚合查询 聚合是一种基于查询条件对数据进行分桶、计算的方法。 聚合可以嵌套&#xff0c;由此可以组合复杂的操作&#xff08;Bucketing 聚合可以包含 sub-aggregation&#xff09;。 聚合的三种…

3种常见的Class级别的错误

转载自 3种常见的Class级别的错误ClassNotFoundException 很明显&#xff0c;这个错误是 找不到类异常&#xff0c;即在当前classpath路径下找不到这个类。 ClassNotFoundException继承了Exception&#xff0c;是必须捕获的异常&#xff0c;所以这个异常一般发生在显示加载类的…

(转)es 聚合查询并返回每个组的数据

转自&#xff1a; https://blog.csdn.net/ywdhzxf/article/details/84878760 需求 我现在是有一个这样的需求, 我需要在es里面按照 cityarea 进行分组(group by), 并且每个 cityarea的组 需要展示 按照时间排序的前10条, 状态全部为1的数据 实现 { "query": {…

java程序员被误导的一个概念,Set也可以有序

转载自 java程序员被误导的一个概念&#xff0c;90%人不知道我们经常听说List是有序且重复的&#xff0c;Set是无序不重复的。这里有个误区&#xff0c;这里说的顺序有两个概念&#xff0c;一是按添加的顺序排列&#xff0c;二是按自然顺序a-z排列。Set并不是无序的&#xff0c…

es7 bulk api 批量插入es

post localhost:9200/cuiji/_doc/_bulk {"index":{}} {"RCRD_ID":"RD_TR_001", "CUST_NUM":"CUSTA", "LAST_MODIFY_TIME":"2020-07-18 00:00:50", "NOTE_CONTENT":"催记A001"} {…

TreeSet的null值与元素类型的约束

一、TreeSet Java的TreeSet通过TreeMap来实现&#xff0c;具有自然排序的功能。 在默认情况下&#xff0c;元素不允许为null值&#xff0c;元素必须是相同类型&#xff0c;元素必须实现了Comparable接口&#xff1b;否则会出现java.lang.ClassCastException。 可以通过设置Comp…

转-HTTPClient调用https请求,通过基本认证用户名密码(Basic Auth)

转自&#xff1a; https://blog.csdn.net/qq_27605885/article/details/79131483 本文来源是Apache官网例子&#xff1a;http://hc.apache.org/httpcomponents-client-4.5.x/httpclient/examples/org/apache/http/examples/client/ClientAuthentication.java 之前找过很多博客…

你知道void和Void的区别吗

转载自 你知道void和Void的区别吗 区别 void 用于无返回值的方法定义。 Void Void是void的包装方法&#xff0c;和其他基础类型的包装方法不同是Void不能被实例化&#xff0c;Void还可用于一直返回null的方法或者返回null的泛型。 代码示例

httpclient es action_request_validation_exception validation failed 1 no requests added原因和解决方法

【README】 批量插入数据到es&#xff0c;报错如下&#xff1b; action_request_validation_exception validation failed 1 no requests added 原因&#xff1a; 在请求头设置报文为 json格式&#xff0c;但报文并非json格式&#xff0c; 如 java字符串&#xff1a; post l…

POJ1321(DFS)

Problem Descrption 在一个给定形状的棋盘&#xff08;形状可能是不规则的&#xff09;上面摆放棋子&#xff0c;棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列&#xff0c;请编程求解对于给定形状和大小的棋盘&#xff0c;摆放k个棋子的所有可行的…

关于java流的几个概念:IO、BIO、NIO、AIO,有几个人全知道?

转载自 关于java流的几个概念&#xff1a;IO、BIO、NIO、AIO&#xff0c;有几个人全知道&#xff1f; 关于同步、阻塞的知识我之前的文章有介绍&#xff0c;所以关于流用到这些概念与之前多线程用的概念一样。 下面具体来看看java中的几种流 IO/BIO BIO就是指IO&#xff0c;即…

转:字符串和编码

转&#xff1a; https://www.liaoxuefeng.com/wiki/1016959663602400/1017075323632896 字符编码 我们已经讲过了&#xff0c;字符串也是一种数据类型&#xff0c;但是&#xff0c;字符串比较特殊的是还有一个编码问题。 因为计算机只能处理数字&#xff0c;如果要处理文本&…

HDU2067(卡特兰数)

Problem Descrption 小兔的叔叔从外面旅游回来给她带来了一个礼物&#xff0c;小兔高兴地跑回自己的房间&#xff0c;拆开一看是一个棋盘&#xff0c;小兔有所失望。不过没过几天发现了棋盘的好玩之处。从起点(0&#xff0c;0)走到终点(n,n)的最短路径数是C(2n,n),现在小兔又想…

JAVA元注解@interface详解(@Target,@Documented,@Retention,@Inherited)

转载自 JAVA元注解interface详解(Target,Documented,Retention,Inherited) jdk1.5起开始提供了4个元注解&#xff0c;用来定义自定义注解的注解&#xff0c;它们分别是&#xff1a; Target 指定注解使用的目标范围&#xff08;类、方法、字段等&#xff09;&#xff0c;其参考值…

转:字符编码笔记:ASCII,Unicode 和 UTF-8

转&#xff1a; http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html 作者&#xff1a; 阮一峰 日期&#xff1a; 2007年10月28日 今天中午&#xff0c;我突然想搞清楚 Unicode 和 UTF-8 之间的关系&#xff0c;就开始查资料。 这个问题比我想象的复杂&am…