面试总结-线程池

  • 1.线程池类型及应用
  • 1.1newSingleThreadExecutor单个线程的线程池
  • 1.2newFixedThreadExecutor(n)固定数量的线程池
  • 1.3newCacheThreadExecutor(推荐使用)可缓存线程池
  • 1.4newScheduleThreadExecutor大小无限制的线程池
  • 2.线程池任务执行流程:
  • 3.一个线程有三个方法,怎么保证三个是顺序执行
  • 第一种方式:
  • 第二种方式:
  • 第三种方式:
  • 第四种方式:

1.线程池类型及应用

线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。 Executors可以说是一个工厂类,可以用它来创建不同的线程池。

常见的几个线程池

1.1newSingleThreadExecutor单个线程的线程池

ExecutorService sPool = Executors.newSingleThreadExecutor();

即线程池中每次只有一个线程工作,单线程串行执行任务。
底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue() 无解阻塞队列
通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:一个任务一个任务执行的场景

1.2newFixedThreadExecutor(n)固定数量的线程池

ExecutorService fPool = Executors.newFixedThreadPool(3);

每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行。
底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue() 无解阻塞队列
通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:执行长期的任务,性能好很多

1.3newCacheThreadExecutor(推荐使用)可缓存线程池

ExecutorService cPool = Executors.newCachedThreadPool();

当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器

1.4newScheduleThreadExecutor大小无限制的线程池

ExecutorService executorService= Executors.newScheduledThreadPool(10);

支持定时和周期性的执行线程。
底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
适用:周期性执行任务的场景

2.线程池任务执行流程:

1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

3.一个线程有三个方法,怎么保证三个是顺序执行

第一种方式:

顺序在线程中创建实例(最容易想到的办法)。

public class TestTwo {
        static TestTwo t=new TestTwo();
        class T1 extends Thread{
            @Override
            public void run() {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //T1线程中要处理的东西
                System.out.println("T1线程执行")
            }
        }

        class T2 extends Thread{
            @Override
            public void run() {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //T2线程中要处理的东西
                System.out.println("T2线程执行");
                t.new T1().start();
            }
        }

        class T3 extends Thread{
            @Override
            public void run() {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //T3线程中要处理的东西
                System.out.println("T3线程执行");
                t.new T2().start();
            }
        }
        
        public static void main(String[] args) {
            t.new T3().start();       
            //打印结果如下:             
            //T3线程执行        
            //T2线程执行
            //T1线程执行
        }
}

第二种方式:

看到有人说运用单个线程池(SingleThreadExecutor)来实现,确切的说这里不太符合,从打印结果看出,其实是在一个线程里,执行了三个任务。

Thread t1 = new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " run 1");
                }
            }, "T1");
            
             Thread t2 = new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " run 2");
                }
            }, "T2");
            
             Thread t3 = new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " run 3");
                }
            }, "T3");

            //三个线程顺序执行 第一种方案,单个线程池 顺序放入执行队列中
            ExecutorService executor = Executors.newSingleThreadExecutor();

            executor.submit(t3);
            executor.submit(t2);
            executor.submit(t1);
            executor.shutdown();

第三种方式:

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

package com.gs.demo1;
 
public class ThreadTest1 {
 
	public static void main(String[] args) {
		Thread t1 = new Thread(new Work(null),"线程t1");
		Thread t2 = new Thread(new Work(t1),"线程t2");
		Thread t3 = new Thread(new Work(t2),"线程t3");
		
		t1.start();
		t2.start();
		t3.start();			
	}
	
	static class Work implements Runnable{
		private Thread beforeThread;
		public Work(Thread beforeThread) {
			this.beforeThread = beforeThread;
		}
		@Override
		public void run() {
			if(beforeThread!=null) {
				try {
					//某线程调用该方法,会让其他线程处于等待状态,让其运行完毕,再执行其他线程.   
					beforeThread.join();
					System.out.println("Thread start:"+Thread.currentThread().getName());
				}catch(Exception e){
					e.printStackTrace();
				}
			}else {
				System.out.println("Thread start:"+Thread.currentThread().getName());
			}
			
		}
	}
}

第四种方式:

使用CountDownLatch
CountDownLatch(闭锁)是一个很有用的工具类,利用它我们可以拦截一个或多个线程使其在某个条件成熟后再执行。它的内部提供了一个计数器,在构造闭锁时必须指定计数器的初始值,且计数器的初始值必须大于0。另外它还提供了一个countDown方法来操作计数器的值,每调用一次countDown方法计数器都会减1,直到计数器的值减为0时就代表条件已成熟,所有因调用await方法而阻塞的线程都会被唤醒。这就是CountDownLatch的内部机制,看起来很简单,无非就是阻塞一部分线程让其在达到某个条件之后再执行。

package com.gs.demo2;
 
import java.util.concurrent.CountDownLatch;
 
public class ThreadTest2 {
	public static void main(String[] args) {
		CountDownLatch c1 = new CountDownLatch(0);//计数器为0
		CountDownLatch c2 = new CountDownLatch(1);//计数器为1
		CountDownLatch c3 = new CountDownLatch(1);//计数器为1
		
		Thread t1 = new Thread(new Work(c1, c2),"线程t1");
	    //c1为0,t1线程可以执行。t1线程的计数器 c2 减1
	  
	    Thread t2 = new Thread(new Work(c2, c3),"线程t2");
	    //t1的计数器c2为0时,t2才能执行。t2的计数器c3减1
	  
	    Thread t3 = new Thread(new Work(c3, c3),"线程t3");
	    //t3的计数器c3为0时,t3才能执行
	  
	    t1.start();
	    t2.start();
	    t3.start();
	}
	//定义Work线程类,需要传入开始和结束的CountDownLatch参数
	static class Work implements Runnable {
		private CountDownLatch c1;
		private CountDownLatch c2;
		public Work(CountDownLatch c1, CountDownLatch c2) {
			super();
			this.c1 = c1;
			this.c2 = c2;
		}
 
		@Override
		public void run() {
			 try {
				//当某个线程调用CountDownLatch对象的await方法时,将会阻塞,直到计数器的值变成0才放行。
				c1.await();
				System.out.println("thread start:" + Thread.currentThread().getName());
		        c2.countDown(); //本线程计数器减 1
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
	        
		}
	}
}