精华内容
下载资源
问答
  • 进程线程的区别(超详细)

    万次阅读 多人点赞 2019-10-03 21:57:46
    进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。 与进程不同的是同类的多个线程共享进程的堆方法区资源,但每个线程有...

    进程和线程

    进程

    一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。

    任务管理器

    线程

    进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

    与进程不同的是同类的多个线程共享进程的方法区资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

    Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。

    public class MultiThread {
    	public static void main(String[] args) {
    		// 获取 Java 线程管理 MXBean
    		ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    		// 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
    		ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
    		// 遍历线程信息,仅打印线程 ID 和线程名称信息
    		for (ThreadInfo threadInfo : threadInfos) {
    			System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
    		}
    	}
    }
    

    上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):

    [6] Monitor Ctrl-Break //监听线程转储或“线程堆栈跟踪”的线程
    [5] Attach Listener //负责接收到外部的命令,而对该命令进行执行的并且把结果返回给发送者
    [4] Signal Dispatcher // 分发处理给 JVM 信号的线程
    [3] Finalizer //在垃圾收集前,调用对象 finalize 方法的线程
    [2] Reference Handler //用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收的线程
    [1] main //main 线程,程序入口
    

    从上面的输出内容可以看出:一个 Java 程序的运行是 main 线程和多个其他线程同时运行

    进程与线程的区别总结

    线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

    根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

    资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

    包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

    内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

    影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

    执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

    从 JVM 角度说进程和线程之间的关系(重要)

    图解进程和线程的关系

    下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。

    在这里插入图片描述

    从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器虚拟机栈本地方法栈

    程序计数器为什么是私有的?

    程序计数器主要有下面两个作用:

    1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
    2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

    需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。

    所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置

    虚拟机栈和本地方法栈为什么是私有的?

    • 虚拟机栈:每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
    • 本地方法栈:和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

    所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。

    一句话简单了解堆和方法区

    堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

    多进程和多线程区别

    多进程:操作系统中同时运行的多个程序

    多线程:在同一个进程中同时运行的多个任务

    举个例子,多线程下载软件,可以同时运行多个线程,但是通过程序运行的结果发现,每一次结果都不一致。 因为多线程存在一个特性:随机性。造成的原因:CPU在瞬间不断切换去处理各个线程而导致的,可以理解成多个线程在抢CPU资源。

    多线程提高CPU使用率

    多线程

    多线程并不能提高运行速度,但可以提高运行效率,让CPU的使用率更高。但是如果多线程有安全问题或出现频繁的上下文切换时,运算速度可能反而更低。

    Java中的多线程

    Java程序的进程里有几个线程:主线程,垃圾回收线程(后台线程)等

    在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。

    Java支持多线程,当Java程序执行main方法的时候,就是在执行一个名字叫做main的线程,可以在main方法执行时,开启多个线程A,B,C,多个线程 main,A,B,C同时执行,相互抢夺CPU,Thread类是java.lang包下的一个常用类,每一个Thread类的对象,就代表一个处于某种状态的线程

    展开全文
  • 1.Callable接口 ...Callable的接口Runnable接口的区别是:Callable有一个call方法能够得到任务执行结果,而Runnable的run方法无法得到返回结果。Callable的接口的定义如下: public interface Callable {

    1.Callable<V>接口

    ThreadPoolExecutor不仅可以执行Runnable的实现类,还可以执行Callable接口的实现类。Callable的接口和Runnable接口的区别是:Callable有一个call方法能够得到任务执行结果,而Runnable的run方法无法得到返回结果。Callable的接口的定义如下:

    public interface Callable<V> {   
          V   call()   throws Exception;   
    } 
    该接口声明了一个名称为call()的方法,同时这个方法可以有返回值V,也可以抛出异常。嗯,对该接口我们先了解这么多就行,下面我们来说明如何使用,前篇文章我们说过,无论是Runnable接口的实现类还是Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行,ThreadPoolExecutor或ScheduledThreadPoolExecutor都实现了ExcutorService接口,而因此Callable需要和Executor框架中的ExcutorService结合使用,我们先看看ExecutorService提供的方法:
    <T> Future<T> submit(Callable<T> task);  
    <T> Future<T> submit(Runnable task, T result);  
    Future<?> submit(Runnable task);  
    第一个方法:submit提交一个实现Callable接口的任务,并且返回封装了异步计算结果的Future。
    第二个方法:submit提交一个实现Runnable接口的任务,并且指定了在调用Future的get方法时返回的result对象。
    第三个方法:submit提交一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。
    因此我们只要创建好我们的线程对象(实现Callable接口或者Runnable接口),然后通过上面3个方法提交给线程池去执行即可。还有点要注意的是,除了我们自己实现Callable对象外,我们还可以使用工厂类Executors来把一个Runnable对象包装成Callable对象。Executors工厂类提供的方法如下:

    public static Callable<Object> callable(Runnable task)  
    public static <T> Callable<T> callable(Runnable task, T result)  

    2.Future<V>接口

    Future<V>接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。我们看看Future接口的源码:

    public interface Future<V> {  
        boolean cancel(boolean mayInterruptIfRunning);  
        boolean isCancelled();  
        boolean isDone();  
        V get() throws InterruptedException, ExecutionException;  
        V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;  
    }
    方法说明:
    V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
    V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。
    boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
    boolean isCanceller() :如果任务完成前被取消,则返回true。
    boolean cancel(boolean mayInterruptRunning) :如果任务还没开始,执行cancel(...)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(...)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。
    通过方法分析我们也知道实际上Future提供了3种功能:(1)能够中断执行中的任务(2)判断任务是否执行完成(3)获取任务执行完成后额结果。


    接下来,我们用一个具体的例子说明他们的用法:

    /*
     * 主线程类
     */
    public class ThreadPoolTest2 {
    	//向线程池提交一个单独的任务并得到任务执行的结果
    	public static void subTask(ThreadPoolExecutor executor){
    		Future<Integer> fut=executor.submit(new Callable<Integer>() {
    			public Integer call() throws Exception {
    				System.out.println("计算任务正在执行。。。");
    				int summ=0;
    				for(int i=1;i<=100;i++){
    					Thread.currentThread().sleep(100);
    					summ=summ+i;
    				}
    				System.out.println("计算任务结束");
    				return summ;
    			}
    		});
    		try {
    			//当然这里我们还可以中途终止任务的执行,但也有可能终止不了。
    			//Thread.currentThread().sleep(15000);
    			//fut.cancel(true);//15秒后取消任务的执行
    			Integer result=fut.get();
    			System.out.println("单独提交的任务执行结果:"+result);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    	public static void main(String[] args) {
    		// 创建线程池对象
    		ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
    				new LinkedBlockingQueue<Runnable>());
    		// 该线程用于对线程池的检测
    		new Thread(new CheckTask(executor)).start();
    		try {
    			//执行15个任务
    			for (int i = 1; i <= 15; i++) {
    				MyTask myTask = new MyTask("task==" + i);
    				executor.execute(myTask);
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		//向线程池提交一个单独的任务并得到任务执行的结果
    		subTask(executor);
    		executor.shutdown();
    	}
    }
    
    其它的类请参照【java线程池之ThreadPoolExecutor(一)】

    如果有人看不懂内部类的话,我们也可以改成这个样子:

    /*
     * 自定义Callable任务
     */
    class MyCallable implements Callable<Integer>{
    	public Integer call() throws Exception {
    		System.out.println("计算任务正在执行。。。");
    		int summ=0;
    		for(int i=1;i<=100;i++){
    			Thread.currentThread().sleep(100);
    			summ=summ+i;
    		}
    		System.out.println("计算任务结束");
    		Integer result=new Integer(summ); 
    		return result;
    	}
    }
    =============================

    /*
     * 主线程类
     */
    public class ThreadPoolTest2 {
    	//向线程池提交一个单独的任务并得到任务执行的结果
    	public static void subTask(ThreadPoolExecutor executor){
    		Future<Integer> fut=executor.submit(new MyCallable());
    		try {
    			//当然这里我们还可以中途终止任务的执行,但也有可能终止不了。
    			//Thread.currentThread().sleep(15000);
    			//fut.cancel(true);//15秒后取消任务的执行
    			Integer result=fut.get();
    			System.out.println("单独提交的任务执行结果:"+result);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    	public static void main(String[] args) {
    		// 创建线程池对象
    		ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
    				new LinkedBlockingQueue<Runnable>());
    		// 该线程用于对线程池的检测
    		new Thread(new CheckTask(executor)).start();
    		try {
    			//执行15个任务
    			for (int i = 1; i <= 15; i++) {
    				MyTask myTask = new MyTask("task==" + i);
    				executor.execute(myTask);
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		//向线程池提交一个单独的任务并得到任务执行的结果
    		subTask(executor);
    		executor.shutdown();
    	}
    }
    ==================执行结果如下所示:==============

    正在执行task==2
    正在执行task==4
    正在执行task==1
    正在执行task==3
    正在执行task==5
    =========第1次检测start===========
    线程池中核心线程数:5
    线程池中活跃线程数目:5
    线程池中允许的最大线程数目:10
    队列中等待执行的任务数目:11
    已经执行完成的任务数目:0
    =========第1次检测end===========
    =========第2次检测start===========
    线程池中核心线程数:5
    线程池中活跃线程数目:5
    线程池中允许的最大线程数目:10
    队列中等待执行的任务数目:11
    已经执行完成的任务数目:0
    =========第2次检测end===========
    ......
    
    线程:task==11执行完毕
    正在执行task==14
    线程:task==8执行完毕
    正在执行task==15
    线程:task==10执行完毕
    计算任务正在执行。。。
    =========第14次检测start===========
    线程池中核心线程数:5
    线程池中活跃线程数目:5
    线程池中允许的最大线程数目:10
    队列中等待执行的任务数目:0
    已经执行完成的任务数目:11
    =========第14次检测end===========
    ........
    
    计算任务结束
    单独提交的任务执行结果:5050
    =========第24次检测start===========
    线程池中核心线程数:5
    线程池中活跃线程数目:0
    线程池中允许的最大线程数目:10
    队列中等待执行的任务数目:0
    已经执行完成的任务数目:16
    =========第24次检测end===========
    
    请大家注意文本加粗的地方。










    展开全文
  • (3)当workQueue放不下新入的任务时,新建线程加入线程池,并处理请求,如果池子大小撑到了maximumPoolSize就用RejectedExecutionHandler来拒绝处理.  (4)另外,当线程池的线程数大于corePoolSize的时候,多余的...

            API  文档中很清楚,SpringFrameWork 的 ThreadPoolTaskExecutor 是辅助 JDK 的 ThreadPoolExecutor  的工具类,它将属性通过 JavaBeans 的命名规则提供出来,方便进行配置。

    1.JDK之ThreadPoolExecutor的使用

     Spring中的ThreadPoolTaskExecutor是借助于JDK并发包中的java.util.concurrent.ThreadPoolExecutor来实现的.下面先学习下ThreadPoolExecutor中的相关信息.ThreadPoolExecutor构造函数如下: 

    Java代码   收藏代码
    1. public ThreadPoolExecutor(int corePoolSize,  
    2.                           int maximumPoolSize,  
    3.                           long keepAliveTime,  
    4.                           TimeUnit unit,  
    5.                           BlockingQueue<Runnable> workQueue,  
    6.                           ThreadFactory threadFactory,  
    7.                           RejectedExecutionHandler handler) {  

    下面分别说下各项代表的具体意义: 
    int corePoolSize:线程池维护线程的最小数量. 
    int maximumPoolSize:线程池维护线程的最大数量. 
    long keepAliveTime:空闲线程的存活时间. 
    TimeUnit unit: 时间单位,现有纳秒,微秒,毫秒,秒枚举值. 
    BlockingQueue<Runnable> workQueue:持有等待执行的任务队列. 
    RejectedExecutionHandler handler: 用来拒绝一个任务的执行,有两种情况会发生这种情况。 
    一是:在execute方法中若addIfUnderMaximumPoolSize(command)为false,即线程池已经饱和; 
    二是:在execute方法中, 发现runState!=RUNNING || poolSize == 0,即已经shutdown,就调用ensureQueuedTaskHandled(Runnable command),在该方法中有可能调用reject。 

    Reject策略预定义有四种: 
    (1)ThreadPoolExecutor.AbortPolicy策略,是默认的策略,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。 
    (2)ThreadPoolExecutor.CallerRunsPolicy策略 ,调用者的线程会执行该任务,如果执行器已关闭,则丢弃. 
    (3)ThreadPoolExecutor.DiscardPolicy策略,不能执行的任务将被丢弃. 
    (4)ThreadPoolExecutor.DiscardOldestPolicy策略,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程). 

    2. Spring中ThreadPoolTaskExecutor的使用

     最常用方式就是做为BEAN注入到容器中,如下代码: 

    Java代码   收藏代码
    1. <bean id="threadPoolTaskExecutor"  
    2.     class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">  
    3.     <property name="corePoolSize" value="10" />  
    4.     <property name="maxPoolSize" value="15" />  
    5.     <property name="queueCapacity" value="1000" />  
    6. </bean>  

    ThreadPoolExecutor执行器的处理流程: 
    (1)当线程池大小小于corePoolSize就新建线程,并处理请求. 
    (2)当线程池大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去从workQueue中取任务并处理. 
    (3)当workQueue放不下新入的任务时,新建线程加入线程池,并处理请求,如果池子大小撑到了maximumPoolSize就用RejectedExecutionHandler来做拒绝处理. 
    (4)另外,当线程池的线程数大于corePoolSize的时候,多余的线程会等待keepAliveTime长的时间,如果无请求可处理就自行销毁. 
    了解清楚了ThreadPoolExecutor的执行流程,开头提到的org.springframework.core.task.TaskRejectedException异常也就好理解了,ThreadPoolTaskExecutor类中使用的 就是ThreadPoolExecutor.AbortPolicy()策略,直接抛出异常。

    3.spring线程池与FutureTask配合使用获取任务执行状态

     

    用ThreadPoolExecutor的时候,又想知道被执行的任务的执行情况,这时就可以用FutureTask。

     ThreadPoolTask:

    package zmx.spring.threadpool.test;
    
    
    import java.io.Serializable; 
    import java.util.concurrent.Callable; 
       
    public class ThreadPoolTask implements Callable<String>, Serializable { 
    
         private static final long serialVersionUID = 0; 
         
         // 保存任务所需要的数据 
         private Object threadPoolTaskData; 
         private static int consumeTaskSleepTime = 2000; 
         public ThreadPoolTask(Object tasks) { 
             this.threadPoolTaskData = tasks; 
         }
         
    	 @Override
         public String call() throws Exception { 
            // 处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句 
            System.out.println("开始执行任务:" + threadPoolTaskData); 
            String result = ""; 
            //便于观察,等待一段时间 
            try {  
                //Thread.sleep((long)(Math.random()*consumeTaskSleepTime));
                for (long i = 0; i < 10000000; i++) { 
                } 
    
                result = "OK"; 
            } catch (Exception e) { 
                e.printStackTrace(); 
                result = "ERROR"; 
            }  
            return result; 
        }
    
     
    } 
    
    


    模拟客户端提交的线程:

    package zmx.spring.threadpool.test;
    
    
    import java.util.concurrent.ExecutionException; 
    
    
    import java.util.concurrent.FutureTask; 
    import java.util.concurrent.TimeUnit; 
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 
    
    public class StartTaskThread implements Runnable { 
    
         private ThreadPoolTaskExecutor threadPoolTaskExecutor; 
         private int i; 
         public StartTaskThread(ThreadPoolTaskExecutor threadPoolTaskExecutor, int i) { 
             this.threadPoolTaskExecutor = threadPoolTaskExecutor; 
             this.i = i; 
         } 
    
         @Override 
         public void run() { 
             String task = "task@ " + i; 
             System.out.println("创建任务并提交到线程池中:" + task); 
             FutureTask<String> futureTask = new FutureTask<String>(new ThreadPoolTask(task)); 
             threadPoolTaskExecutor.execute(futureTask); 
             // 在这里可以做别的任何事情 
             String result = null; 
             try { 
                 // 取得结果,同时设置超时执行时间为1秒。同样可以用future.get(),不设置执行超时时间取得结果 
                 // result = futureTask.get(1000, TimeUnit.MILLISECONDS);
                 while(true){
                     if(futureTask.isDone()){
                    	 result = futureTask.get();
                    	 break;
                     } 
                 }
    
             } catch (InterruptedException e) { 
                 futureTask.cancel(true); 
             } catch (ExecutionException e) { 
                 futureTask.cancel(true); 
             } catch (Exception e) { 
                 futureTask.cancel(true); 
                 //超时后,进行相应处理 
             } finally { 
                 System.out.println("task@" + i + ":result=" + result); 
             } 
    
         } 
    
    } 
    
    

    spring配置文件:

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:jee="http://www.springframework.org/schema/jee" 
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd   
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd   
           http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd   
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
         
     
        
       <bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> 
             <!-- 核心线程数,默认为1 --> 
             <property name="corePoolSize" value="10" /> 
             <!-- 最大线程数,默认为Integer.MAX_VALUE --> 
             <property name="maxPoolSize" value="50" /> 
    
             <!-- 队列最大长度,一般需要设置值: 大于等于notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE -->
             <property name="queueCapacity" value="50" /> 
     
             <!-- 线程池维护线程所允许的空闲时间,默认为60s --> 
             <property name="keepAliveSeconds" value="300" /> 
             <!-- 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者 --> 
             <property name="rejectedExecutionHandler"> 
                 <!-- AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常 --> 
                 <!-- CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度 --> 
                 <!-- DiscardOldestPolicy:抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行 --> 
                 <!-- DiscardPolicy:抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行 --> 
                 <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /> 
             </property> 
         </bean> 
        
        
    </beans>
    

    测试类:

    package zmx.spring.threadpool.test;
    
    
    import org.junit.Test; 
    import org.junit.runner.RunWith; 
    import org.springframework.beans.factory.annotation.Autowired; 
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 
    import org.springframework.test.context.ContextConfiguration;  
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 
    @RunWith(SpringJUnit4ClassRunner.class) 
    // 指定的运行runner,并且把你所指定的Runner作为参数传递给它 
    @ContextConfiguration(locations = "classpath:zmx/spring/threadpool/test/applicationContext.xml") 
    public class TestThreadPool{
    	
         private static int produceTaskSleepTime = 10; 
         private static int produceTaskMaxNumber = 100; 
         @Autowired 
         private ThreadPoolTaskExecutor threadPoolTaskExecutor; 
         
         public ThreadPoolTaskExecutor getThreadPoolTaskExecutor() { 
             return threadPoolTaskExecutor; 
         } 
         public void setThreadPoolTaskExecutor(ThreadPoolTaskExecutor threadPoolTaskExecutor) { 
             this.threadPoolTaskExecutor = threadPoolTaskExecutor; 
         } 
    
         @Test 
         public void testThreadPoolExecutor() { 
             for (int i = 1; i <= produceTaskMaxNumber; i++) { 
                 try { 
                     Thread.sleep(produceTaskSleepTime); 
                 } catch (InterruptedException e1) { 
                     e1.printStackTrace(); 
                 } 
                 new Thread(new StartTaskThread(threadPoolTaskExecutor, i)).start(); 
                 
                 
             } 
             
             threadPoolTaskExecutor.shutdown();
         } 
    
     } 
    
    

    运行结果:

    创建任务并提交到线程池中:task@ 1
    开始执行任务:task@ 1
    创建任务并提交到线程池中:task@ 2
    task@1:result=OK
    开始执行任务:task@ 2
    task@2:result=OK
    创建任务并提交到线程池中:task@ 3
    开始执行任务:task@ 3
    task@3:result=OK
    创建任务并提交到线程池中:task@ 4
    开始执行任务:task@ 4
    task@4:result=OK
    创建任务并提交到线程池中:task@ 5
    开始执行任务:task@ 5
    task@5:result=OK
    创建任务并提交到线程池中:task@ 6
    开始执行任务:task@ 6
    task@6:result=OK
    创建任务并提交到线程池中:task@ 7
    开始执行任务:task@ 7


     

     

     

     

     

     

     

    展开全文
  • 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的几行代码,我们需要知道其输出内容顺序。因为javascript是一门单线程语言,所以我们...

    转载自:https://juejin.im/post/59e85eebf265da430d571f89

    本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。

    不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的几行代码,我们需要知道其输出内容和顺序。因为javascript是一门单线程语言,所以我们可以得出结论:

    • javascript是按照语句出现的顺序执行的

    看到这里读者要打人了:我难道不知道js是一行一行执行的?还用你说?稍安勿躁,正因为js是一行一行执行的,所以我们以为js都是这样的:

    let a = '1';
    console.log(a);
    
    let b = '2';
    console.log(b);复制代码

     

     

     

    然而实际上js是这样的:

    setTimeout(function(){
        console.log('定时器开始啦')
    });
    
    new Promise(function(resolve){
        console.log('马上执行for循环啦');
        for(var i = 0; i < 10000; i++){
            i == 99 && resolve();
        }
    }).then(function(){
        console.log('执行then函数啦')
    });
    
    console.log('代码执行结束');复制代码

     

     

     

    依照js是按照语句出现的顺序执行这个理念,我自信的写下输出结果:

    //"定时器开始啦"
    //"马上执行for循环啦"
    //"执行then函数啦"
    //"代码执行结束"复制代码

    去chrome上验证下,结果完全不对,瞬间懵了,说好的一行一行执行的呢?

     

     

     

    我们真的要彻底弄明白javascript的执行机制了。

    1.关于javascript

    javascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。所以一切javascript版的"多线程"都是用单线程模拟出来的,一切javascript多线程都是纸老虎!

    2.javascript事件循环

    既然js是单线程,那就像只有一个窗口的银行,客户需要排队一个一个办理业务,同理js任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:

    • 同步任务
    • 异步任务

    当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。关于这部分有严格的文字定义,但本文的目的是用最小的学习成本彻底弄懂执行机制,所以我们用导图来说明:

     

     

     

    导图要表达的内容用文字来表述的话:

    • 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
    • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
    • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
    • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

    我们不禁要问了,那怎么知道主线程执行栈为空啊?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

    说了这么多文字,不如直接一段代码更直白:

    let data = [];
    $.ajax({
        url:www.javascript.com,
        data:data,
        success:() => {
            console.log('发送成功!');
        }
    })
    console.log('代码执行结束');复制代码

    上面是一段简易的ajax请求代码:

    • ajax进入Event Table,注册回调函数success
    • 执行console.log('代码执行结束')
    • ajax事件完成,回调函数success进入Event Queue。
    • 主线程从Event Queue读取回调函数success并执行。

    相信通过上面的文字和代码,你已经对js的执行顺序有了初步了解。接下来我们来研究进阶话题:setTimeout。

    3.又爱又恨的setTimeout

    大名鼎鼎的setTimeout无需再多言,大家对他的第一印象就是异步可以延时执行,我们经常这么实现延时3秒执行:

    setTimeout(() => {
        console.log('延时3秒');
    },3000)复制代码

    渐渐的setTimeout用的地方多了,问题也出现了,有时候明明写的延时3秒,实际却5,6秒才执行函数,这又咋回事啊?

    先看一个例子:

    setTimeout(() => {
        task();
    },3000)
    console.log('执行console');复制代码

    根据前面我们的结论,setTimeout是异步的,应该先执行console.log这个同步任务,所以我们的结论是:

    //执行console
    //task()复制代码

    去验证一下,结果正确!
    然后我们修改一下前面的代码:

    setTimeout(() => {
        task()
    },3000)
    
    sleep(10000000)复制代码

    乍一看其实差不多嘛,但我们把这段代码在chrome执行一下,却发现控制台执行task()需要的时间远远超过3秒,说好的延时三秒,为啥现在需要这么长时间啊?

    这时候我们需要重新理解setTimeout的定义。我们先说上述代码是怎么执行的:

    • task()进入Event Table并注册,计时开始。
    • 执行sleep函数,很慢,非常慢,计时仍在继续。
    • 3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep也太慢了吧,还没执行完,只好等着。
    • sleep终于执行完了,task()终于从Event Queue进入了主线程执行。

    上述的流程走完,我们知道setTimeout这个函数,是经过指定时间后,把要执行的任务(本例中为task())加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。

    我们还经常遇到setTimeout(fn,0)这样的代码,0秒后执行又是什么意思呢?是不是可以立即执行呢?

    答案是不会的,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。举例说明:

    //代码1
    console.log('先执行这里');
    setTimeout(() => {
        console.log('执行啦')
    },0);复制代码
    //代码2
    console.log('先执行这里');
    setTimeout(() => {
        console.log('执行啦')
    },3000);复制代码

    代码1的输出结果是:

    //先执行这里
    //执行啦复制代码

    代码2的输出结果是:

    //先执行这里
    // ... 3s later
    // 执行啦复制代码

    关于setTimeout要补充的是,即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。有兴趣的同学可以自行了解。

    4.又恨又爱的setInterval

    上面说完了setTimeout,当然不能错过它的孪生兄弟setInterval。他俩差不多,只不过后者是循环的执行。对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。

    唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。这句话请读者仔细品味。

    5.Promise与process.nextTick(callback)

    传统的定时器我们已经研究过了,接着我们探究Promiseprocess.nextTick(callback)的表现。

    Promise的定义和功能本文不再赘述,不了解的读者可以学习一下阮一峰老师的Promise。而process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。

    我们进入正题,除了广义的同步任务和异步任务,我们对任务有更精细的定义:

    • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
    • micro-task(微任务):Promise,process.nextTick

    不同类型的任务会进入对应的Event Queue,比如setTimeoutsetInterval会进入相同的Event Queue。

    事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。听起来有点绕,我们用文章最开始的一段代码说明:

    setTimeout(function() {
        console.log('setTimeout');
    })
    
    new Promise(function(resolve) {
        console.log('promise');
    }).then(function() {
        console.log('then');
    })
    
    console.log('console');复制代码
    • 这段代码作为宏任务,进入主线程。
    • 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。(注册过程与上同,下文不再描述)
    • 接下来遇到了Promisenew Promise立即执行,then函数分发到微任务Event Queue。
    • 遇到console.log(),立即执行。
    • 好啦,整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行。
    • ok,第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。
    • 结束。

    事件循环,宏任务,微任务的关系如图所示:

     

     

     

    我们来分析一段较复杂的代码,看看你是否真的掌握了js的执行机制:

    console.log('1');
    
    setTimeout(function() {
        console.log('2');
        process.nextTick(function() {
            console.log('3');
        })
        new Promise(function(resolve) {
            console.log('4');
            resolve();
        }).then(function() {
            console.log('5')
        })
    })
    process.nextTick(function() {
        console.log('6');
    })
    new Promise(function(resolve) {
        console.log('7');
        resolve();
    }).then(function() {
        console.log('8')
    })
    
    setTimeout(function() {
        console.log('9');
        process.nextTick(function() {
            console.log('10');
        })
        new Promise(function(resolve) {
            console.log('11');
            resolve();
        }).then(function() {
            console.log('12')
        })
    })复制代码

    第一轮事件循环流程分析如下:

    • 整体script作为第一个宏任务进入主线程,遇到console.log,输出1。
    • 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1
    • 遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1
    • 遇到Promisenew Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1
    • 又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2
    宏任务Event Queue微任务Event Queue
    setTimeout1process1
    setTimeout2then1
    • 上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。

    • 我们发现了process1then1两个微任务。

    • 执行process1,输出6。
    • 执行then1,输出8。

    好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:

    • 首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2
    宏任务Event Queue微任务Event Queue
    setTimeout2process2
     then2
    • 第二轮事件循环宏任务结束,我们发现有process2then2两个微任务可以执行。
    • 输出3。
    • 输出5。
    • 第二轮事件循环结束,第二轮输出2,4,3,5。
    • 第三轮事件循环开始,此时只剩setTimeout2了,执行。
    • 直接输出9。
    • process.nextTick()分发到微任务Event Queue中。记为process3
    • 直接执行new Promise,输出11。
    • then分发到微任务Event Queue中,记为then3
    宏任务Event Queue微任务Event Queue
     process3
     then3
    • 第三轮事件循环宏任务执行结束,执行两个微任务process3then3
    • 输出10。
    • 输出12。
    • 第三轮事件循环结束,第三轮输出9,11,10,12。

    整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。
    (请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)

    process.nextTick(function(){
        console.log(7);
    });
    
    new Promise(function(resolve){
        console.log(3);
        resolve();
        console.log(4);
    }).then(function(){
        console.log(5);
    });
    
    process.nextTick(function(){
        console.log(8);
    });

    这段代码运行结果是3,4,7,8,5
    process.nextTick和Promise都是Microtasks,为什么process.nextTick会先执行?

    rocess.nextTick 永远大于 promise.then,原因其实很简单。。。在Node中,_tickCallback在每一次执行完TaskQueue中的一个任务后被调用,而这个_tickCallback中实质上干了两件事:

    1.nextTickQueue中所有任务执行掉(长度最大1e4,Node版本v6.9.1)

    2.第一步执行完后执行_runMicrotasks函数,执行microtask中的部分(promise.then注册的回调)

    所以很明显 process.nextTick > promise.then

    6.写在最后

    (1)js的异步

    我们从最开头就说javascript是一门单线程语言,不管是什么新框架新语法糖实现的所谓异步,其实都是用同步的方法去模拟的,牢牢把握住单线程这点非常重要。

    (2)事件循环Event Loop

    事件循环是js实现异步的一种方法,也是js的执行机制。

    (3)javascript的执行和运行

    执行和运行有很大的区别,javascript在不同的环境下,比如node,浏览器,Ringo等等,执行方式是不同的。而运行大多指javascript解析引擎,是统一的。

    (4)setImmediate

    微任务和宏任务还有很多种类,比如setImmediate等等,执行都是有共同点的,有兴趣的同学可以自行了解。

    (5)最后的最后

    • javascript是一门单线程语言
    • Event Loop是javascript的执行机制

    牢牢把握两个基本点,以认真学习javascript为中心,早日实现成为前端高手的伟大梦想!

    展开全文
  • triggers: 触发器,用于设定触发任务的条件,触发器包含了调度的逻辑,每个任务都有自己的触发器决定该任务下次运行的时间。 job stores: 任务储存器,用于存放任务,把任务放在内存或者数据库中,一个 executors: ...
  • MapTaskReduceTask运行机制以及Map任务的并行度1、MapTask运行机制详解以及Map任务的并行度详细步骤:mapTask的一些基础设置配置(mapred-site.xml当中社会):2、ReduceTask 工作机制以及reduceTask的并行度详细...
  • 任务、微任务

    万次阅读 多人点赞 2018-10-10 10:58:47
    任务与微任务? B-树中含511个关键字,B-树为3阶,则包含叶子节点层该树最大深度为? 8 9 10 11
  • 前提说明 为了确保服务不会被过多的http长连接压垮,我们需要对tomcat设定个最大连接数,超过这个连接数的请求会拒绝,让其负载到其它机器。...动手去 一开始根据故障todoList提供的参数MaxKeepAliveReques...
  • 任务学习(Multi-Task Learning)

    万次阅读 2018-10-23 10:45:27
     多任务学习(Multi-task learning)是任务学习(single-task learning)相对的一种机器学习方法。在机器学习领域,标准的算法理论是一次学习一个任务,也就是系统的输出为实数的情况。复杂的学习问题先被分解...
  • Quartz 是一种功能丰富的,开放源码的作业调度库,可以在几乎任何Java应用程序集成 - 从最小的独立的应用程序到规模最大电子商务系统。Quartz可以用来创建简单或复杂的日程安排执行几十,几百,甚至是十万的作业数 -...
  • 向线程池提交任务的两个方法 execute()、submit() 的区别,我总结的主要有四点 (1)申明位置不同:execute() 方法定义在 Executor 接口中,submit() 方法定义在 ExecutorService 接口中;  ExecutorService 接口...
  • 《机器学习实战》学习笔记(一):机器学习基础

    万次阅读 多人点赞 2019-08-19 17:01:32
    专栏【机器学习】 【机器学习】《机器学习实战》读书笔记及代码 总目录 ... ————————————————...目录专栏【机器学习】本章内容何谓机器学习2、关键术语3、机器学习的主要任务4、如何选择合适的算法5...
  • 分布式计算任务调度算法总结

    万次阅读 2019-02-20 17:08:55
    主要有这些因素影响着分布式系统的性能:网络延迟、数据通信效能、计算节点处理能力、任务的分割、无法预算处理时间、任务的颠簸等等。 我们在寻求分布式计算调度算法时,就是有针对性的以解决这些问题为目的,从...
  • 任务执行、取消关闭

    千次阅读 2017-05-09 16:37:46
    任务TaskTask就是抽象、离散的工作单元unit。把一个应用程序的工作work分离到任务中,可简化程序的管理;这种分离还在不同事务间划分了 自然的分界线,可以方便程序出现错误时进行恢复;同时,这种分离还可以为并行...
  • 关键词:内部任务评价(Intrinsic Evaluation) 外部任务评价(extrinsic evaluations)。超参数影响下的类比评价任务。人类决策词向量距离的相关性。结合上下文处理歧义。窗口分类。 这个课堂笔记我们将会对词...
  • 写这篇博文的起因是,我在论坛宣传我开源的新项目YTask(go语言异步任务队列)时,有小伙伴在下面回了一句“为什么不用nsq?”。这使我想起,我在同事介绍celery时同事说了一句“这不就是kafka吗?”。 那么YTask...
  • 浅论定点DSP与浮点DSP的区别

    万次阅读 2018-04-02 20:43:54
    这两者有什么区别呢?于是专门查找并总结了一些资料,如下: 一般来说,定点DSP处理器具有速度快,功耗低,价格便宜的特点;而浮点DSP处理器则计算精确,动态范围大,速度快,易于编程,功耗大,价格高。 1、宏观...
  • 机器学习(额外篇):聚类算法分类算法的区别

    千次阅读 多人点赞 2019-06-22 10:54:30
    机器学习中有两类的大问题,一个是分类,一个是聚类。 在生活中,我们常常没有过多...分类(classification):分类任务就是通过学习得到一个目标函数f,把每个属性集x映射到一个预先定义的类标号y中。 分类是根据一...
  • 系统任务4.1 显示任务4.1.1 displaywrite任务4.1.2 strobe监控4.1.3 连续监控4.2 文件I/O任务和函数4.2.1 打开文件关闭文件4.2.2 文件输出4.2.3 数据转换为字符串4.2.4 读取文件内容4.2.5 文件I/O错误状态4.2.6 ...
  • 位置式PID与增量式PID区别浅析

    万次阅读 多人点赞 2019-04-26 08:55:12
    PID 控制器以各种形式使用超过了 1 世纪,广泛应用在机械设备、气动设备 电子设备.在工业应用中PID及其衍生算法是应用最广泛的算法之一,是当之无愧的万能算法 PID 实指“比例 proportional”、“积分 integral...
  • 贝叶斯估计极大似然估计到底有何区别

    万次阅读 多人点赞 2017-03-12 21:28:00
    在开始接触最大似然估计贝叶斯估计时,大家都会有个疑问:最大似然估计贝叶斯估计二者很相似,到底有何区别?本文便来说说二者的不同之处以及推导二者使用时的数学模型!预热知识必知如何求类条件概率密度: ...
  • 游戏任务系统设计思路

    千次阅读 2016-11-09 15:59:00
    WOW中的任务虽然不是必须完成的,但是顺着部落主线的剧情延伸至南海镇的任务却是很明显的主线剧情,从亡灵新手村学会了如何死尸打交道,并且如何与已故的亡灵战士沟通成了亡灵族的本能,那么下面任务就是完成赫尔...
  • 在多任务优化的情景下,如果任务之间存在潜在关系,那么高质量的解在这些任务之间的转移可以显著提高算法的性能。然而有的时候缺乏关于任务间协同作用的任何先验知识(黑盒优化),主要是负迁移导致算法的性能受损,...
  • lightgbm二分类,多分类以及回归任务(含python源码) 1. 简介 内心一直想把自己前一段时间写的代码整理一下,梳理一下知识点,方便以后查看,同时也方便大家交流。希望我的分享能帮助到一些小白用户快速前进,...
  • SpringBoot异步任务(多线程)

    千次阅读 2019-08-30 15:06:25
    配置类 @Configuration @EnableAsync public ...// 最大线程数量 ...结果 从这个线程池的名字中,可以看到,分别使用的是两个线程池。 注意事项 如下方式会使@Async失效 异步方法使用static修饰
  • 浅谈uCOS-II的任务(上)

    千次阅读 2018-11-19 20:41:16
    文章目录前言什么是任务任务的管理任务的状态系统任务任务的优先权以及优先级别任务堆栈任务堆栈的创建任务堆栈的初始化任务控制块及其链表任务控制块链表任务控制块的初始化参考资料 前言 大概花了四天时间将将uCOS...
  • uC/OS-II任务就绪表及任务调度

    千次阅读 2016-10-11 10:18:26
    最近开始花时间去学习uc/OS-II,一方面是工作上用的是这个系统,另一方面就是想去了解实时操作系统与普通操作系统的区别,学到任务就绪表及任务调度这里,对实时的概念有所了解,所以写此文帮助自己梳理,也希望与...
  • 任务进化优化算法(一)——多因子进化算法(MFEA)

    万次阅读 多人点赞 2019-10-25 19:09:21
    最近看了很多关于多任务优化的文章,觉得这是一个蛮有意思的方向,想把里面最经典的两个方法介绍给大家,今天先介绍第一个MFEA,这个方向有一个平台,这里面有原作者的代码及最新的出版物,感兴...
  • 在activiti任务中,主要分为两大类查询任务(个人任务和任务): 确切指定了办理者的任务,这个任务将成为指定者的私有任务,即个人任务。 无法指定具体的某一个人来办理的任务,可以把任务分配给几个人...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 223,090
精华内容 89,236
关键字:

做任务和做结果的最大区别