Java程序员从笨鸟到菜鸟(十二)进程和线程详解

1. 定义:

进程(process):可并发执行的程序在某个数据集合上的一次计算活动,也是操作系统进行资源分配的基本单位。

线程(thread):进程中包含一个或多个执行单位。

2. 进程

进程的五种状态

创建:分配了PCB(Process Control Block)进程控制块,进程所需的资源未分配,进程还未进入主存,创建工作未完成。

就绪:进程分配到了除CPU以外的所有必要资源,等待获取CPU(就绪状态是进入运行状态的唯一入口)

运行:进程获得了CPU,程序正在运行

阻塞:正在执行的进程由于某个事件而暂时无法保持继续执行

死亡:进程到达自然结束或者因意外被结束,进入终止状态

阻塞的状态分为三种

1. 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态

2. 同步阻塞:线程获取synchronized同步锁失败(此时锁被其它线程占用)

3. 其它阻塞:通过调用线程的sleep()或join()或发出了I/O请求。

死锁:两个或多个执行单元之间相互等待对方结束而引起的阻塞

例如:

一个线程T1获得了对资源R1的访问权

一个线程T2获得了对资源R2的访问权

T1请求对R2的访问权但此时权利被T2所占不得不等待

T2请求对R1的访问权但此时权利被T1所占不得不等待

T1和T2将永远维持等待状态,陷入了死锁的处境

解决方案:

1. 同一时刻不允许同一个线程访问多个资源

2. 为资源的访问权的获取定义一个关系顺序

3. 为所有访问资源的请求系统的定义一个最大等待时间(超时时间)、并处理失败的情况

进程控制块(PCB):可以记录进程的属性信息,以便凑走系统对进程进行控制和管理,而且PCB标志着进程的存在,操作系统根据系统中是否存在该进程的PCB而判断该进程是否存在,PCB包含以下三类信息:进程标识信息、处理器状态信息、进程控制信息。

进程的特征

1. 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。

2. 并发性:任何进程都可以同其它程序一起并发执行

3. 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源的独立单位

4. 异步性:由于进程间的相互制约使进程具有执行的间断性

3. 线程

线程是进程的一个实体,是被系统调度和分派的基本单位。

线程的性质

1. 线程是进程内的一个相对独立的可执行单元。

2. 由于线程是被调度的基本单元,而进程不是调度单元,所以每个进程在创建时,至少需要同时创建一个线程。

3. 进程是被分给并拥有资源的基本单元,同一进程的多个线程共享该资源,但线程不拥有资源。

4. 线程间能共享资源,所以需要通信和同步机制,且在需要的时候可以创建其它线程,但线程间不存在父子关系。

多线程

1. 指这个程序运行时产生了不止一个线程。

2. 实现方式:没有返回结果的:继承Thread类、实现Runnable接口;有返回结果:实现Callable接口

启动线程的方法:通过Thread类的start()方法,启动线程,并调用run()方法。

并行和并发

并行:多个CPU实例或者多台机器同时执行一段处理逻辑,真正的同时。

并发:通过CPU调度算法,让用户看上去同时执行,实际上操作层面不是真正的同时。

下图图示演示线程的五种状态图:原图来自传送门

关于多线程,想深入了解的可看Java中的多线程你只要看这一篇就够了

线程详解

1. 创建

1.1 继承Thread类:子类应该重写run()方法

1.2 实现Runnable接口:run()方法里面写出需要的操作

2. 实例化

2.1 继承Thread类:直接new实例化

2.2 实现Runnable接口:使用Thread的构造方法

3. 启动

在线程的Thread对象调用start()方法,使该线程从新状态转移到可运行状态,当该线程运行时,run()方法将运行

4. 实例

4.1 实现Runnable接口

/**
 * create by tan on 2018/5/23
 * 线程创建实例
 * 1.继承Thread类
 * 2.实现Runnable接口
 *
 * 创建时定义run()方法
 * 执行时执行start()方法
 **/
public class DoSomething implements Runnable{
    private String name;

    public DoSomething(String name) {
        this.name = name;
    }

    public void run() {
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 2; j++) {
                System.out.println(name + ":" + i);
            }
        }
    }

}
/**
 * create by tan on 2018/5/23
 * 测试Runnable接口实现的多线程
 **/
public class TestRunnable {
    public static void main(String[] args) {
        DoSomething ds1 = new DoSomething("张三");
        DoSomething ds2 = new DoSomething("李四");

        Thread t1 = new Thread(ds1);
        Thread t2 = new Thread(ds2);

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

4.2 继承Thread类

/**
 * create by tan on 2018/5/23
 * 测试扩展Thread类实现多线程
 **/
public class TestThread extends Thread {
    public TestThread(String name) {
        super(name);
    }

    /**
     * 阻止线程执行:睡眠
     * Thread的sleep()方法
     * 静态方法强制当前执行的线程休眠(暂停执行),在苏醒之前不会返回到可运行状态,睡眠时间到期,返回到可运行状态
     * 睡眠位置:sleep(毫秒)方法放在调用线程的run()方法之内
     * 注意:
     * 1.线程睡眠是帮助所有线程获得运行机会的最好方法
     * 2.线程睡眠到期自动苏醒,并返回可运行状态,不是运行状态
     * 3.只能控制当前正在运行的线程
     * */
    public void run() {
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 2; j++) {
                System.out.println(this.getName() + ":" + i);
                try {
                    Thread.sleep(1);
                    System.out.println("sleep方法,每隔1毫秒输出");
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Thread t1 = new TestThread("张三");
        Thread t2 = new TestThread("李四");

        t1.start();
        /**
         * Thread类中的jion()使用
         * 1.作用:主要作用是同步,可以使得线程之间的并行执行变为串行执行
         * 如下代码:
         * 程序在main线程中调用t1的jion()方法,需要返回t1线程继续执行直到线程t1执行完毕才执行t2线程
         * 当加入参数之后:例如t1.jion(10),在这10毫秒内,只执行t1线程,10毫秒之后,t1和t2线程回到并行执行状态
         * jion()方法必须跟start()方法后面,换言之,没有开启线程也不存在同步的说法了
         * 实现方法:通过调用线程的wait()方法来实现同步
         * */
        t1.join();
        t2.start();
    }
}

Thread类的sleep()、join()方法在注释中有详解

 

5. 线程的优先级和线程让步yield()

优先级:线程总是存在优先级,优先级在1~10,JVM线程调度就是基于优先级的抢先调度机制,大多数情况下,正在运行程序的优先级大于线程池中的优先级,当线程池都具有相同的优先级,调度度程序的JVM实现自由选择线程,这时可能有两种操作:1.选择一个线程运行,直到阻塞或是完成为止;2.时间分片,线程池内的线程提供均等的运行机会。可以通过setPriority(num)来设置线程的优先级,在Thread类中有三个常量:MAX_PRIORITY(最大)、MIN_PRIORITY(最小)、NORM_PRIORITY(默认为 5)。

yield()方法:当前运行的线程状态变为可运行的状态,以允许其他具有相同优先级的线程有运行的机会,不会导致线程进入到等待、阻塞、睡眠状态

6. 同步和锁

锁:每一个java内置对象都有一个内置锁,当程序运行到非静态的synchronized同步方法上时,自动获得当前实例的锁,这时候其它线程不能进入到该对象上的synchronized方法或代码块,直到释放。

同步实现:竟相访问的资源设为private、同步那些修改变量的代码,使用synchronized关键字同步方法或代码

代码实现:以银行账户存款、取款操作,来演示同步

/**
 * create by tan on 2018/5/23
 * 用于测试线程同步
 * 对于同步,在具体的java代码块中需要完成以下两个操作:
 * 1.把竞争访问的资源标识为private
 * 2.同步那些修改变量的代码,使用synchronized关键字同步方法或代码
 * synchronized只能标记非抽象方法,不能标识成员变量
 *
 * 同步方法以银行账户为例,模拟存款、取款操作
 **/
public class TestSync {
    public static void main(String[] args) {
        User user = new User("张三", 100);
        MyThread mt1 = new MyThread("线程A", user, 200);
        MyThread mt2 = new MyThread("线程B", user, -600);
        MyThread mt3 = new MyThread("线程C", user, 800);
        MyThread mt4 = new MyThread("线程D", user, 900);
        MyThread mt5 = new MyThread("线程E", user, -100);

        mt1.start();
        mt2.start();
        mt3.start();
        mt4.start();
        mt5.start();
    }
}
    class MyThread extends Thread {
        private User user;
        private int y = 0;

        MyThread(String name, User user, int y) {
            super(name);
            this.user = user;
            this.y = y;
        }

        public void run() {
            user.operate(y);
        }
    }


    // 创建账户对象
    class User {
        private String name; // 账户名称
        private double count; // 账户余额

        public User(String name, double count) {
            this.name = name;
            this.count = count;
        }

        public String getId() {
            return name;
        }

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

        public double getCount() {
            return count;
        }

        public void setCount(double count) {
            this.count = count;
        }

        // 添加操作方法
        public synchronized void operate(int x) {
            try {
                Thread.sleep(10);
                count += x;
                System.out.println(Thread.currentThread().getName() + "操作结束,增加:" + x + ",当前余额为:" + count);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        @Override
        public String toString() {
            return "User{" +
                    "name=" + name +
                    ", count=" + count +
                    '}';
        }
}

运行结果:对应的金额是能对应上的

线程A操作结束,增加:200,当前余额为:300.0
线程E操作结束,增加:-100,当前余额为:200.0
线程D操作结束,增加:900,当前余额为:1100.0
线程C操作结束,增加:800,当前余额为:1900.0
线程B操作结束,增加:-600,当前余额为:1300.0

Process finished with exit code 0

没有在用户操作方法上添加同步锁即synchronized关键字

运行结果:对应金额错乱了

线程D操作结束,增加:900,当前余额为:1400.0
线程A操作结束,增加:200,当前余额为:1400.0
线程E操作结束,增加:-100,当前余额为:1300.0
线程B操作结束,增加:-600,当前余额为:1400.0
线程C操作结束,增加:800,当前余额为:1400.0

Process finished with exit code 0

关于同步实现,可以让关键字synchronized来修饰方法,或是代码块,有时候同步代码块会带来比同步方法带来更好的效果;应尽可能在synchronized方法或是synchronized代码块中使用sleep()或者yield()方法,synchronized程序占有者对象锁,那么其他线程只能在一边等着,不仅耗费资源,而且严重影响效率。

经典案例:生产者-消费者-仓库 模型

/**
 * create by tan on 2018/5/23
 * 对于线程的同步,采用最经典的生产者和消费者来演示
 * 1、生产者仅仅在仓储未满时候生产,仓满则停止生产。
 * 2、消费者仅仅在仓储有产品时候才能消费,仓空则等待。
 * 3、当消费者发现仓储没产品可消费时候会通知生产者生产。
 * 4、生产者在生产出可消费产品时候,应该通知等待的消费者去消费。
 **/
public class Test {
    public static void main(String[] args) {
        Property property = new Property();
        Consumer c1 = new Consumer(50, property);
        Consumer c2 = new Consumer(30, property);
        Consumer c3 = new Consumer(20, property);
        Producer p1 = new Producer(10, property);
        Producer p2 = new Producer(20, property);
        Producer p3 = new Producer(70, property);

        c1.start();
        c2.start();
        c3.start();
        p1.start();
        p2.start();
        p3.start();
    }
}

/**
 * 仓库
 * */
class Property {
    private static final int MAX_SIZE = 100; // 仓库总数为100
    private int currentNum; // 当前的库存量

    public Property() {
    }

    public Property(int currentNum) {
        this.currentNum = currentNum;
    }

    /**
     * 生产指定数量的产品
     * @Param needNum
     * */
    public synchronized void produce(int needNum) {
        // 先判断是否需要进行生产
        while(needNum + currentNum > MAX_SIZE) {
            System.out.println("需要生产的产品数量:" + needNum + "超过剩余库存量:" + (MAX_SIZE - currentNum) + ".暂时不能执行生产任务!");
            try {
                // 当前的生产线程等待
                wait();
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 满足生产条件,则进行生产
        currentNum += needNum;
        System.out.println("已经生产了:" + needNum + "个产品,先仓储量为:" + currentNum);
        // 唤醒在此对象等待的所有线程
        notifyAll();
    }

    /**
     * 消费指定的产品数
     * @Param needNum
     * */
    public synchronized void consume(int needNum) {
        // 检测是否可以进行消费
        while(needNum > currentNum) {
            try {
                // 当前的生产线程等待,释放当前的线程锁
                wait();
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 满足消费条件,进行消费
        currentNum -= needNum;
        System.out.println("已经消费了:" + needNum + "个产品,仓库剩余:" + currentNum);
        notifyAll();
    }
}

/**
 * 生产者
 *
 * */
class Producer extends Thread {
    private int neddNum; // 生产产品的数量
    private Property property; // 仓库

    Producer(int needNum, Property property) {
        this.neddNum = needNum;
        this.property = property;
    }

    public void run() {
        // 生产指定数量的产品
        property.produce(neddNum);
    }
}

/**
 * 消费者
 *
 * */
class Consumer extends Thread {
    private int needNum;
    private Property property;

    Consumer(int needNum, Property property) {
        this.needNum = needNum;
        this.property = property;
    }
    public void run() {
        property.consume(needNum);
    }
}

运行结果:

已经生产了:10个产品,先仓储量为:10
已经生产了:20个产品,先仓储量为:30
已经消费了:20个产品,仓库剩余:10
已经生产了:70个产品,先仓储量为:80
已经消费了:50个产品,仓库剩余:30
已经消费了:30个产品,仓库剩余:0

Process finished with exit code 0

线程的安全高于性能。

关于interrupt()中断:只会影响wait状态、sleep状态、join状态,被打断的线程会抛出InterruptedException,Thread.interrupted()检测当前线程是否发生中断,返回boolean,interrupt()方法只是将这个状态设置为true而已,在wait等阻塞的时候会去检查并抛出异常。

Lock类主要目的和synchronized一样,都是为了解决同步问题,处理资源竞争的问题,功能类似,但有些区别:

1. lock更灵活,可以自由定义多把锁的加锁解锁操作(synchronized要按照先加的后解顺序)

2.提供多种加锁方案:lock阻塞式,trylock无阻塞式,lockInterruptily可打断式。

3.性能更高

 

Lock类有三个实现:

ReentrantLock、ReentrantReadWriteLocak.ReadLock、ReentrantReadWriteLocak.WriteLock

7. 线程池

为什么要用:

1. 创建、销毁线程伴随着系统开销,过于频繁创建、销毁线程会很大程度上影响效率

例如:创建线程时间T1,执行线程耗时T2,销毁线程耗时T3,当T1+T3>T2,是否认为执行任务不划算,正好线程池缓存线程,可用已有的闲置线程来执行任务。

2. 线程并发数量过多,抢占资源从而导致阻塞

线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况,运用线程池能有效的控制线程最大并发数。

3. 对线程一些简单的管理

例如延时执行,定时循环执行的策略等

线程池ThreadPoolExecutor

构造函数:4个构造函数,一共7种类型

  • int corePoolSize-> 该线程池中核心线程数的最大值,如果线程池新建线程的时候,当前线程数小于最大值,则创建的线程为核心线程,超过之后为非核心线程。核心线程默认情况下会一直存在线程池中,即使什么都不干,如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,超过一定时间就会被销毁。
  • int maximumPoolSize-> 线程池中线程总数的最大值,线程总数=核心线程+非核心线程.
  • long keepAliveTime-> 线程池中非核心线程闲置超时时长,一个非核心线程,如果不干活时长超过这个参数所设定的时长,就会被销毁,如果设置allowCoreThreadTimeOut=true,则会作用于核心线程。
  • TimeUnit unit-> keepAliveTime的单位,TimeUnit是个枚举(是一个整形常数的集合,如SUNDAY...)类型
TimeUnit.DAYS; // 天
TimeUnit.HOURS: // 小时
TimeUnit.MINUTES; // 分钟
TimeUnit.SECONDS; // 秒
TimeUnit.MILLSECONDS; // 毫秒
TimeUnit.MICROSECONDS; // 微秒
TimeUnit.NANOSECONDS; // 纳秒
  • BlockingQueue<Runnable>workQueue-> 该线程池中的任务队列,维护着等待执行的Runnable对象,当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务,常用的有三种队列:SynchronousQueue、LinkedBlockingDeque、ArrayBlockingQueue
ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小
LinkedBlockingQueue:单向队列,遵循 FIFO;LinkedBlockingDeque 双向队列
SynchronousQueue:不会保存的提交的任务,而是将直接新建一个线程来执行新来的任务
  • threadFactory:线程工厂,主要用来创建线程
  • handler:表示当拒绝处理任务时的策略
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出 RejectedExecutionException 异常
ThreadPoolExecutor.DiscardPolicy; 也是丢弃任务,但是不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy; 丢弃队列最前面的任务,然后重新尝试执行任务
ThreadPoolExecutor.CallerRunsPolicy; 由调用线程处理该任务

这四种策略是独立无关的,是对任务处理的四种表现形式,最简单的方式是直接丢弃任务,
但是涉及到对其哪一个任务,比如可以丢弃当前将要加入队列的任务本身(DiscardPolicy),
或者丢弃任务队列中的最旧任务(DiscardOldestPolicy).丢弃最旧任务也不是简单的丢弃最旧任务,
而是一些额外的处理。除了丢弃任务也可以直接抛出一个异常(RejectedExecutionException),
这是比较简单的方式,抛出异常(AbortPolicy)尽管实现方式比较简单,
但是由于抛出一个RuntimeException,因此会中断调用者的处理过程,除了抛出异常以外,
还可以不进入线程池执行,在这种方式(CallerRunsPolicy)中任务将由调用者线程去执行

Executor 是一个顶层接口,在里面只声明了一个方法 execute(Runnable),返回值为 void,用来执行穿进去的任务

ExecutorService 接口继承了 Executor 接口,并声明了一些其它方法:submit、shutdown 等

抽象类 AbstractExecutorService 实现了 ExecutorService 接口

然后 ThreadPoolExecutor 继承了类 AbstractExecutorService

向ThreadPoolExecutor添加任务:execute()方法

ThreadPoolExecutor的策略:

假设任务队列没有大小限制:        

  • 线程数未达到corePoolSize,新建一个核心线程执行任务,不会放入队列中
  • 线程数量达到corePoolSize,但小于最大线程数,并且任务队列是LinkedBlockingDeque的时候,超过核心线程的数量的任务会放在任务队列中排队
  • 线程数量达到corePoolSize,但小于最大线程数,并且任务队列是SynchronousQueue的时候,线程池会创建新建线程执行任务,这些任务不会放在任务队列中,这些线程属于非核心线程,任务完成后,闲置时间达到了超时时间就会被清除
  • 如果线程数量大于corePoolSize,并且大于最大线程数,当任务队列是LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队,当任务队列是LinkedBlockingDeque并且没有大小限制时,线程池的最大线程数设置是无效的
  • 如果线程数量大于corePoolSize,并且大于最大线程数,当任务队列是SynchronousQueue的时候,会因为线程池拒绝任务而抛出异常

任务队列有大小设置:

  • 当LinkedBlockingDeque塞满时,新增的任务会直接创建线程来执行,当线程数超过最大线程数会抛出异常
  • SynchronousQueue没有数量限制,根本不会保存任务,而是直接创建线程来执行

规则验证:

实现Runnable接口

public class DoSomething implements Runnable{
    public void run() {
        // 测试ThreadPoolExecutor创建线程池
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "正在运行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

验证规则:先使用execute方法开启三个线程,打印线程数,再开启三个线程,打印线程数,睡眠8秒后,打印线程数

        DoSomething ds = new DoSomething();
        executor.execute(ds);
        executor.execute(ds);
        executor.execute(ds);
        System.out.println("---先开三个线程---");
        System.out.println("核心线程数:" + executor.getCorePoolSize());
        System.out.println("线程池数:" + executor.getPoolSize());
        System.out.println("队列任务数:" + executor.getQueue().size());
        System.out.println("---再开三个线程---");
        executor.execute(ds);
        executor.execute(ds);
        executor.execute(ds);
        System.out.println("核心线程数:" + executor.getCorePoolSize());
        System.out.println("线程池数:" + executor.getPoolSize());
        System.out.println("队列任务数:" + executor.getQueue().size());
        try {
            Thread.sleep(8000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("---8秒之后");
        System.out.println("核心线程数:" + executor.getCorePoolSize());
        System.out.println("线程池数:" + executor.getPoolSize());
        System.out.println("队列任务数:" + executor.getQueue().size());
        executor.shutdown(); // 关闭线程

 

验证1:

 

1.核心线程为6,最大线程数为10,超时为5秒,队列是SynchronousQueue

ThreadPoolExecutor executor = new ThreadPoolExecutor(6, 10, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

打印结果:

---先开三个线程---
核心线程数:6
线程池数:3
队列任务数:0
---再开三个线程---
核心线程数:6
线程池数:6
队列任务数:0
pool-1-thread-1正在运行
pool-1-thread-5正在运行
pool-1-thread-2正在运行
pool-1-thread-6正在运行
pool-1-thread-3正在运行
pool-1-thread-4正在运行
---8秒之后
核心线程数:6
线程池数:6
队列任务数:0

由输出结果可以看出每个任务都是直接启动一个核心线程来执行任务,一共创建了6个线程,不会放入队列中,8秒之后线程池还是6个线程,核心线程默认情况下不会被回收,不受时间限制

验证2:

1.核心线程为3,最大线程数为6,超时为5秒,队列是LinkedBlockingDeque

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());

打印结果:

---先开三个线程---
核心线程数:3
线程池数:3
队列任务数:0
---再开三个线程---
核心线程数:3
线程池数:3
队列任务数:3
pool-1-thread-2正在运行
pool-1-thread-3正在运行
pool-1-thread-1正在运行
pool-1-thread-2正在运行
pool-1-thread-3正在运行
pool-1-thread-1正在运行
---8秒之后
核心线程数:3
线程池数:3
队列任务数:0

Process finished with exit code 0

验证3:

1.核心线程为3,最大线程数为6,超时为5秒,队列是SynchronousQueue

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

运行结果:

---先开三个线程---
核心线程数:3
线程池数:3
队列任务数:0
---再开三个线程---
核心线程数:3
线程池数:6
队列任务数:0
pool-1-thread-2正在运行
pool-1-thread-5正在运行
pool-1-thread-1正在运行
pool-1-thread-3正在运行
pool-1-thread-4正在运行
pool-1-thread-6正在运行
---8秒之后
核心线程数:3
线程池数:3
队列任务数:0

Process finished with exit code 0

当队列是SynchronousQueue时,超出核心线程的任务会创建新的线程来执行,但这些时非核心线程,受超时时间限制,最后只有剩下的3个核心线程

验证4:

1.核心线程数是3,最大线程数是4,队列是LinkedBlockingDeque

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 4, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());

运行结果:

---先开三个线程---
核心线程数:3
线程池数:3
队列任务数:0
---再开三个线程---
核心线程数:3
线程池数:3
队列任务数:3
pool-1-thread-3正在运行
pool-1-thread-1正在运行
pool-1-thread-2正在运行
pool-1-thread-1正在运行
pool-1-thread-2正在运行
pool-1-thread-3正在运行
---8秒之后
核心线程数:3
线程池数:3
队列任务数:0

Process finished with exit code 0

LinkedBlockingDeque队列不受最大线程数的影响,但是在LinkedBlockingDequeue有大小限制时就会瘦最大线程数影响

将队列大小设置为2

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 4, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(2));

运行结果:

---先开三个线程---
核心线程数:3
线程池数:3
队列任务数:0
---再开三个线程---
核心线程数:3
线程池数:4
队列任务数:2
pool-1-thread-3正在运行
pool-1-thread-1正在运行
pool-1-thread-2正在运行
pool-1-thread-4正在运行
pool-1-thread-1正在运行
pool-1-thread-3正在运行
---8秒之后
核心线程数:3
线程池数:3
队列任务数:0

Process finished with exit code 0

首先三个任务开启了三个核心线程1,2,3,然后第四、五个加入到队列中,第六个因为队列满了,就直接创建一个新线程4,8秒后,非核心线程受超时时间限制,最后只剩3个线程

验证5:

1.核心线程数是3 ,最大线程数是4,超时5秒,队列是SynchronousQueue

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 4, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

运行结果:

---先开三个线程---
核心线程数:3
线程池数:3
队列任务数:0
---再开三个线程---
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task thread.DoSomething@8a548b rejected from java.util.concurrent.ThreadPoolExecutor@15356d5[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
	at thread.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:23)
pool-1-thread-4正在运行
pool-1-thread-3正在运行
pool-1-thread-2正在运行
pool-1-thread-1正在运行

添加第5个任务的时候就报错了,因为SynchronousQueue不保存任务,收到任务就去创建线程,所以超过最大线程数就抛出异常了

ThreadPoolExecutor模块参考文章:https://blog.csdn.net/qq_25806863/article/details/71126867

常用的4种线程池:

  • CachedThreadPool(): 可缓存线程池:线程数无限制、有空闲线程则复用空闲线程,如果没有就新建线程、一定程度上减少频繁创建、销毁线程,减少系统开销。创建方法 ExecutorService cachedThreadPool = Executors.newCachedThreadPool()
  • FixThreadPool():定长线程池:可控制线程的最大并发数,超出的线程会在队列中等待
  • ScheduledThreadPool():定长线程池:支持定时及周期性任务执行
  • SingleThreadExecutor():单线程化的线程池:有且只有一个线程执行任务,所有线程任务按指定顺序执行,及遵循队列的入队和出队规则。

接下来代码演示4种线程池

以上面存款、取款操作来演示

1.CachedThreadPool: 可缓存线程池

/**
 * 使用ExecutorService来创建CachedThreadPool 不限线程数量
         * */
 ExecutorService exec = Executors.newCachedThreadPool();

 exec.execute(mt1);
 exec.execute(mt2);
 exec.execute(mt3);
 exec.execute(mt4);
 exec.execute(mt5);
 // 关闭线程池
 exec.shutdown();

运行结果:

pool-1-thread-3操作结束,增加:800,当前余额为:900.0
pool-1-thread-4操作结束,增加:900,当前余额为:1800.0
pool-1-thread-1操作结束,增加:200,当前余额为:2000.0
pool-1-thread-2操作结束,增加:-600,当前余额为:1400.0
pool-1-thread-5操作结束,增加:-100,当前余额为:1300.0

Process finished with exit code 0

2.FixedThreadPool: 指定线程数量线程池

/**
 * 使用ExecutorService来创建FixedThreadPool,规定线程数量
 * */
ExecutorService exec = Executors.newFixedThreadPool(2); // 通过线程池创建2个线程

运行结果:

pool-1-thread-2操作结束,增加:-600,当前余额为:-500.0
pool-1-thread-1操作结束,增加:200,当前余额为:-300.0
pool-1-thread-1操作结束,增加:900,当前余额为:600.0
pool-1-thread-2操作结束,增加:800,当前余额为:1400.0
pool-1-thread-1操作结束,增加:-100,当前余额为:1300.0

Process finished with exit code 0

3. ScheduledThreadPool: 可定时循环执行任务线程池

/**
 * 使用SchedulePoolExecutor来创建ScheduleThreadPool 可以定时并且周期性的执行任务
 * */
 ScheduledExecutorService exec = Executors.newScheduledThreadPool(2);
 exec.schedule(mt1, 1000, TimeUnit.MILLISECONDS); // 延时1秒,触发一次

 exec.scheduleAtFixedRate(mt1, 2000, 5000, TimeUnit.MILLISECONDS); // 延迟2秒,每隔5秒周期性触发

运行结果:

pool-1-thread-2操作结束,增加:200,当前余额为:300.0
pool-1-thread-1操作结束,增加:-600,当前余额为:-300.0 // 周期性的进行-600的操作,相当于一直在透支
pool-1-thread-1操作结束,增加:-600,当前余额为:-900.0
pool-1-thread-1操作结束,增加:-600,当前余额为:-1500.0
pool-1-thread-1操作结束,增加:-600,当前余额为:-2100.0

Process finished with exit code -1

4. SingleThreadPool: 单线程的线程池,一次只有一个线程在执行任务,按任务提交顺序,顺序执行任务

/**
 * 使用ExecutorService来创建SingleThreadPool单个工作线程,如果单个线程因为某些错误中止
 * 新的线程会代替完成后续线程,按顺序执行任务,每次只允许有一个任务处于激活状态,相当于数量为1
 * 的FixedThreadExecutor
 * */
 ExecutorService exec = Executors.newSingleThreadExecutor();

运行结果:

pool-1-thread-1操作结束,增加:200,当前余额为:300.0
pool-1-thread-1操作结束,增加:-600,当前余额为:-300.0
pool-1-thread-1操作结束,增加:800,当前余额为:500.0
pool-1-thread-1操作结束,增加:900,当前余额为:1400.0
pool-1-thread-1操作结束,增加:-100,当前余额为:1300.0

Process finished with exit code 0

一般情况下会使用Executors创建线程池,但目前不推荐(阿里巴巴Java开发手册也不允许这方法创建线程池),而是通过ThreadPoolExecutro方式创建可以明确线程池运行规则,规避资源耗尽的风险

 

  1. newFixedThreadPool和newSingleThreadPool;主要问题是堆积的请求处理队列会耗费非常大的内存,甚至有可能OOM(Out of Memory)
  2. newCachedThreadPool和newScheduledThreadPool:主要问题是创建的最大线程数是integer.MAX_VALUE,会创建非常多的线程,甚至OOM

4. 进程和线程的区别

1. 一个程序至少有一个进程,一个进程至少有一个线程。进程是资源分配的最小单位,线程是运行的最小执行单位。

2.  线程拥有独立的堆栈空间,但是可以共享数据段,它们彼此之间共享大部分数据,开销比较少,切换速度块,效率高;进程拥有独立的堆栈空间和数据段,每当新建一个进程时必须分配独立的地址空间,系统开销比较大;进程之间相互独立,所以一个进程的崩溃不会对其它进程产生影响,而线程只是一个进程之间的不同执行路径,一个线程死掉就等于整个进程死掉。

3. 进程之间相互独立,进程间的通信相对复杂,而线程由于共享数据段,通信会比较方便。

4. 线程使得CPU系统更加有效,当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

备注:进程和线程的部分信息来自Java中的多线程你只要看这一篇就够了

           进程和线程的区别信息来自进程和线程的定义、区别与联系

版权声明:欢迎转载, 转载请保留原文链接。https://mp.csdn.net/postedit/79543828

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值