一、线程间的协作

1.1 线程的状态

Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态)。
image.png

  1. 新建状态,当线程创建完成时为新建状态,即new Thread(…),还没有调用start方法时,线程处于新建状态。
  2. 就绪状态,当调用线程的的start方法后,线程进入就绪状态,等待CPU资源。处于就绪状态的线程由Java运行时系统的线程调度程序(thread scheduler)来调度。
  3. 运行状态,就绪状态的线程获取到CPU执行权以后进入运行状态,开始执行run方法。
  4. 阻塞状态,线程没有执行完,由于某种原因(如,I/O操作等)让出CPU执行权,自身进入阻塞状态。
  5. 死亡状态,线程执行完成或者执行过程中出现异常,线程就会进入死亡状态。

1.2 wait和notify

wait和notify方法都是Object方法,组成了一组线程间协作的方法。

wait
  1. wait()方法的作用是将当前运行的线程挂起(即让其进入阻塞状态),直到notify或notifyAll方法来唤醒线程.
  2. wait(long timeout),该方法与wait()方法类似,唯一的区别就是在指定时间内,如果没有notify或notifAll方法的唤醒,也会自动唤醒。
  3. wait(long timeout,long nanos),本意在于更精确的控制调度时间,不过从jdk8来看,对纳秒的处理只做了四舍五入,所以还是按照毫秒来处理的
  4. 调用wait方法后,线程是会释放对monitor对象的所有权的。
public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
            timeout++;
        }

        wait(timeout);
    }

示例

package com.paddx.test.concurrent;

public class WaitTest {
	//wait必须在synchronize关键字下使用,否则报IllegalMonitorStateException
    public synchronized void testWait(){//增加Synchronized关键字
        System.out.println("Start-----");
        try {
            wait(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("End-------");
    }

    public static void main(String[] args) {
        final WaitTest test = new WaitTest();
        new Thread(new Runnable() {
            @Override
            public void run() {
                test.testWait();
            }
        }).start();
    }
	
	/*
	Start-----
	End-------
	*/
}
notify/notifyAll
  1. 既然wait方式是通过对象的monitor对象来实现的,所以只要在同一对象上去调用notify/notifyAll方法,就可以唤醒对应对象monitor上等待的线程了。
  2. notify和notifyAll的区别在于前者只能唤醒monitor上的一个线程,对其他线程没有影响,而notifyAll则唤醒所有的线程
  3. 通过wait方法阻塞的线程,唤醒后还需要竞争到锁(monitor)才会被真正执行。

示例

package com.paddx.test.concurrent;

public class NotifyTest {
    public synchronized void testWait(){
        System.out.println(Thread.currentThread().getName() +" Start-----");
        try {
            wait(0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() +" End-------");
    }

    public static void main(String[] args) throws InterruptedException {
        final NotifyTest test = new NotifyTest();
        for(int i=0;i<5;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.testWait();
                }
            }).start();
        }

        synchronized (test) {
            test.notify();
        }
        Thread.sleep(3000);
        System.out.println("-----------分割线-------------");

        synchronized (test) {
            test.notifyAll();
        }
    }
	
	/*
	Thread-0 Start-----
	Thread-1 Start-----
	Thread-2 Start-----
	Thread-3 Start-----
	Thread-4 Start-----
	Thread-0 End-------
	-----------分割线-------------
	Thread-4 End-------
	Thread-3 End-------
	Thread-2 End-------
	Thread-1 End-------
	*/
}

调用notify方法时只有线程Thread-0被唤醒,但是调用notifyAll时,所有的线程都被唤醒了。

1.3 sleep/yield/join

这几个方法都位于Thread类中,是另外一组线程间协作的方法。

sleep
  1. sleep方法的作用是让当前线程暂停指定的时间(毫秒),比较容易理解。
  2. 需要注意的是其与wait方法的区别,wait方法依赖于同步,而sleep方法可以直接调用。
  3. 而更深层次的区别在于sleep方法只是暂时让出CPU的执行权,并不释放锁。而wait方法则需要释放锁。

示例

package com.paddx.test.concurrent;

public class SleepTest {
    public synchronized void sleepMethod(){
        System.out.println("Sleep start-----");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Sleep end-----");
    }

    public synchronized void waitMethod(){
        System.out.println("Wait start-----");
        synchronized (this){
            try {
                wait(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Wait end-----");
    }

    public static void main(String[] args) {
        final SleepTest test1 = new SleepTest();

        for(int i = 0;i<3;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test1.sleepMethod();
                }
            }).start();
        }


        try {
            Thread.sleep(10000);//暂停十秒,等上面程序执行完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-----分割线-----");

        final SleepTest test2 = new SleepTest();

        for(int i = 0;i<3;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test2.waitMethod();
                }
            }).start();
        }

    }
	
	/*
	Sleep start-----
	Sleep end-----
	Sleep start-----
	Sleep end-----
	Sleep start-----
	Sleep end-----
	-----分割线-----
	Wait start-----
	Wait start-----
	Wait start-----
	Wait end-----
	Wait end-----
	Wait end-----
	*/
}
  1. 通过sleep方法实现的暂停,程序是顺序进入同步块的,只有当上一个线程执行完成的时候,下一个线程才能进入同步方法,sleep暂停期间一直持有monitor对象锁,其他线程是不能进入的。
  2. 当调用wait方法后,当前线程会释放持有的monitor对象锁,因此,其他线程还可以进入到同步方法,线程被唤醒后,需要竞争锁,获取到锁之后再继续执行。
yield
  1. yield方法的作用是暂停当前线程,以便其他线程有机会执行
  2. 不能指定暂停的时间,并且也不能保证当前线程马上停止。
  3. yield方法只是将Running状态转变为Runnable状态。
package com.paddx.test.concurrent;

public class YieldTest implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName() + ": " + i);
            Thread.yield();
        }
    }

    public static void main(String[] args) {
        YieldTest runn = new YieldTest();
        Thread t1 = new Thread(runn,"FirstThread");
        Thread t2 = new Thread(runn,"SecondThread");
		
        t1.start();
        t2.start();
    }
	
	/*
	FirstThread: 0
	SecondThread: 0
	FirstThread: 1
	SecondThread: 1
	FirstThread: 2
	SecondThread: 2
	FirstThread: 3
	SecondThread: 3
	FirstThread: 4
	SecondThread: 4
	*/
}

这个例子就是通过yield方法来实现两个线程的交替执行。这种交替并不一定能得到保证,源码中也对这个问题进行说明:调度器可能会忽略该方法。

join

join方法的作用是父线程等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程,JDK中提供三个版本的join方法,其实现与wait方法类似。
源码

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
				//这里的join只调用了wait方法,却没有对应的notify方法,原因是Thread的start方法中做了相应的处理,所以当join的线程执行完成以后,会自动唤醒主线程继续往下执行。
                wait(0);
            }
        } else {
			//这有一个争锁的过程的,如果没有争到锁就必须等待,而这次的等待时间是不算入计数的时间的,所以这就有可能造成主线程等待的时间比规定的时间长。
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
				//join方法就是通过wait方法来将线程的阻塞,如果join的线程还在执行,则将当前线程阻塞起来,直到join的线程执行完成,当前线程才能执行。
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

示例

package com.paddx.test.concurrent;

public class JoinTest implements Runnable{
    @Override
    public void run() {

        try {
            System.out.println(Thread.currentThread().getName() + " start-----");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " end------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        for (int i=0;i<5;i++) {
            Thread test = new Thread(new JoinTest());
            test.start();
            try {
                test.join(); //调用join方法
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Finished~~~");
    }
	
	/*
	Thread-0 start-----
	Thread-0 end------
	Thread-1 start-----
	Thread-1 end------
	Thread-2 start-----		
	Thread-2 end------
	Thread-3 start-----
	Thread-3 end------
	Thread-4 start-----
	Thread-4 end------
	Finished~~~
	*/
}

二、volatile

2.1 概述

  1. 并发中的可见性、有序性及原子性问题,通常情况下可以通过Synchronized关键字来解决。
  2. 不过Synchronized是一个比较重量级的操作,对系统的性能有比较大的影响,通常都避免使用Synchronized来解决问题。
  3. volatile关键字就是Java中提供的另一种解决可见性和有序性问题的方案。
  4. 对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证i这种操作的原子性,因为本质上i是读、写两次操作。

2.2 作用

防止重排序

在并发环境下的单例实现方式,我们通常可以采用双重检查加锁(DCL)的方式来实现。

package com.paddx.test.concurrent;

public class Singleton {
	// volatile关键字														
    public static volatile Singleton singleton;

    /**
     * 构造函数私有,禁止外部实例化
     */
    private Singleton() {};

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (singleton) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

实例化一个对象其实可以分为三个步骤:

  1. 分配内存空间。
  2. 初始化对象。
  3. 将内存空间的地址赋值给对应的引用。

但是由于操作系统可以对指令进行重排序,2和3顺序可能会颠倒。多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。

实现可见性

可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。

示例
直观上说,这段代码的结果只可能有两种:b=3;a=3 或 b=2;a=1

package com.paddx.test.concurrent;

public class VolatileTest {
    int a = 1;
    int b = 2;

    public void change(){
        a = 3;
        b = a;
    }

    public void print(){
        System.out.println("b="+b+";a="+a);
    }

    public static void main(String[] args) {
        while (true){
            final VolatileTest test = new VolatileTest();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.change();
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.print();
                }
            }).start();

        }
    }
	
	/*
	b=2;a=1
	b=2;a=1
	b=3;a=3
	b=3;a=3
	b=3;a=1	 //第一个线程将值a=3修改后,但是对第二个线程是不可见											
	b=3;a=3
	b=2;a=1
	b=3;a=3
	b=3;a=3
	*/
}

如果将a和b都改成volatile类型的变量再执行,则再也不会出现b=3;a=1的结果了。

保证原子性
  1. long和double两种数据类型的操作可分为高32位和低32位两部分,因此普通的long或double类型读/写可能不是原子的。
  2. 因此,将共享的long和double变量设置为volatile类型,这样能保证任何情况下对long和double的单次读/写操作都具有原子性。

volatile是无法保证原子性的

package com.paddx.test.concurrent;

public class VolatileTest01 {
    volatile int i;

    public void addI(){
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
        final  VolatileTest01 test01 = new VolatileTest01();
        for (int n = 0; n < 1000; n++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test01.addI();
                }
            }).start();
        }

        Thread.sleep(10000);//等待10秒,保证上面程序执行完成

        System.out.println(test01.i);
    }
}

i++其实是一个复合操作,包括三步骤:

  1. 读取i的值。
  2. 对i加1。
  3. 将i的值写回内存。

可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。

2.3 原理

可见性实现

线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作。这也是导致线程间数据不可见的本质原因。对volatile变量的写操作与普通变量的主要区别有两点:

  1. 修改volatile变量时会强制将修改后的值刷新的主内存中。
  2. 修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。
有序性实现

如果a happen-before b,则a所做的任何操作对b是可见的。(happen-before这个词容易被误解为是时间的前后)。

  1. 同一个线程中的,前面的操作 happen-before 后续的操作。(即单线程内按代码顺序执行。但是,在不影响在单线程环境执行结果的前提下,编译器和处理器可以进行重排序,这是合法的。换句话说,这一是规则无法保证编译重排和指令重排)。
  2. 监视器上的解锁操作 happen-before 其后续的加锁操作。(Synchronized 规则)
  3. 对volatile变量的写操作 happen-before 后续的读操作。(volatile 规则)
  4. 线程的start() 方法 happen-before 该线程所有的后续操作。(线程启动规则)
  5. 线程所有的操作 happen-before 其他线程在该线程上调用 join 返回成功后的操作。
  6. 如果 a happen-before b,b happen-before c,则a happen-before c(传递性)。

为了实现volatile内存语义,volatile的重排序规则如下
image.png

  1. 当第二个操作是volatile 写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile 写之前的操作不会被编译器重排序到volatile 写之后。如果进行重排序,那么volatile 写会使其他CPU的缓存行无效,就不能保证volatile 写之前的共享变量数据的一致,如此就违背了内存语义。
  2. 当第一个操作是volatile 读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile 读之后的操作不会被编译器重排序到volatile 读之前。如果进行重排序,当前缓存行的数据就会被置为无效,那么缓存行中的普通共享变量也会再从主存中重新读取,如此就违背了内存语义。
  3. 当第一个操作是volatile 写,第二个操作是volatile 读时,不能重排序。

2.4 总结

  1. volatile是并发编程中的一种优化,在某些场景下可以代替Synchronized。
  2. volatile的不能完全取代Synchronized的位置,只有在一些特殊的场景下,才能适用volatile。
    1. 对变量的写操作不依赖于当前值。(否则无法保证原子性)
    2. 该变量没有包含在具有其他变量的不变式中。

三、线程池

public ThreadPoolExecutor(int corePoolSize,  //线程池中核心线程的数量
                              int maximumPoolSize,  //线程池中最大线程数量
                              long keepAliveTime,  //非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。
                              TimeUnit unit,  //keepAliveTime的单位,有纳秒、微秒、毫秒、秒、分、时、天等
                              BlockingQueue<Runnable> workQueue,  //线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
                              ThreadFactory threadFactory,  //创建线程新功能,一般默认
                              RejectedExecutionHandler handler) //拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。

3.1 产生背景

  1. 为每个请求创建一个新线程并销毁的开销很大,甚至要比花在处理实际的用户请求的时间和系统资源更多。容易引起资源不足,造成浪费。为解决单个任务处理时间很短而请求的数目巨大的问题,引出线程池:
  2. 通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。因为在请求到达时线程已经存在,消除了线程创建所带来的延迟,使应用程序响应更快;
  3. 通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。

3.2 使用风险

用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。

死锁

池内的线程都处于阻塞状态,需要等待队列中另一任务的执行结果,但这一任务却因为没有未被占用的线程而不能运行。

资源不足
  1. 线程消耗包括内存和其它系统资源在内的大量资源。除了 Thread 对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。
  2. JVM 可能会为每个 Java 线程创建一个本机线程,这些本机线程将消耗额外的系统资源。
  3. 虽然线程之间切换的调度开销很小,但如果有很多线程,环境切换也可能严重地影响程序的性能。
  4. 除了线程自身所使用的资源以外,服务请求时所做的工作可能需要其它资源,例如 JDBC 连接、套接字或文件。这些也都是有限资源,有太多的并发请求也可能引起失效,例如不能分配 JDBC 连接。
线程泄漏
  1. 发生线程泄漏的一种情形出现在任务抛出一个 RuntimeException 或一个 Error 时。如果池类没有捕捉到它们,那么线程只会退出而线程池的大小将会永久减少一个。当这种情况发生的次数足够多时,线程池最终就为空,而且系统将停止,因为没有可用的线程来处理任务。
  2. 有些任务可能会永远等待某些资源或来自用户的输入,而这些资源又不能保证变得可用,用户可能也已经回家了,诸如此类的任务会永久停止,而这些停止的任务也会引起和线程泄漏同样的问题。

3.3 使用准则

  1. 不要对那些同步等待其它任务结果的任务排队。可能会导致上面所描述的那种形式的死锁。
  2. 在为时间可能很长的操作使用合用的线程时要小心。请指定最长的等待时间,以及随后是失效还是将任务重新排队以便稍后执行。

3.4 线程池的大小

  1. 最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
    比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。
  2. 这个公式进一步转化为:
    最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。

3.5 常用线程池

newCachedThreadPool
  1. 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. 工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE),这样可灵活的往线程池中添加线程。
  3. 如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
  4. 在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
 public static void main(String[] args) {
  ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
  for (int i = 0; i < 10; i++) {
   final int index = i;
   try {
    Thread.sleep(index * 1000);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   cachedThreadPool.execute(new Runnable() {
    public void run() {
     System.out.println(index);
    }
   });
  }
 }
}
newFixedThreadPool
  1. 创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
  2. FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。
  3. 但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,程序不会退出,还会占用一定的系统资源。
package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        //定长线程池的大小最好根据系统资源进行设置
        System.out.println(Runtime.getRuntime().availableProcessors());
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}		
newSingleThreadExecutor
  1. 创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  2. 如果这个线程异常结束,会有另一个取代它,保证顺序执行。
  3. 单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}
newScheduleThreadPool

创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
延迟3秒执行,延迟执行示例代码如下:

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.schedule(new Runnable() {
            public void run() {
                System.out.println("delay 3 seconds");
            }
        }, 3, TimeUnit.SECONDS);
    }
}

表示延迟1秒后每3秒执行一次,定期执行示例代码如下:

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            public void run() {
                System.out.println("delay 1 seconds, and excute every 3 seconds");
            }
        }, 1, 3, TimeUnit.SECONDS);
    }
}

3.6 线程池的单例

懒汉式
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author Tao
 * @Date 2020/11/3
 * @Time 15:55
 */
public class ThreadPoolService {
    private static final int DEFAULT_CORE_SIZE = 100;
    private static final int MAX_QUEUE_SIZE = 500;
    private volatile static ThreadPoolExecutor executor;

    private ThreadPoolService() {
    }

    ;

    // 获取单例的线程池对象
    public static ThreadPoolExecutor getInstance() {
        if (executor == null) {
            synchronized (ThreadPoolService.class) {
                if (executor == null) {
                    executor = new ThreadPoolExecutor(DEFAULT_CORE_SIZE,// 核心线程数
                            MAX_QUEUE_SIZE, // 最大线程数
                            Integer.MAX_VALUE, // 闲置线程存活时间
                            TimeUnit.MILLISECONDS,// 时间单位
                            new LinkedBlockingDeque<Runnable>(Integer.MAX_VALUE),// 线程队列
                            Executors.defaultThreadFactory()// 线程工厂
                    );
                }
            }
        }
        return executor;
    }

    public void execute(Runnable runnable) {
        if (runnable == null) {
            return;
        }
        executor.execute(runnable);
    }

    // 从线程队列中移除对象
    public void cancel(Runnable runnable) {
        if (executor != null) {
            executor.getQueue().remove(runnable);
        }
    }

}
懒汉式
public class AsyncTaskExecutor {

    /** 线程池保持ALIVE状态线程数 */
    public static final int                 CORE_POOL_SIZE      = 10;

    /** 线程池最大线程数 */
    public static final int                 MAX_POOL_SIZE       = 40;

    /** 空闲线程回收时间 */
    public static final int                 KEEP_ALIVE_TIME     = 1000;

    /** 线程池等待队列 */
    public static final int                 BLOCKING_QUEUE_SIZE = 1000;

    /** 业务请求异步处理线程池 */
        private static final ThreadPoolExecutor processExecutor = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.MICROSECONDS,
            new LinkedBlockingQueue<Runnable>(BLOCKING_QUEUE_SIZE), Executors.defaultThreadFactory());

    private AsyncTaskExecutor() {};

    /**
     * 异步任务处理
     *
     * @param task 任务
     */
    public void execute(Runnable task) {
        processExecutor.submit(task);
    }

}

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议