队列 线程池 自定义线程池

一. 队列

  1. 什么是队列: 一种用来存储数据的数据结构,有先进先出的特点
  2. 队列的分类: 非阻塞队列ConcurrentLinkedQueue, 阻塞队列 BlockingQueue

阻塞队列与非阻塞队列的区别:

阻塞队列: 入列时如果超过了指定范围,会阻塞等待,在获取数据时如果队列中没有也会阻塞等待,可以有效防止队列容器溢出,数据丢失,是线程安全的
非阻塞队列: 数据入列时如果超出了指定范围,不会等待,直接报错,在出列时,如果为空不会等待直接报错

根据队列中存放的数据个数是否是指定有限的,分为有界队列,与无界队列

常用队列:

ArrayDeque, (数组双端队列) 
PriorityQueue, (优先级队列) 
ConcurrentLinkedQueue, (基于链表的并发队列) 
DelayQueue, (延期阻塞队列,阻塞队列实现了BlockingQueue接口) 
ArrayBlockingQueue, (基于数组的并发阻塞队列) 
LinkedBlockingQueue, (基于链表的FIFO阻塞队列) 
LinkedBlockingDeque, (基于链表的FIFO双端阻塞队列) 
PriorityBlockingQueue, (带优先级的无界阻塞队列) 
SynchronousQueue (并发同步阻塞队列)

1. ConcurrentLinkedDeque并发非阻塞式队列

  • 优点: 适用于高并发场景下的,利用CAS无锁机制,基于链接节点的非阻塞无界线程安全队列,该队列遵循先进先出原则,不允许向该队列中添加null值,
  • 添加方法: add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中这俩个方法没有任何区别)
  • 删除方法: poll() 和peek() 都是取头元素节点,区别在于前者会删除元素,后者不会
  1. 使用示例
/*** 非阻塞式队列的特征: 当向该队列中添加数据时,如果添加的数据超出了
 * 队列的总数,会直接抛出异常,如果获取队列中的数据时,如队列为空会返回null
 * 注意获取队列中数据后要使用可以自动删除被获取的数据的方式,不然,下次
 * 获取数据获取到的还是原来的*/
public class TestConcurrentLinkedDeque {

    public static void main(String[]args){

        //创建非阻塞式队列ConcurrentLinkedDeque对象,根据存放到队列的数据
        //类型,设置泛型类型
        ConcurrentLinkedDeque<String> q = new ConcurrentLinkedDeque();
        //向队列中添加数据
        q.offer("AA");
        q.offer("BB");
        q.offer("FF");
        q.offer("RR");
        q.offer("CC");
        //从头获取元素,被获取到的队列元素会自动删除(支持此方式)
        System.out.println(q.poll());//多次执行poll会获取下一个
        System.out.println(q.poll());
        //从头获取元素,被获取后不会被删除(一般不会使用此方式)
        System.out.println(q.peek());
        //获取当前队列元素的总个数(如果前面被获取了个数会删除)
        System.out.println(q.size());//上面执行了一次poll方法,会删除掉一个,打印为4
    }
}

2. BlockingQueue阻塞队列

  • 阻塞队列是一个支持两个附加操作的队列,附加的操作是:获取元素时,如果队列为空,获取元素的线程会等待队列变为非空,然后执行。当队列满时,存储元素的线程会等待队列可用。
  • 阻塞队列常用于多线程数据共享,线程通讯,生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素
  • “生产者”和“消费者”模型中,通过队列可以实现两者之间的数据共享。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然,通过阻塞式队列解决这些问题,当需要生产数据时如果不能生产成功,当前线程等待生成成功后,其他线程才允许执行,当需要消费时如果消费不成功,当前线程在消费处等待消除成功后,其他线程允许执行

ArrayBlockingQueue

有边界的阻塞队列(可以不阻塞,阻塞设置在向队列中添加或获取数据时设置添加与获取的等待时间,如果添加或获取不成功,指定等待多长时间,如果在这段时间内还是没有添加或获取成功,则返回结果),内部实现是一个数组。有边界的意思是它的容量是有限的,必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。ArrayBlockingQueue遵循先进先出规则

	//创建ArrayBlockingQueue,根据传入列队元素类型设置泛型,并设置列队元素的个数
	ArrayBlockingQueue <String> arrays = new ArrayBlockingQueue<String>(3);
	//非阻塞式添加,此方式没有设置当添加数据不成功时的等待时间
	arrays.offer("ttt");
	arrays.offer("vvv");
	arrays.offer("rr");
	// 阻塞式添加,如果添加不成功设置等待时间,该时间内如果还是没有添加成功
	//则不添加,可以通过返回的boolea值进行判断(此处指定等待时间为1,后面时时间单位为秒)
	arrays.offer("qq", 1, TimeUnit.SECONDS);
	
	//非阻塞获取
	arrays.poll();
	//阻塞获取,如果获取不到,指定等待,在指定等待时间内
	//获取不到,最后返回null;
	arrays.poll(3,TimeUnit.SECONDS);

LinkedBlockingQueue

阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE的容量 。它的内部实现是一个链表。遵循先进先出,规则

	//创建LinkedBlockingQueue列队对象,并制定元素个数(可以不指定那么就是无边界的)
	LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue(3);
	linkedBlockingQueue.offer("AAA");//非阻塞式添加
	linkedBlockingQueue.offer("DDD",3,TimeUnit.SECONDS);//阻塞式添加
	linkedBlockingQueue.add("BBB");
	System.out.println(linkedBlockingQueue.size());
	linkedBlockingQueue.poll();//非阻塞式获取
	linkedBlockingQueue.poll(3,TimeUnit.SECONDS);//阻塞式获取

PriorityBlockingQueue

是一个没有边界的队列,它的排序规则和 java.util.PriorityQueue一样。需要注意,PriorityBlockingQueue中允许插入null对象。所有插入PriorityBlockingQueue的对象必须实现 java.lang.Comparable接口,队列按照Comparable来设置元素的先后顺序。可以通过PriorityBlockingQueue获得一个迭代器Iterator,但这个迭代器并不保证按照优先级顺

SynchronousQueue

可以看为是同步列队,队列内部仅允许容纳一个元素。当一个线程插入一个元素,其他插入线程进入阻塞状态,只有插入的元素被消费后,才可以再次执行插入操作,消费也是相同。

3. 通过队列实现线程通讯

  1. 创建生产者,持有一个阻塞队列,将生产的数据存入队列中
//实现Runnable接口,重写run方法方式创建生产线程类
class ProducerThread implements Runnable {
   private BlockingQueue<String> blockingQueue;//队列对象属性
   //原子类AtomicInteger,通过这个原子类的的方法,对数据所运算,可以保证线程安全
   private AtomicInteger count = new AtomicInteger();
   //标识符,用来标识下一步的操作,是该生成还是消费,并使用volatile设置可见性
   //flag这个标识符是多个线程的共享数据,共享数据应该使用synchronized来同步
   //防止冲突,但是,flag这个数据并不参关于结果的真正运算,只是用来判断进行标识
   //并不会涉及到原子性的问题,所以设置volatile可见性,禁止重排序
   private volatile boolean FLAG = false;

   public ProducerThread(BlockingQueue<String> blockingQueue) {
      this.blockingQueue = blockingQueue;
   }
   /*生产者run方法,方法中通过判断flag来判断解释了是否应该进行生产操作
   * 如果flag不是true,则通过创建生产线程对象时传递进来操作队列元素对象
   * 阻塞式生成队列元素,生产消息,filg不是true是循环为死循环,只要列队中
   * 设置的边界不满(在创建列队对象时设置为3),会不停的生产消息(不停生产,
   * 后面消费线程会不停消费,只要消费线程不停止读取,永远都存不满)*/
   @Override
   public void run() {
      System.out.println(Thread.currentThread().getName() + "生产者开始启动....");
      while (!FLAG) {
         //通过原子类调用incrementAndGet方法,做累计+1运算
         // (当前默认为第一次调用返回1,保证线程安全,返回字符串)
         String data = count.incrementAndGet() + "";
         try {
            //向队列中阻塞添加数据,并设置等待时间为2,返回添加结果offer
            boolean offer = blockingQueue.offer(data, 1, TimeUnit.SECONDS);
            if (offer) {//如果添加成功
               System.out.println(Thread.currentThread().getName() + ",生产队列" + data + "成功..");
               //stop();不使用
            } else {//如果添加失败
               System.out.println(Thread.currentThread().getName() + ",生产队列" + data + "失败..");
            }
            Thread.sleep(1000);//线程休眠
         } catch (Exception e) {

         }
      }
      System.out.println(Thread.currentThread().getName() + ",生产者线程停止...");
   }
   public void stop() {
      this.FLAG = false;
   }
}

/*注意,
* 1.此处的filg标识与线程通讯中使用的filg是有一定区别的,线程通讯中的filg是在共享数据中
* 是两个线程同时操作一个filg, 此处的filg各个线程里面都有,各自是各自的,互不影响的,
* 此处的filg标识只是用来标识是否允许开启while循环,进行生产消息,或者消费消息,
* 2.由于设置的生产消息线程执行会没睡眠1秒执行一次(while,与sleep,)当队列数据存放满了以后
* 会等待1秒,合起来时每两秒生产一个消息,
* 消费消息是阻塞式消费,如果获取不到消息会阻塞等待2秒,两个线程同时执行时根据设置的时间配合
* 就是每生产一个,就消费一个
* 3.在main方法中注意子线程的开启方式,创建子线程对象,将子线程对象传入Thread构造器中,
* 创建Thread对象,通过Thread对象开启线程
* 4.没有使用同步,线程页不需要等待,filg也不是共享数据,再好好考虑一下还是否需要volatile来修饰
* */

  1. 创建消费者,也持有一个队列,在初始化时需要对这个队列进行赋值,通过持有的队列,取出要消费的数据进行消费
class ConsumerThread implements Runnable {
   private volatile boolean FLAG = true;
   private BlockingQueue<String> blockingQueue;

   public ConsumerThread(BlockingQueue<String> blockingQueue) {
      this.blockingQueue = blockingQueue;
   }

   /*消费在run方法中,通过判断flag标识,来验whil循环是否开启,
   * 如果flag为true,则是消费消息,通过初始化消费线程对象时传入的队列对象
   * 调用poll,阻塞式获取该队列中的元素,并设置阻塞等待时间
   * 判断获取到的数据是否为null,如果能获取到数据,循环不停执行,获取消息
   * 否则设置filg,return跳出while循环结束代码执行
   * 每消费一个消息停止执行消费,让生产者生产消息
   * ,接下来应该进行 生成消息的操作,生成线程与消费线是并发执行的,都在运行中,不用设置线程阻塞与等待*/
   @Override
   public void run() {
      System.out.println(Thread.currentThread().getName() + "消费者开始启动....");
      while (FLAG) {//通过flag判断如果是true则进行消费,如果不是
         try {
            String data = blockingQueue.poll(2, TimeUnit.SECONDS);

            //不用进行""验证,引入如果为""说明取到了值,值时""而已,但是列队中不允许存""
            if (data == null ) {
               FLAG = false;//
               System.out.println("消费者超过2秒时间未获取到消息.");
               return;
            }
            System.out.println("消费者获取到队列信息成功,data:" + data);

         } catch (Exception e) {
            // TODO: handle exception
         }
      }
   }
  1. 生产消费测试,根据生产消费的特性,选择指定的队列存储数据,生产时将队列赋值给生产线程中的属性,消费时在消费线程的队列属性中获取数据进行消费
 	public static void main(String[] args) {
      /*创建消费线程类与生产线程类时需要传递操作列队数据的对象,通过这个
      * 对象,生产线程生产消息,消费线程消费消息*/
      //创建操作列队数据对象LinkedBlockingQueue,阻塞列队,并设置列队元素个数
      BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(3);
      //创建生产者线程(传入队列对象,该对象要与消费者中是同一个对象)
      ProducerThread producerThread = new ProducerThread(blockingQueue);
      //创建消费者线程(传入队列对象)
      ConsumerThread consumerThread = new ConsumerThread(blockingQueue);
      Thread t1 = new Thread(producerThread);//通过Thread方式启动线程
      Thread t2 = new Thread(consumerThread);
      t1.start();//启动生产者线程,生产消息
      t2.start();//启动消费者线程,消费消息
      //10秒后 停止线程..
      try {
         Thread.sleep(10*1000);
         producerThread.stop();
      } catch (Exception e) {
         // TODO: handle exception
      }
   }

二. 线程池

  1. 什么是线程池: java通过多线程支持并发处理,在并发操作时会有多个线程创建,处理,处理完毕后关闭下次等操作,线程的创建于销毁会造成资源浪费,为了解决这个问题,将多个线程预先设置好,放入一个池中,java几乎所有的异步,并发等操作都可以通过线程池来完成
  2. 线程池优点:减少线程的创建于销毁步骤,降低资源浪费,合理的设置池中线程的个数有效防止线程溢出,接收到任务后直接处理,减去了创建线程步骤的,增加相应速度,线程是稀有资源,在资源层面来看又是重量级的,使用线程池,可以合理有效的管理线程
  3. 什么是线程溢出: 线程的创建需要占用大量的cpu资源,频繁的创建造成的溢出

线程池的分类:

  1. newCachedThreadPool: 可缓存线程池,无限大小,jvm自动回收,如果线程池线程个数超过处理要求可新建线程,如果线程空闲,可灵活回收
  2. newFixedThreadPool: 定长的线程池,设置线程最大并发时的数量,如果超出了这个数量,会在列队中等待
  3. newScheduledThreadPool: 定长线程池,支持定时及周期性任务执行

线程池原理分析

  1. 程序运行时首先判断线程池中的核心线程数是否创建完毕,是否在执行任务
  2. 如果核心线程都在执行任务,判断线程池中的队列是否存满,没有存满则将任务放入工作队列中,等待线程执行完毕再去队列中去出这个任务执行
  3. 如果队列已满,则去判断最大线程数是否创建完毕,如果最大线程数没有创建完毕,则通过最大线程数里面的线程执行
  4. 如果最大线程已经创建完毕,则将请求任务交由饱和策略处理: 丢弃或拒绝,或者挤占已存在的任务

Executors 创建线程池

  1. Executors工厂类中提供了创建 newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool 等创建线程池的方法

  2. execute() 与 submit() 方法都可以通过线程池对象调用这两个方法去执行线程中的逻辑代码,但是submit() 方法可以拿到线程执行后的返回值具体了解Callable与Future模式

  3. shutdown() 线程池停止运行

  4. 示例:

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

public class TestThreadPool {
    public static void main(String[] args) {
        TestThreadPool testThreadPool= new TestThreadPool();
        //testThreadPool.testNewCachedThreadPool();//测试缓存线程池(无限大小)
        //testThreadPool.testNewFixedThreadPool();//测试定长线程池
        //testThreadPool.testNewScheduledThreadPool();//测试定长,周期执行线程池
        testThreadPool.testNewSingleThreadExecutor();//测试单线程化线程池
        /*方法中通过线程池对象,调用execute()方法(newScheduledThreadPool定长周期性
        * 调用的是.schedule()方法),将线程对象传入方法中,启动线程,执行线程中的run方法
        * (方法中传入的是匿名内部类方式创建的线程类,可以外部创建线程类对象,将对象传入调用运行的)*/
    }
    //测试newCachedThreadPool可缓存线程池
    public  void testNewCachedThreadPool(){
        //创建一个无线大小可缓存线程池(通过Executors调用静态方法newCacheThreadPool()方法)
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

        /*创建线程*/
        for (int i = 0; i < 10; i++) {
            //使用final修饰的原因是要在匿名内部类的子线程中使用
            //内部类使用外部类的参数要用final来修饰(为什么temp被final修饰后
            //后面打印的值还可以变,原因是temp在for循环中,每循环一次就创建一个
            //打印的每个temp都是每次循环自己的,互不影响并将当前循环的下标i赋值给temp)
            final int temp = i;
            /*通过execute()方法,将线程对象传入该线程池中,启动线程(此处使用的是匿名线程类,
            * 也可以创建一个线程类对象,将线程类对象传递给execute方法,通过这个方法启动线程
            * 执行线程类中需要多线程操作的逻辑业务run方法* */
            newCachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + temp);
                }
            });
            /*观察运行后的结果可以的得知,正常不使用线程池,根据for循环的循环执行
            * 需要创建十个线程,来运行reun中的方法,而此处通过newCachedThreadPool
            * 线程池启动线程,只创建了6个(会根据请求自己判断进行设置的)当先开启运行的
            * 线程,执行run方法完毕后,会线程复用,再次运行run方法,节省了线程的创建,
			* 但是每来一个任务时,都会在必要时新建线程执行任务,这就有可能导致大量的
			*线程被创建,进而系统瘫痪*/
        }
    }


    //测试newFixedThreadPool定长线程池
    public void testNewFixedThreadPool(){
        //创建newFixedThreadPool定长线程池对象,并设置线程池中最大线程数量,
        //通过创建出来的指定线程个数的这些线程,线程复用做多线程的逻辑操作
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);

        for( int i =0 ;i<10;i++){
            final int temp = i;
            newFixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+":"+temp);
                }
            });
            /*根据观察运行结果得出,不使用线程池,会创建10个线程分别来
            * 运行run方法,使用newFixedThreadPool线程池,会创建指定个数的
            * 线程,当线程未执行完毕,其他执行会在阻塞队列中等待,指定线程空闲时再执行
            * 执行流程:
            * 1.判断线程池中的核心线程是否都在执行任务,指定创建的核心线程是否全部创建完毕,
            * 如果创建完毕,判断核心线程中是否有空闲线程,如果核心线程创建完毕,并且都在执行任务
            * 2.线程池判断工作队列是否已满,如果没有满,则将新提交的任务存储到工作列队中,
            * 等线程池中核心线程有执行完毕的,在执行列队中的任务
            * 3.如果工作队列已满,则去判断最大线程数的线程,是否全部创建,如果最大线程数都以全部       
			*   创建,并且队列已满则交给饱和策略来处理这个任务* */
        }
    }

    //测试newScheduledThreadPool定长线程池,支持定时及周期性任务执行。延迟执行
    public void testNewScheduledThreadPool(){
        //创建newScheduledThreadPool定长,支持周期性任务,可延时执行的线程池对象,
        // 并设置线程池中最大线程数量,注意,不可以使用ExecutorService 来接收创建的对象
        // 注意开启线程的方式,需要指定定时时间
        //通过创建出来的指定线程个数的这些线程,线程复用做多线程的逻辑操作
     ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);

        for( int i =0 ;i<10;i++){
            final int temp = i;
            //开启线程使用schedule()方法,传入线程对象,设置线程周期性执行,
            newScheduledThreadPool.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+":"+temp);
                }
            },3, TimeUnit.SECONDS);//指定时间
            /*根据观察得出结果: 创建newScheduledThreadPool线程池对象设置线程个数
            * 开启线程运行时,会根据schedule方法设置的等待时间,如果线程池中一个线程都没创建
            * 会根据设置的时间等待,等待完成后,才会开始运行,如果有一个运行了,就不会再去等待*/
        }
    }

    //测试newSingleThreadExecutor单线程化线程池,使用唯一的工作线程来执行任务
    //保证所有任务按照指定顺序执行
    public void testNewSingleThreadExecutor(){
        //创建newSingleThreadExecutor单线程线程池,
        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();

        for( int i =0 ;i<10;i++){
            final int temp = i;
            //开启线程使用execute方法,传入线程对象,
            newSingleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+":"+temp);
                }
            });
            /*根据观察得出结:由始至终都只有一条线程运行,当执行完一次,
            * 重新使用这一个线程执行下一次*/

        }
			executor.shutdown();//停止线程池
    }
    
}

线程池调用 submit() 方法,

自定义线程池

查看通过 Executors 调用 newFixedThreadPool() 方法创建线程池的源码,发现内部是通过ThreadPoolExecutor 的构造器指定核心线程数,最大线程数,指定队列的方式来创建的,所以我们可以通过ThreadPoolExecutor 构造器,选择合适的队列,核心线程数,最大线程数来实现自定义线程池,ThreadPoolExecutor 构造器中,第一个参数指定核心线程数,第二个参数指定对打线程数,第三个参数是只线程空闲超时时间,超时后自动回收,第四个参数为指定超时时间的单位,第五个参数为指定的存放队列
在这里插入图片描述
自定义线程池示例

import java.util.concurrent.*;
/*自定义线程池,创建ThreadPollExecutor对象,通过构造器,设置需要创建的线程池参数
* 传递核心线程数,最大线程数,空闲线程存活时间,时间类型,对应的队列
* 通过创建的ThreadPoolExecutor对象,调用execute时,传入对应的自定义线程类,
* 实际上调用的是ThreadPoolExecutor中的方法,将通过构造器传递的参数传递给方法
* 通过这个方法,运行线程池*/
public class Test0007 {
   public static void main(String[] args) {
   		//创建自定义线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
              1,//核心线程数(实际运行的线程个数?
             2,//最大线程数,线程池中最大允许创建的线程
            30l, TimeUnit.SECONDS, //空闲线程存活时间,推荐30秒,
			//列队,此处使用指定长度列队,(最多缓存三个任务)
            new LinkedBlockingQueue<Runnable>(3) );
      for (int i = 1; i <= 6; i++) {
         //自定义线程类,
         TaskThred t1 = new TaskThred("任务" + i);
         //ThreadPoolExecutor对象调用自身的execute方法,通过创建
         //这个对象时设置的初始化参数运行线程池,设置线程池
         //传入线程类,对线程类中的run方法,使用线程池设置的多线程进行操作
         executor.execute(t1);
      }
      //线程池停止运行
      executor.shutdown();
      /*观察运行结果理解: 自定义线程池最大线程数为2,列队中运行存放3个任务
      * 当用户运行程序时,通过for循环,会进行6次提交任务的请求,最后一次提交的任务
      * 先判断核心线程是否空闲,不空闲,然后判断核心线程是否创建完毕,创建完毕(1)
      * 然后判断列队是否存满,已存满(3)最大线程是否创建完毕,创建完毕(2) ,
      * 队列+最大线程,就是可执行任务为5,而提交6此超过了,
*为什么队列使用阻塞式: 当用户提交的任务超出最大线程数时,会将任务放入队列中阻塞等待,等待当有线程任务执行完毕后,在队列中任务出列,通过空闲线程来执行这个任务,如果使用非阻塞式队列,存入队列任务会立即出列,而出列后又没有空闲线程来执行,会直接挂掉

执行流程: 1.2.3.4.都在使用核心线程复用执行,如果线程正则执行中,会将线程放入队列阻塞等待,当5时创建最大线程执行,当6时,判断核心线程都在执行,判断队列已存满,判读最大线程以创建,报错*/
   }
}

线程类

class TaskThred implements Runnable {
   private String taskName;

   public TaskThred(String taskName) {
      this.taskName = taskName;
   }
   //需要多线程执行的方法
   @Override
   public void run() {
      System.out.println(Thread.currentThread().getName()+taskName);
   }
}

三. 合理配置线程数

在配置核心线程数时要区分是CPU密集还是IO密集

  • CPU密集: 任务执行需要大量运行,而没有阻塞,cpu一直在全速运行(单核cpu除外)
  • IO密集: 任务需要大量的IO,产生阻塞(请求数据库中的数据成为io操作,当请求数据库时,发生阻塞成为io阻塞)。在单线程上运行IO密集型的任务可能会发生阻塞,导致浪费大量的CPU运算能力在等待上。所以在IO密集型任务中使用多线程可以大大的加速程序运行(当有请求阻塞,其他请求使用多线程做操作),即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间
  • io密集型,容易发生io阻塞,解决阻塞线程等待浪费资源问题使用多线程技术解决,多线程请求使每个阻塞请求互不影响,提高响应速率, 为了不浪费cpu等待资源,配置最大线程数为2*cpu核数
  • cpu密集型,没有阻塞线程,用不到太多线程,配置最大线程数为cup核数
发布了48 篇原创文章 · 获赞 0 · 访问量 572

猜你喜欢

转载自blog.csdn.net/qq_29799655/article/details/105534868