前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

      多线程爬坑之路-Thread和Runable源码解析

      多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

一.线程池ThreadPool的基本定义?

  线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

二.线程池的背景及作用?

  线程池是用来解决多任务并发,频繁创建线程和销毁线程导致资源的浪费和效率低下的一种技术。类似于数据库连接池,这种池化技术用于将部分资源重复利用,或者说是用已有的资源对多个任务进行服务。线程池就是创建固定的线程数量,服务于并发的任务,避免每一个任务,系统都进行线程的创建和销毁,增加系统的负担,线程数量过多时会造成内存溢出。

三.线程池的运用场景:

  频繁的任务创建,快速的任务响应,同等的任务优先级。如果需要很长时间才有一个任务需要执行,那样创建线程池就是浪费资源,如果执行一个任务需要几个小时,那这个任务在线程池中执行一次就会阻塞大量的线程。任务的优先级在线程池没有作用,因为线程池的任务执行顺序是队列。如果我们需要执行的任务有轻重缓急,那么不建议使用线程池。

四.线程池的基本类图

五.线程池的创建过程:

线程池的类图中我们可以得到最终的两个实现类,ThreadPoolExeutorScheduleThreadPoolExeutor,这两个类都可以创建一个线程池。但是通常我们使用这两个类的封装类Exeutors来创建一个线程池。

1、TheadPoolExeutor:运用这个类我们需要传入四个参数

  • corePoolsize:核心线程数量
  • maximumPoolSize:最大线程数量
  • keepAliveTime:最大存活时间(当一个线程没有任务执行时最大的存活时间)
  • until:用于格式化时间,指定时间类型。TimeUnit:一个枚举类,详情请看:http://www.importnew.com/7219.html
  • workQueue:任务队列的接口声明(BlockingQueue<Runnable>是一个继承Queue的接口)
 public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}

2、SheduleThreadPoolExeutor:这个类的四个构造函数可以根据不同的参数构造不同用途的线程池。

 public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, //调用的是super()方法来构造线程池。
new DelayedWorkQueue());
}
   public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}
  public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
 public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}

3、Exeutors:这个类主要是对以上两个类做了包装,通过这个类各种静态方法我们可以创建满足各种需求的线程池。Exeutors类通过传递不同的参数以达到创建不同类型线程池的目的。

Exeutors的方法摘要列表:(省略了部分)完整的类大家可以看源码,API:http://www.javaweb.cc/help/JavaAPI1.6/java/util/concurrent/Executors.html

方法摘要
static Callable<Object> callable(PrivilegedAction<?> action) 
          返回 Callable 对象,调用它时可运行给定特权的操作并返回其结果。
static Callable<Object> callable(PrivilegedExceptionAction<?> action) 
          返回 Callable 对象,调用它时可运行给定特权的异常操作并返回其结果。
static Callable<Object> callable(Runnable task) 
          返回 Callable 对象,调用它时可运行给定的任务并返回 null
static

<T> Callable<T>
callable(Runnable task, T result) 
          返回 Callable 对象,调用它时可运行给定的任务并返回给定的结果。
static ThreadFactory defaultThreadFactory() 
          返回用于创建新线程的默认线程工厂。
static ExecutorService newCachedThreadPool() 
          创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) 
          创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。
static ExecutorService newFixedThreadPool(int nThreads) 
          创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 
          创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
          创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) 
          创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
static ExecutorService newSingleThreadExecutor() 
          创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) 
          创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程,并在需要时使用提供的 ThreadFactory 创建新线程。
static ScheduledExecutorService newSingleThreadScheduledExecutor() 
          创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) 
          创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
static

<T> Callable<T>
privilegedCallable(Callable<T> callable) 
          返回 Callable 对象,调用它时可在当前的访问控制上下文中执行给定的 callable 对象。
static

<T> Callable<T>
privilegedCallableUsingCurrentClassLoader(Callable<T> callable) 
          返回 Callable 对象,调用它时可在当前的访问控制上下文中,使用当前上下文类加载器作为上下文类加载器来执行给定的 callable 对象。
static ThreadFactory privilegedThreadFactory() 
          返回用于创建新线程的线程工厂,这些新线程与当前线程具有相同的权限。
static ExecutorService unconfigurableExecutorService(ExecutorService executor) 
          返回一个将所有已定义的 ExecutorService 方法委托给指定执行程序的对象,但是使用强制转换可能无法访问其他方法。
static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor) 
          返回一个将所有已定义的 ExecutorService 方法委托给指定执行程序的对象,但是使用强制转换可能无法访问其他方法。

列表中红色部分就是一些常用的创建线程池的方法,这些方法的类型分为两类scheduleExecutorService和ExecutorService,这是两个接口,

scheduleExecutorService类型的线程池分两类:

  1.newScheduleThreadPool:是有延迟执行或者定期执行的任务的线程池

  2.newSingleThreadScheduleExecutor:是有延迟执行或者定期执行的任务的单线程线程池

ExecutorService类型的线程池分三类:

  1.newCacheThreadPool:可重用,可扩展(按需创建)

  2.newFixedThreadPool:可重用,固定数量,共享的无界队列

  3.newSingleThreadExeutor:单线程,无界队列                      

Exeutors可通过调用上面五种封装方法创建不同应用场景的线程池,这些方法的具体实现源码如下:

1、newCachedThreadPool源码:

  • 调用ThreadPoolExecutor创建线程:

    • 第一个参数为0表示他的核心线程数是0,可根据任务需要创建线程,每一个使用完得线程具有60秒的存活时间,
    • 第二个参数最大线程数:Integer.MAX_VALUE,int类型的最大值。
    • 第三个参数最大存活时间:60秒,第四个参数:时间的单位是SECONDS,秒。
    • 最后一个表示任务队列:SynchronousQueue(这个类的实现后面再看),这里没有指定线程工厂在ThreadPoolExecutor类中会使用默认的工厂。
 /**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available. These pools will typically improve the performance
* of programs that execute many short-lived asynchronous tasks.
* Calls to {@code execute} will reuse previously constructed
* threads if available. If no existing thread is available, a new
* thread will be created and added to the pool. Threads that have
* not been used for sixty seconds are terminated and removed from
* the cache. Thus, a pool that remains idle for long enough will
* not consume any resources. Note that pools with similar
* properties but different details (for example, timeout parameters)
* may be created using {@link ThreadPoolExecutor} constructors.
*
* @return the newly created thread pool
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
  • 有线程工厂作为参数:与上面唯一不同的是多了一个参数,threadFactory会替代掉默认的工厂来创建线程。
  /**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available, and uses the provided
* ThreadFactory to create new threads when needed.
* @param threadFactory the factory to use when creating new threads
* @return the newly created thread pool
* @throws NullPointerException if threadFactory is null
*/
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}

其他的类我就不一一列出源码了,给出下面的表格,可以一目了然的看到他们之间的创建差异。(其中忽略掉线程工厂Threadfactory和Hanler(对于无法通过一个线程池执行任务的处理程序))因为他们无法对线程池的差异造成影响。

静态方法中的参数主要有三个:ThreadFactor,corePoolsize,Handler.其中corePoolSize最为重要,设置的数量要根据具体的需要而定。过少会造成线程阻塞性能下降,过多会造成资源浪费。

Executors

     静态方法

调用方法(new)

调用方法Executor的参数

corePoolSize

maximumPoolSize

keepAliveTime

 TimeUnit

 workQueue

newCacheThreadPool

ThreadPoolExecutor

0

Integer.MAX_VALUE

60s

SECONDS(秒)

SynchronousQueue

newFixedThreadPool

ThreadPoolExecutor

n(参数)

n(参数)

0s

MILLISECONDS

LinkedBlockingQueue

newSingleThreadExecutor

ThreadPoolExecutor

1

1

0s

MILLISECONDS

LinkedBlockingQueue

newSingleThreadScheduledExecutor

ScheduledThreadPoolExecutor

1

Integer.MAX_VALUE

0s

NANOSECONDS

DelayedWorkQueue

newScheduledThreadPool

ScheduledThreadPoolExecutor

n(参数)

Integer.MAX_VALUE

0s

NANOSECONDS

DelayedWorkQueue

Integer.MAX_VALUE=int型的最大值,2^32-1=2147483647

4、如何选择创建线程池的类型?

  corePoolSize:保证了最少有多少个存活的线程。如果我们的任务很稳定,一直能够保证最少x个任务需要被执行,那我们可以将这个值设置为x,如果我们的任务有时多有时少,有时候没有,我们可以设置为0,让它自动根据需要创建线程,这就是newCacheThreadPool的方法,因为不确定接下来有没有任务,所以这些线程使用完之后保持60秒的活时间,如果有任务则重用这些线程,没任务则60秒后线程销毁。如果我们的任务中,始终有一个任务需要执行,但是有时会有更多的线程,那我们可以选择newSingleThreadScheduledExecutor,他保证了corePoolSize=1,始终有一个线程等待任务。最大值为Integer.MAX_VALUE。我们可以根据自己的场景需要选择合适的线程池。

核心方法从Executors转移到了ThreadPoolExecutor类和ScheduledThreadPoolExecutor类上。下面深入一下构建一个线程池需要的步骤和准备。理解了他是如何创建一个线程池之后我们可以手动写自己的线程池。

6、ThreadPoolExecutor类视图:

总结一下类里面就是三个部分:

  1.构造方法:第一个构造方法部分我们已经研究过了

  2.属性操作:第二个部分属性操作,就是获取我们构造的线程池的一些属性,基本就是构造方法中的参数。

  3.线程池管理:主要还是内部的调用,去对线程的创建和管理,对任务的执行和监控。

线程池一旦创建好了只需要往里面添加任务执行就好了。而我们需要关心的就是创建的线程池性能是否足够,是否可以优化。

示例:创建线程池

 import java.util.concurrent.ThreadFactory;

 public class ThreadFactorImpl implements ThreadFactory{//这里自己实现了一个线程工厂,只是为了下面创建线程池做准备,大家可以忽略掉默认的工厂没这么简单。后面给源码
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
 import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; public class TestThreadPool {
private int num; public int test() {
return num++;
}
public static void main(String[] args) {
ExecutorService es1 = Executors.newCachedThreadPool();
ExecutorService es2 = Executors.newCachedThreadPool(new ThreadFactorImpl());
ExecutorService es3 = Executors.newFixedThreadPool(10);
ExecutorService es4 = Executors.newFixedThreadPool(10, new ThreadFactorImpl());
ExecutorService es5 = Executors.newSingleThreadExecutor();
ExecutorService es6 = Executors.newSingleThreadExecutor(new ThreadFactorImpl());
ExecutorService es7 = Executors.newScheduledThreadPool(10);
ExecutorService es8 = Executors.newScheduledThreadPool(10, new ThreadFactorImpl());
ExecutorService es9 = new ScheduledThreadPoolExecutor(10);
ExecutorService es10 = new ThreadPoolExecutor(1, 10, 20, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
}

当Executors的构造方法满足不了需求的时候,就需要直接通过原始类ThreadPoolExecutor类和ScheduledThreadPoolExecutor类来传入合适的参数。

defaultThreadFactory默认的线程工厂:

 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 = "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;
}
}

创建了线程池,有了线程工厂,当任务量>线程数的时候,工厂就会创建线程去执行任务。

六、线程池是如何管理和执行任务的?

执行任务只需要调用execute()方法就够了,下面我们创建了四个任务分别给各自的对象num数据+1。然后把任务加入线程池,execute方法实际上是加入工作队列的过程。

示例:execute执行任务

 import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class TestThreadPool implements Runnable{
private int num;
public TestThreadPool(int num){
this.num = num;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public void run() {
setNum(++this.num);
}
public static void main(String[] args) {
ExecutorService es1 = Executors.newCachedThreadPool();
//任务t1,t2,t3,t4
TestThreadPool t1 = new TestThreadPool(1);
TestThreadPool t2 = new TestThreadPool(2);
TestThreadPool t3 = new TestThreadPool(3);
TestThreadPool t4 = new TestThreadPool(4);
es1.execute(t1);
es1.execute(t2);
es1.execute(t3);
es1.execute(t4);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t1.getNum());
System.out.println(t2.getNum());
System.out.println(t3.getNum());
System.out.println(t4.getNum());
}
}

执行结果如下:跟我们预想的结果相同。

2
3
4
5

execute方法源码:

由于执行任务的线程是内部工厂创建的所以我们无法观测到线程的状态和属性,通过Execute的源码来看一下它的执行过程:(不同的创建方法对应了不同的线程池类,同时也对应了不同的execute方法)下面是ThreadPoolExecutor类的execute方法源码

 public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}

英语好的直接看源码的注释部分,注释中说:

excute的过程主要分三步:

  1. 如果当前正在运行的线程数小于corePoolSize的话,那么尝试使用传入的任务(command)作为第一个任务启动一个新的线程执行。addWorker函数会原子性的检查线程池的runState以及workerCount防止不应该添加的新线程被添加。如果是假警报的话,那么addWorker函数就会返回false,表示添加新线程失败。
  2. 如果一个Task被成功的加入队列了,然后仍然需要重新check一次是否需要重新添加一个线程,因为有可能在上一次检查到这次检查之间,已经存在的线程已经死亡。或者,自从进入这个方法后,线程池已经被关闭(shut down)。所以我们需要重新check状态,并且在必要的时候,如果处于stopped状态,需要重新回滚到队列中,或者如果没有的话,就需要重新启动一个线程。
  3. 如果不能把task加入到队列中,那么就会尝试去添加一个新的线程,如果它失败了,就知道是已经当前线程池是处于shut down或者处于饱和状态,那么就执行拒绝(reject)操作。

  简单的来说:就是检查线程池的状态,看是否能够把任务加入到队列或者是否需要用一个线程去执行它,最终就是,加入队列等待,或者加入队列执行,或者说拒绝(线程池状态问题。。)

  再往下深入,就要到虚拟机的实现机制上的一些东西了包括同步机制,锁,锁优化问题,等等。至少到目前为止,我们知道了线程池的使用和它的重点在哪里,怎样根据需求去创建一个合适的线程池。

  后面的话就要进入并发问题了,到了线程的难点部分。

[Java多线程]-线程池的基本使用和部分源码解析(创建,执行原理)的更多相关文章

  1. JUC源码学习笔记5——线程池,FutureTask,Executor框架源码解析

    JUC源码学习笔记5--线程池,FutureTask,Executor框架源码解析 源码基于JDK8 参考了美团技术博客 https://tech.meituan.com/2020/04/02/jav ...

  2. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  3. Java中的容器(集合)之HashMap源码解析

    1.HashMap源码解析(JDK8) 基础原理: 对比上一篇<Java中的容器(集合)之ArrayList源码解析>而言,本篇只解析HashMap常用的核心方法的源码. HashMap是 ...

  4. Java多线程——线程池

    系统启动一个新线程的成本是比较高的,因为它涉及到与操作系统的交互.在这种情况下,使用线程池可以很好的提供性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池. 与数据库连接池类似 ...

  5. 深入理解Java多线程——线程池

    目录 为什么需要线程池 定义 ThreadPoolExecutor 工作队列workQueue 不同的线程池 Executor 线程池的工作原理 线程池生命周期 线程池增长策略 线程池大小的设置 线程 ...

  6. java 多线程 线程池:多核CPU利用ExecutorService newWorkStealingPool; ForkJoinPool线程池 执行可拆分的任务RecursiveAction;RecursiveTask

    1,给定并行级别: 1,ExecutorService newWorkStealingPool(int parallelism): 创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队 ...

  7. Java中的容器(集合)之ArrayList源码解析

    1.ArrayList源码解析 源码解析: 如下源码来自JDK8(如需查看ArrayList扩容源码解析请跳转至<Java中的容器(集合)>第十条):. package java.util ...

  8. java多线程--线程池的使用

    程序启动一个新线程的成本是很高的,因为涉及到要和操作系统进行交互,而使用线程池可以很好的提高性能,尤其是程序中当需要创建大量生存期很短的线程时,应该优先考虑使用线程池. 线程池的每一个线程执行完毕后, ...

  9. java多线程-线程池

    线程池(Thread Pool)对于限制应用程序中同一时刻运行的线程数很有用.因为每启动一个新线程都会有相应的性能开销,每个线程都需要给栈分配一些内存等等. 我们可以把并发执行的任务传递给一个线程池, ...

随机推荐

  1. sql脚本创建用户角色权限表

    /******************************************/ /* 名称:权限管理 */ /* 编写日期:2010.5.20 */ /* 开发者:dangqinghua * ...

  2. Python 内置函数介绍

    作者博文地址:http://www.cnblogs.com/spiritman/ Python Built-in Functions

  3. 按Right-BICEP要求的测试用例

    测试方法:Right-BICEP 测试计划 1.Right-结果是否正确? 2.B-是否所有的边界条件都是正确的? 3.P-是否满足性能要求? 4.结果是否有符合要求的20道题目? 5.所得到的最大数 ...

  4. asp.netcore mvc 权限拦截

    1-背景介绍 需要做一个简单权限系统,基于 角色,用户,菜单 的模式 基于IActionFilter全局拦截,在内部跳转或者浏览器跳转的时候,拦截是成功的,当通过AJAX 请求的时候,页面就不会跳转 ...

  5. IT小小鸟 读书笔记

    讲真的,整本书我并没有看完,翻阅了一下,然后小小的借鉴了一下! 首先设计你自己的进度条 进度条的设计是一个很多人都知道的故事:同样的耗时,如果不给任何进度提示,只是在完成之后才弹出一个完成消息,中间没 ...

  6. HttpCookie Class

    提供创建和操作各 HTTP Cookie 的类型安全方法. #region 写入指定Cookie的值 +static void WriteCookie(string cookieName, strin ...

  7. lintcode-392-打劫房屋

    392-打劫房屋 假设你是一个专业的窃贼,准备沿着一条街打劫房屋.每个房子都存放着特定金额的钱.你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且 当相邻的两个房子同一天被打劫时,该系统会自 ...

  8. git找回当前目录下误删的所有文件

    git checkout . 参考:http://opentechschool.github.io/social-coding/extras/delete-restore.html

  9. 【Leetcode】179. Largest Number

    Given a list of non negative integers, arrange them such that they form the largest number. For exam ...

  10. Java实现的词频统计

    要求: 1.读取文件: 2.记录出现的词汇及出现频率: 3.按照频率降序排列: 4.输出结果. 概要: 1.读取的文件路径是默认的,为了方便调试,将要统计的文章.段落复制到文本中即可:2.只支持英文: ...