跳转至

Java 多线程

one today is worth two tomorrows.

人生最重要的决定,是相信自己的直觉,然后不遗余力地证明你素偶偶的努力有意义

1. 介绍

程序 Program,是一个指令的集合

进程 Process,(正在执行中的程序)是一个静态的概念

  • 进程是程序的一次静态态执行过程, 占用特定的地址空间.
  • 每个进程都是独立的,由3部分组成cpu,data,code
  • 缺点:内存的浪费,cpu的负担

线程 是进程中一个“单一的连续控制流程” (a single sThread,equential flow of control)/执行路径

  • 线程又被称为轻量级进程(lightweight process)。Threads run at the same time, independently of one another
  • 一个进程可拥有多个并行的(concurrent)线程
  • 一个进程中的线程共享相同的内存单元/内存地址空间->可以访问相同的
  • 变量和对象,而且它们从同一堆中分配对象->通信、数据交换、同步操作
  • 由于线程间的通信是在同一地址空间上迚行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快

两种实现方式哪种用的比较多,推荐使用第二种方式,

  • java是单继承,将继承关系留给最需要的类
  • 使用runnable接口之后不需要给共享变量添加static关键字,每次创建一个对象,作为共享对象即可

2. 线程

第一种实现多线程,继承Thread类

  • 需要继承Thread类
  • 必须要重写run方法,指的是核心执行的逻辑
  • 线程在启动的时候,不要直接调用run方法,而是要通过start()来进行调用
  • 每次运行相同的代码,出来结果可能不一样,原因在于多线程谁先抢占资源无法进行人为控制

第二种实现多线程,实现Runnable接口

  • 实现Runnable接口
  • 重写run方法
  • 创建Thread对象,将刚刚创建好的runnable的子类实现作为thread的构造参数
  • 通过thread.start()进行启动

第一种实现多线程

package ThreadD;

/**
 * @author summer
 * @create 2020-02-08 16:49
 */
public class ThreadDemo extends Thread{
    //重写父为的run方法
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-----"+i);
        }
    }

    public static void main(String[] args) {
        // 创建对象,就创建好一个线程
        ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.start();  // 开启多线程
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"======"+i);
        }
    }
}
/**
 * Thread类中的run方法是存储线程要运行的代码
 * 主线程要运行的代码存放在main方法中
 * 
 * d.run();//仅仅是对象调方法,而是创建了线程但并没有运行
 * d.start();//开启线程并执行该线程的run方法
 * */
运行结果
main======0
main======1
main======2
main======3
Thread-0-----0
main======4
Thread-0-----1
Thread-0-----2
Thread-0-----3
Thread-0-----4
Thread-0-----5
Thread-0-----6
Thread-0-----7
Thread-0-----8
Thread-0-----9

第二种实现多线程

public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--------------"+i);
        }
    }

    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        thread.start();
        for(int i =0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"==========="+i);
        }
    }
}

3. 线程状态

线程状态图
线程状态图

3.1 新生状态:

当创建好当前线程对象之后,没有启动之前(调用start方法之前)

ThreadDemo thread = new ThreadDemo()
RunnableDemo run = new RunnableDemo()

3.2 就绪状态

准备开始执行,并没有执行,表示调用start方法之后,当对应的线程创建完成,且调用start方法之后,所有的线程会添加到一个就绪队列中,所有的线程同时去抢占cpu的资源

3.3 运行状态

当当前进程获取到cpu资源之后,就绪队列中的所有线程会去抢占cpu的资源,谁先抢占到谁先执行,在执行的过程中就叫做运行状态抢占到cpu资源,执行代码逻辑开始

3.4 死亡状态

当运行中的线程正常执行完所有的代码逻辑或者因为异常情况导致程序结束叫做死亡状态

进入的方式

  • 正常运行完成且结束
  • 人为中断执行,比如使用stop方法
  • 程序抛出未捕获的异常

3.5 阻塞状态

在程序运行过程中,发生某些异常情况,导致当前线程无法再顺利执行下去,此时会进入阻塞状态,进入阻塞状态的原因消除之后,有的阻塞队列会再次进入到就绪状态中,随机抢占cpu的资源,等待执行

进入的方式

  • sleep方法
  • 等待io资源
  • join方法(代码中执行的逻辑)

4. 线程操作方法

方法名称 描述
public static Thread currentThread() 返回目前正在执行的线程
public final String getName() 返回线程的名称
public final int getPriority() 返回线程的优先级
public final void setPriority(String name) 设定线程名称
public final boolean isAlive() 判断线程是否在活动,如果是,返回true,否则返回false
public final void join() 调用该方法的线程强制执行,其它线程处于**阻塞**状态,该线程执行完毕后,其它线程再执行
public static void sleep(long millis) 使用当前正在执行的线程休眠millis秒,线程处于**阻塞**状态
public static void yield() 当前正在执行的线程暂停一次,允许其他线程执 行,不阻塞,线程进入**就绪状态**,如果没有其他等待执行的线程,这个时候**当前线程就会马上恢复执**
public final void stop() 强迫线程停止执行。已过时。不推荐使用。
MyRun.class
package ThreadD;

/**
 * @author summer
 * @create 2020-02-08 19:24
 */
public class MyRun implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"------------"+i);
        }
    }
}
JoinTest.class
package ThreadD;

/**
 * @author summer
 * @create 2020-02-08 19:26
 */
public class JoinTest {
    public static void main(String[] args) {
        MyRun myRun = new MyRun();
        Thread thread = new Thread(myRun);
        thread.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("当前线程名字: "+ Thread.currentThread().getName()+"---"+i);
            System.out.println("当前线程优先级: "+ Thread.currentThread().getPriority()+"---"+i);
            System.out.println("当前线程是否存活: "+ Thread.currentThread().isAlive()+"---"+i);
            System.out.println("当前线程是否是守护进程: "+ Thread.currentThread().isDaemon()+"---"+i);
        }
    }
}
输出
Thread-0------------0
Thread-0------------1
Thread-0------------2
Thread-0------------3
Thread-0------------4
Thread-0------------5
Thread-0------------6
Thread-0------------7
Thread-0------------8
Thread-0------------9
当前线程名字: main---0
当前线程优先级: 5---0
当前线程是否存活: true---0
当前线程是否是守护进程: false---0
当前线程名字: main---1
当前线程优先级: 5---1
当前线程是否存活: true---1
当前线程是否是守护进程: false---1
当前线程名字: main---2
当前线程优先级: 5---2
当前线程是否存活: true---2
当前线程是否是守护进程: false---2
当前线程名字: main---3
当前线程优先级: 5---3
当前线程是否存活: true---3
当前线程是否是守护进程: false---3
当前线程名字: main---4
当前线程优先级: 5---4
当前线程是否存活: true---4
当前线程是否是守护进程: false---4

调用该方法的线程强制执行,其它线程处于阻塞状态,该线程执行完毕后,其它线程再执行,t.join()方法只会使 主线程进入等待池并等待t线程执行完毕后才会被唤醒。 并不影响同一时刻处在运行状态的其他线程。

join
package ThreadD;

/**
 * @author summer
 * @create 2020-02-08 19:26
 */
public class JoinTest {
    public static void main(String[] args) {
        MyRun myRun = new MyRun();
        Thread thread = new Thread(myRun);
        thread.start();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
//            System.out.println("当前线程优先级: "+ Thread.currentThread().getPriority()+"---"+i);
//            System.out.println("当前线程是否存活: "+ Thread.currentThread().isAlive()+"---"+i);
//            System.out.println("当前线程是否是守护进程: "+ Thread.currentThread().isDaemon()+"---"+i);
            if(i==3){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


输出结果
main---0
main---1
main---2
main---3
Thread-0------------0
Thread-0------------1
Thread-0------------2
Thread-0------------3
Thread-0------------4
Thread-0------------5
Thread-0------------6
Thread-0------------7
Thread-0------------8
Thread-0------------9
main---4
sleep
package ThreadD;

/**
 * @author summer
 * @create 2020-02-08 20:17
 */
public class SleepTest {
    public static void main(String[] args) {
        MyRun run = new MyRun();
        Thread thread = new Thread(run,"线程A");
        thread.start();

        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"======"+i);
            if (i == 2){
                try {
                    thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

输出
main======0
main======1
main======2
线程A------------0
线程A------------1
线程A------------2
线程A------------3
线程A------------4
线程A------------5
线程A------------6
线程A------------7
线程A------------8
线程A------------9
main======3
main======4
yield
package ThreadD;

/**
 * @author summer
 * @create 2020-02-08 20:33
 */
public class YieldTest {
    public static void main(String[] args) {
        MyRun run = new MyRun();
        Thread thread = new Thread(run,"线程A");
        thread.start();

        for (int i = 0; i < 5; i++) {

            if (i == 2){
                Thread.yield();
                System.out.println(Thread.currentThread().getName()+"======"+i+"礼让一次");
            }else{
                System.out.println(Thread.currentThread().getName()+"======"+i);
            }
        }
    }
}

线程A------------0
线程A------------1
线程A------------2
线程A------------3
线程A------------4
线程A------------5
线程A------------6
线程A------------7
线程A------------8
线程A------------9
main======0
main======1
main======2礼让一次
main======3
main======4

注意

在多线程的时候,可以实现唤醒和等待的过程,但是唤醒和等待操作的对应不是thread类
   而是我们设置的共享对象或者共享变量

多线程并发访问的时候回出现数据安全问题:
解决方式:
    1、同步代码块
        synchronized(共享资源、共享对象,需要是object的子类){具体执行的代码块}
    2、同步方法
        将核心的代码逻辑定义成一个方法,使用synchronized关键字进行修饰,此时不需要指定共享对象
加锁+购票 同步代码块
package ticket;
/**
 * 使用接口的方式,每次只创建了一个共享对象,所有的线程能够实现资源共享
 *      1、数据不一致的问题
 *          解决方法:线程同步【加锁】 synchronized
 *
 */
public class TicketRunnable2 implements Runnable {

    private int ticket = 5;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this){
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
                }
            }
        }
    }

    public static void main(String[] args) {
        TicketRunnable2 ticket = new TicketRunnable2();
        Thread t1 = new Thread(ticket,"A");
        Thread t2 = new Thread(ticket,"B");
        Thread t3 = new Thread(ticket,"C");
        Thread t4 = new Thread(ticket,"D");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

C正在出售第5张票
A正在出售第4张票
B正在出售第3张票
D正在出售第2张票
C正在出售第1张票
加锁+购票 同步方法
package ticket;

/**
 * 使用接口的方式,每次只创建了一个共享对象,所有的线程能够实现资源共享
 * 1、数据不一致的问题
 * 解决方法:线程同步
 */
public class TicketRunnable3 implements Runnable {

    private int ticket = 5;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.sale();
        }
    }

    /*
     * 使用同步方法解决多线程数据安全的问题
     * */
    public synchronized void sale() {

        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
        }
    }

    public static void main(String[] args) {
        TicketRunnable3 ticket = new TicketRunnable3();
        Thread t1 = new Thread(ticket, "A");
        Thread t2 = new Thread(ticket, "B");
        Thread t3 = new Thread(ticket, "C");
        Thread t4 = new Thread(ticket, "D");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

同步监视器

  • synchronized(obj){}中的obj称为同步监视器
  • 同步代码块中同步监视器可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的监视器是this,也就是该对象本身

同步监视器的执行过程

  • 第一个线程访问,锁定同步监视器,执行其中代码
  • 第二个线程访问,发现同步监视器被锁定,无法访问
  • 第一个线程访问完毕,解锁同步监视器
  • 第二个线程访问,发现同步监视器未锁,锁定并访问

Java提供了3个方法解决线程之间的通信问题

方法名 作用
final void wait() 表示线程一直等待,直到其它线程通知
final void wait(long timeout) 线程等待指定毫秒参数的时间
final void wait(long timeout,int nanos) 线程等待指定毫秒、微妙的时间
final void notify() 唤醒一个处于等待状态的线程
final void notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行

注意事项:以上方法都只能在同步方法戒者同步代码块中使用,否则会抛出异常

5. 线程的生产者和消费者

Goods.class
 package dxc;

/**
 * @author summer
 * @create 2020-02-09 9:22
 */
public class Goods {
    private String name;
    private String type;

    public Goods(String name, String type) {
        this.name = name;
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "name='" + name + '\'' +
                ", type='" + type + '\'' +
                '}';
    }
}
Consumer.class
 package dxc;

import java.util.concurrent.BlockingQueue;

/**
 * @author summer
 * @create 2020-02-09 9:41
 */
public class Consumer implements Runnable{
    private BlockingQueue<Goods> blockingQueue;

    public Consumer(BlockingQueue<Goods> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Goods goods = this.blockingQueue.take();  // 销售者消费,从队列中取出一个数
                System.out.println("客人: 消费的商品--> "+goods.getType()+"---"+goods.getName());
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
ProducerQueue.class
package dxc;

import java.util.concurrent.BlockingQueue;

/**
 * @author summer
 * @create 2020-02-09 9:26
 */
public class Producer implements Runnable{
    private BlockingQueue<Goods> blockingQueue;

    public Producer(BlockingQueue<Goods> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            Goods goods = null;
            if(i %2 == 0){
                goods = new Goods("包子","牛肉粉丝的");
            }else {
                goods = new Goods("烧麦","鸡肉的");
            }
            System.out.println("早餐店做了 第"+i+"个商品是---> "+goods.getType()+goods.getName());
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                blockingQueue.put(goods); // 生产者生产,队列中塞进一个数
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
ConsummerProducerTest.class
package dxc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * @author summer
 * @create 2020-02-09 9:45
 */
public class ConsummerProducerTest {
    public static void main(String[] args) {
        BlockingQueue<Goods> blockingQueue = new ArrayBlockingQueue<Goods>(10);

        Producer producer = new Producer(blockingQueue);
        Consumer consumer1 = new Consumer(blockingQueue);
//        Consumer consumer2 = new Consumer(blockingQueue);

//        new Thread(consumer).start();
//        new Thread(producer).start();
        Thread thread_proucer = new Thread(producer,"生产者");
        Thread thread_consumer1 = new Thread(consumer1,"消费者1");
//        Thread thread_consumer2 = new Thread(consumer2,"消费者2");
        thread_proucer.start();
        thread_consumer1.start();
//        thread_consumer2.start();
    }
}
运行结果
早餐店做了 第0个商品是---> 牛肉粉丝的包子
早餐店做了 第1个商品是---> 鸡肉的烧麦
客人: 消费的商品--> 牛肉粉丝的---包子
客人: 消费的商品--> 鸡肉的---烧麦
早餐店做了 第2个商品是---> 牛肉粉丝的包子
早餐店做了 第3个商品是---> 鸡肉的烧麦
客人: 消费的商品--> 牛肉粉丝的---包子
客人: 消费的商品--> 鸡肉的---烧麦
早餐店做了 第4个商品是---> 牛肉粉丝的包子
客人: 消费的商品--> 牛肉粉丝的---包子
早餐店做了 第5个商品是---> 鸡肉的烧麦
早餐店做了 第6个商品是---> 牛肉粉丝的包子
客人: 消费的商品--> 鸡肉的---烧麦
早餐店做了 第7个商品是---> 鸡肉的烧麦
客人: 消费的商品--> 牛肉粉丝的---包子
早餐店做了 第8个商品是---> 牛肉粉丝的包子
客人: 消费的商品--> 鸡肉的---烧麦
早餐店做了 第9个商品是---> 鸡肉的烧麦
客人: 消费的商品--> 牛肉粉丝的---包子
客人: 消费的商品--> 鸡肉的---烧麦

6. 线程池

6.1 起因

为什么需要线程池。

在实际使用中,线程是很占用系统资源的,如果对线程管理不善很容易导致系统问题。因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主要有如下好处:

  • 使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建和销毁时造成的消耗
  • 由于没有线程创建和销毁时的消耗,可以提高系统响应速度
  • 通过线程可以对线程进行合理的管理,根据系统的承受能力调整可运行线程数量的大小等

6.2 线程池的原理

线程池

线程池执行所提交的任务过程:

  • 第1步: 先判断线程池中核心线程池所有的线程是否都在执行任务。如果不是,则新创建一个线程执行刚提交的任务,否则,核心线程池中所有的线程都在执行任务,则进入第2步;
  • 第2步: 判断当前阻塞队列是否已满,如果未满,则将提交的任务放置在阻塞队列中;否则,则进入第3步;
  • 第3步: 判断线程池中所有的线程是否都在执行任务,如果没有,则创建一个新的线程来执行任务,否则,则交给饱和策略进行处理

6.3 线程池的分类

线程池分类
线程池分类

  • newCacheThreadPool 线程池
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
  • corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制;
  • keepAliveTime = 60s,线程空闲60s后自动结束。
  • workQueue 为 SynchronousQueue 同步队列,这个队列类似于一个接力棒,入队出队必须同时传递,因为CachedThreadPool线程创建无限制,不会有队列等待,所以使用SynchronousQueue;

适用场景:快速处理大量耗时较短的任务,如Netty的NIO接受请求时,可使用CachedThreadPool。

Task.class
package thread_pool;

/**
 * @author summer
 * @create 2020-02-09 11:28
 */
public class Task implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"线程正在运行");
    }
}
CacheThreadDemo
package thread_pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author summer
 * @create 2020-02-09 11:27
 */
public class CacheThreadDemo {
    public static void main(String[] args) {
        Task task = new Task();
        // 线程池中不限制线程数量
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            executorService.execute(task);
        }
        executorService.shutdown();
    }
}
  • newFixedThreadPool 线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
  • corePoolSize与maximumPoolSize相等,即其线程全为核心线程,是一个固定大小的线程池,是其优势;
  • keepAliveTime = 0 该参数默认对核心线程无效,而FixedThreadPool全部为核心线程;
  • workQueue 为LinkedBlockingQueue(无界阻塞队列),队列最大值为Integer.MAX_VALUE。如果任务提交速度持续大余任务处理速度,会造成队列大量阻塞。因为队列很大,很有可能在拒绝策略前,内存溢出。是其劣势;
  • FixedThreadPool的任务执行是无序的;

适用场景:可用于Web服务瞬时削峰,但需注意长时间持续高峰情况造成的队列阻塞。

newFixedThreadPool
package thread_pool;

//import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author summer
 * @create 2020-02-09 11:42
 */
public class FixedThreadPoolDemo {
    public static void main(String[] args) {
        // 设置线程池大小,最多设置为5个线程同时运行
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            executorService.execute(new Task());
        }
        executorService.shutdown();
    }
}
  • newSingleThreaExecutor 单个线程
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

咋一瞅,不就是newFixedThreadPool(1)吗?定眼一看,这里多了一层FinalizableDelegatedExecutorService包装,这一层有什么用呢,写个dome来解释一下:

public static void main(String[] args) {
        ExecutorService fixedExecutorService = Executors.newFixedThreadPool(1);
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) fixedExecutorService;
        System.out.println(threadPoolExecutor.getMaximumPoolSize());
        threadPoolExecutor.setCorePoolSize(8);

        ExecutorService singleExecutorService = Executors.newSingleThreadExecutor();
//      运行时异常 java.lang.ClassCastException
//      ThreadPoolExecutor threadPoolExecutor2 = (ThreadPoolExecutor) singleExecutorService;
    }

对比可以看出,FixedThreadPool可以向下转型为ThreadPoolExecutor,并对其线程池进行配置,而SingleThreadExecutor被包装后,无法成功向下转型。因此,SingleThreadExecutor被定以后,无法修改,做到了真正的Single。

SingleThreaExecutor
package thread_pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author summer
 * @create 2020-02-09 12:10
 */
public class SingleThreaExecutorDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 5; i++) {
            executorService.execute(new Task());
        }
        executorService.shutdown();
    }
}
  • newSingleThreadScheduledExecutor
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

newScheduledThreadPool调用的是ScheduledThreadPoolExecutor的构造方法,而ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,构造是还是调用了其父类的构造方法。

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

newSingleThreadScheduledExecutor 延迟三秒,执行一次
package thread_pool;

//import java.util.concurrent.Executor;
import java.util.concurrent.*;

/**
 * @author summer
 * @create 2020-02-09 11:42
 */
public class SingleThreadScheduledExecutorDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        System.out.println(System.currentTimeMillis());
        scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("延时3秒");
                System.out.println(System.currentTimeMillis());

            }
        },3,TimeUnit.SECONDS);
    }
}

结果
1581222928304
延时3秒
1581222931310
newSingleThreadScheduledExecutor延时1秒,每三秒执行一次
package thread_pool;

//import java.util.concurrent.Executor;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author summer
 * @create 2020-02-09 11:42
 */
public class SingleThreadScheduledExecutorDemo2 {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        System.out.println(System.currentTimeMillis());
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("延时1秒,每三秒执行一次");
                System.out.println(System.currentTimeMillis());

            }
        },1,3,TimeUnit.SECONDS);
    }
}

6.4 线程池的生命周期

线程池

线程池的生命周期

▪ RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
▪ SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻
塞队列中已保存的任务。

▪ STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。

▪ TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,
线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。

▪ TERMINATED:在terminated() 方法执行完后进入该状态,默认
terminated()方法中什么也没有做。

6.5 线程池的创建

ThreadPoolExecutor提供了四个构造方法.

线程池

我们以最后一个构造方法(参数最多的那个),对其参数进行解释:

public ThreadPoolExecutor(int corePoolSize, // 1
                              int maximumPoolSize,  // 2
                              long keepAliveTime,  // 3
                              TimeUnit unit,  // 4
                              BlockingQueue<Runnable> workQueue, // 5
                              ThreadFactory threadFactory,  // 6
                              RejectedExecutionHandler handler ) { //7
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
}

参数说明

▪ corePoolSize:核心线程池的大小
▪ maximumPoolSize:线程池能创建线程的最大个数
▪ keepAliveTime:空闲线程存活时间
▪ unit:时间单位,为keepAliveTime指定时间单位
▪ workQueue:阻塞队列,用于保存任务的阻塞队列
▪ threadFactory:创建线程的工程类
▪ handler:饱和策略(拒绝策略)

6.5 execute方法执行逻辑

线程池

execute方法执行逻辑

  • 如果当前运行的线程少于corePoolSize,则会创建新的线程来执行新的任务;
  • 如果运行的线程个数等于或者大于corePoolSize,则会将提交的任务存放到阻塞队列workQueue中;
  • 如果当前workQueue队列已满的话,则会创建新的线程来执行任务;
  • 如果线程个数已经超过了maximumPoolSize,则会使用饱和策略RejectedExecutionHandler来进行处理。

6.6 Executor和Submit

线程池
submit是基方法Executor.execute(Runnable)的延伸,通过创建并返回一个Future类对象可用于取消执行和/或等待完成。

6.6 线程池的关闭

关闭线程池,可以通过shutdown和shutdownNow两个方法

原理:遍历线程池中的所有线程,然后依次中断

  • shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;
  • shutdown只是将线程池的状态设置为SHUTDOWN状态,然 后中断所有没有正在执行任务的线程