Java面试准备-线程

问题链接转载  Java面试通关要点汇总集【终极版】

一、创建线程的方式及实现

方式有三种:继承Thread类创建线程类,通过Runnable接口创建线程类和通过Callable和Future创建线程

1)继承Thread类创建线程类

  1. 定义Thread类的子类,并重写该类的run()方法
  2. 创建Thread类的子类,即创建线程对象
  3. 调用线程对象的start()来启动线程
public Test extends Thread {
    int i = 0;
    public void run(){
        for(;i<100;i++){
            System.out.println(getName()+" "+i);
        }
    }
    public static void main(String[] args){
        Test t = new Test();
        t.start();
    }
}

2) 通过Runnable接口创建对象

  1. 定义Runnable接口的实现类,并重写接口的run()方法
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象
  3. 调用Thread对象的start()启动线程
public class Test implements Runnable{
    private int i;
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("hello");
    }
    public static void main(String[] args){
        new Thread(new Test()).start();
    }

}

3)通过Callable和Future创建线程

  1. 创建Callable接口的实现类,并实现call()方法,该方法有返回值
  2. 创建Callable实现类的实例,使用FutureTask类包装Callable对象
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Beetle implements Callable<Integer>{
	public static void main(String[] args){
		Beetle ctt = new Beetle();
		FutureTask<Integer> ft = new FutureTask<>(ctt);
		for(int i=0;i<100;i++){
			System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
			if(i == 20)
				new Thread(ft,"有返回值的线程").start();
		}
		try{
			System.out.println("子线程的返回值: "+ft.get());
		}catch(InterruptedException e){
			e.printStackTrace();
		}catch(ExecutionException e){
			e.printStackTrace();
		}
	}
	@Override
	public Integer call() throws Exception {
		// TODO Auto-generated method stub
		int i = 0;
		for(;i<100;i++)
			System.out.println(Thread.currentThread().getName()+" "+i);
		return i;
	}
}

三种方式的对比

  • 采用实现Runnable,Callable接口创建多线程时,线程类只是实现了Runnable或Callable接口,还可以继承其他类。这种方式下多个线程可以共享同一个target对象。但编程稍微复杂,要访问当前线程则必须使用Thread.currentThread()
  • 使用继承Thread类的方式创建多线程时,访问当前线程无需使用Thread.currentThrea(),直接使用this即可获得当前线程。但不能继承其他类
  • Callable重写call(),Runnable重写run(),Callable任务执行后可返回值,而Runnable的任务没返回值,call()方法可以抛出异常,run()方法不可以。

二、sleep() 、join()、wait()、yield()有什么区别

  • sleep():在指定时间内让当前执行的线程暂停执行,但不会释放"锁标志"。不推荐使用。sleep()使用当前线程进入阻塞状态,在指定时间内不会执行。
  • wait():在其他线程调用对象的notify或notifyAll方法前,当前线程等待。线程会释放掉它所占有的锁标志。当前线程必须拥有当前对象锁,如果没有会抛出IllegalMonitorStateException异常
  • yield():暂停当前正在执行的线程对象。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。yield()只能使同优先级或更高优先级的线程有执行的机会
  • join():等待该线程终止,等待调用join()的线程结束,再继续执行。
class MyThread1 extends Thread{
	public void run(){
		for(int i=0;i<5;i++)
			System.out.println("线程1第"+i+"次执行!");
	}
}
public class Beetle{
	public static void main(String[] args){
		Thread t1 = new MyThread1();
		t1.start();
		for(int i=0;i<10;i++){
			System.out.println("主线程第"+i+"次执行!");
			if( i > 2 ){
				try{
					t1.join();
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		}
	}
}

输出
线程1第0次执行!
线程1第1次执行!
线程1第2次执行!
主线程第0次执行!
主线程第1次执行!
主线程第2次执行!
线程1第3次执行!
线程1第4次执行!
主线程第3次执行!
主线程第4次执行!
主线程第5次执行!
主线程第6次执行!
主线程第7次执行!
主线程第8次执行!
主线程第9次执行!

三、说说 CountDownLatch 原理

CountDownLatch在多线程并发编程中充当一个计时器的功能,维护一个count变量,并且其操作都是原子操作,该类主要通过countDown()和await()两个方法实现功能。首先通过建立CountDownLatch对象,并且传入参数即count初始值。如果一个线程调用await(),那么这个线程便进入阻塞状态,并进入阻塞队列。如果一个线程调用了countDown()则会是count-1,当count为0时,阻塞队列中调用await()的线程便会逐个被唤醒,执行。

如下所示,读操作调用了await(),而写操作调用countDown(),直到count为0后读操作才开始

public class Beetle{
	private final static CountDownLatch cdl = new CountDownLatch(3);
	private final static Vector v = new Vector();
	
	public static class WriteThread extends Thread{
		private final String writeThreadName;
		private final int stopTime;
		private final String str;
		public WriteThread(String name,int time,String str){
			this.writeThreadName = name;
			this.stopTime = time;
			this.str = str;
		}
		public void run(){
			System.out.println(writeThreadName+"开始写入工作");
			try{
				Thread.sleep(stopTime);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
			cdl.countDown();
			v.add(str);
			System.out.println(writeThreadName+"写入内容为: "+str+"。写入工作结束");
		}
	}
	public static class ReadThread extends Thread{
		public void run(){
			System.out.println("读操作之前必须先进行写操作");
			try{
				cdl.await();
			}catch(InterruptedException e){
				e.printStackTrace();
			}
			for(int i=0;i<v.size();i++)
				System.out.println("读取第"+(i+1)+"条记录内容为: "+v.get(i));
			System.out.println("读操作结束");
		}
	}
	public static void main(String[] args){
		new ReadThread().start();
		new WriteThread("WriteThread1",1000,"多线程知识点").start();
		new WriteThread("WriteThread2",2000,"多线程CountDownLatch的知识点").start();
		new WriteThread("WriteThread3",3000,"多线程中控制顺序可以使用CountDownLatch").start();
	}
}

输出
读操作之前必须先进行写操作
WriteThread1开始写入工作
WriteThread2开始写入工作
WriteThread3开始写入工作
WriteThread1写入内容为: 多线程知识点。写入工作结束
WriteThread2写入内容为: 多线程CountDownLatch的知识点。写入工作结束
WriteThread3写入内容为: 多线程中控制顺序可以使用CountDownLatch。写入工作结束
读取第1条记录内容为: 多线程知识点
读取第2条记录内容为: 多线程CountDownLatch的知识点
读取第3条记录内容为: 多线程中控制顺序可以使用CountDownLatch
读操作结束

join()方法也可以实现CountDownLatch的按顺序执行,但如果使用线程池,线程池的线程不能直接使用,使用只能用CountDownLatch,而不能用join()

四、说说 CyclicBarrier 原理

CyclicBarrier也叫同步屏障,在JDK1.5被引入,可以让一组线程达到一个屏障时被阻塞,直到最后一个线程达到屏障时,之前被阻塞的线程才能继续开始执行。CyclicBarrier有两个构造函数:

  • CyclicBarrier(int parties):只需声明需要拦截的线程数
  • CyclicBarrier(int parties , Runnable barrierAction):不仅声明需要拦截的线程数,还要定义一个等待所有线程到达屏障优先执行的Runnable对象

其实现原理:在CyclicBarrier的内部定义一个Lock对象,每当一个线程调用await()方法时,将拦截的线程数减1,然后判断剩余拦截数是否等于初始值parties,如果不是,进入Lock对象的条件队列等待。如果是则执行barrierAction对象的Runnable方法,将锁的条件队列中的所有线程都放入锁等待队列中,这些线程会依次地获得锁,释放锁。

public class Beetle{
	private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
	private static final CyclicBarrier cb = new CyclicBarrier(4,new Runnable(){
		@Override
		public void run() {
			// TODO Auto-generated method stub
			System.out.println("寝室四兄弟一起出发去球场");
		}
	});
	private static class GoThread extends Thread{
		private final String name;
		public GoThread(String name){
			this.name = name;
		}
		public void run(){
			System.out.println(name+"开始从宿舍出发");
			try{
				Thread.sleep(1000);
				cb.await();
				System.out.println(name+"从楼底下出发");
				Thread.sleep(1000);
				System.out.println(name+"到达操场");
			}catch(InterruptedException e){
				e.printStackTrace();
			}catch(BrokenBarrierException e){
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args){
		String[] str = {"李明","王强","刘凯","赵杰"};
		for(int i=0;i<4;i++)
			threadPool.execute(new GoThread(str[i]));
		try{
			Thread.sleep(4000);
			System.out.println("四人一起到达球场,现在开始打球");
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}

输出
李明开始从宿舍出发
赵杰开始从宿舍出发
刘凯开始从宿舍出发
王强开始从宿舍出发
寝室四兄弟一起出发去球场
李明从楼底下出发
赵杰从楼底下出发
刘凯从楼底下出发
王强从楼底下出发
李明到达操场
刘凯到达操场
赵杰到达操场
王强到达操场
四人一起到达球场,现在开始打球

以上便是CyclicBarrier使用实例,通过await()对线程拦截,拦截数加1,当拦截数达到初始值parties后首先执行barrierAction,然后对拦截的线程队列依次获取锁解放锁。

CountDownLatch和CyclicBarrier比较

  • CountDownLatch是线程组之间的等待,即一个或多个线程等待N个线程完成某件事后再进行;而CyclicBarrier则是线程组内的等待,即每个线程相互等待,N个线程被拦截后再一起依次执行
  • CountDownLatch是减计数方式,而CyclicBarrier是加计数方式
  • CountDownLatch计数为0无法重置,而CyclicBarrier计数达到初始值后可以重置
  • CountDownLatch不可以复用,而CyclicBarrier可以复用

五、说说 Semaphore 原理

Semaphore是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证他们能够正确,合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。Semaphore是一种计数信号量,用于管理一组资源,内部是基于AQS的共享模式,相当于给线程规定一个量从而控制允许活动的线程数。

Semaphore的主要方法:

  • Semaphore(int permits):构造函数,创建具有给定许可数的计数信号量并设置为非公平信号量
  • Semaphore(int permits , boolean fair):构造函数,当fair为true时创建具有给定许可数的计数信号量并设置为公平信号量
  • void acquire():从此信号量获取一个许可的线程将一直阻塞
  • void acquire(int n):从此信号量获取给定数目许可,在提供这些许可前一直将线程阻塞
  • void release():释放一个许可,将其返回给信号量
  • void release():释放n个许可
  • int avaliablePermits():当前可用的许可数
public class Beetle{
	public static void main(String[] args){
		final Semaphore window = new Semaphore(5);   // 声明5个许可
		for(int i=0;i<8;i++){
			new Thread(){
				@Override
				public void run() {
					// TODO Auto-generated method stub
					try{
						window.acquire();
						System.out.println(Thread.currentThread().getName()+":开 始买票");
						sleep(2000);
						System.out.println(Thread.currentThread().getName()+": 购票成功");
						window.release();
					}catch(InterruptedException e){
						e.printStackTrace();
					}
				}
			}.start();
		}
	}
}

输出
Thread-0:开 始买票
Thread-3:开 始买票
Thread-4:开 始买票
Thread-2:开 始买票
Thread-1:开 始买票
Thread-2: 购票成功
Thread-0: 购票成功
Thread-5:开 始买票
Thread-4: 购票成功
Thread-3: 购票成功
Thread-7:开 始买票
Thread-6:开 始买票
Thread-1: 购票成功
Thread-5: 购票成功
Thread-7: 购票成功
Thread-6: 购票成功

六、说说 Exchanger 原理

Exchanger(交换者)是用于线程间协作的工具类,Exchanger用于进行线程间的数据交换。它提供了一个同步点,在这个同步点两个线程可以交换彼此的数据。他们通过exchange()交换数据,如果第一个线程先执行exchange(),它会一直等待第二个线程也执行exchange(),当两个线程都到达同步点时,两个线程就可以交换数据。因此使用Exchanger的重点是成对的线程使用exchange(),当一对线程达到同步点就可以进行交换数据

Exchanger类提供了两个方法:

  • String exchange(V x):用于交换,启动交换并等待另一个线程调用exchange()
  • String exchange(V x , long timeout , TimeUnit unit):用于交换,启动交换并等待另一个线程调用exchange(),并且设置了最大等待时间,当等待时间超过timeout则停止等待
public class Beetle{
	public static void main(String[] args){
		ExecutorService executor = Executors.newCachedThreadPool();
		final Exchanger exchanger = new Exchanger();
		executor.execute(new Runnable(){
			String data1 = "克拉克森,小拉林庵寺";
			@Override
			public void run() {
				// TODO Auto-generated method stub
				nbaTrade(data1,exchanger);
			}			
		});
		executor.execute(new Runnable(){
			String data1 = "格里芬";
			@Override
			public void run() {
				// TODO Auto-generated method stub
				nbaTrade(data1,exchanger);
			}			
		});
		executor.execute(new Runnable(){
			String data1 = "哈里斯";
			@Override
			public void run() {
				// TODO Auto-generated method stub
				nbaTrade(data1,exchanger);
			}			
		});
		executor.execute(new Runnable(){
			String data1 = "科比";
			@Override
			public void run() {
				// TODO Auto-generated method stub
				nbaTrade(data1,exchanger);
			}			
		});
		executor.shutdown();
	}
	private static void nbaTrade(String data1,Exchanger exchanger){
		try{
			System.out.println(Thread.currentThread().getName()+"在交易截止之前把 "+data1+" 交易出去");
			Thread.sleep((long)(Math.random()*1000));
			String data2 = (String)exchanger.exchange(data1);
			System.out.println(Thread.currentThread().getName()+"交易得到 "+data2);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}

输出结果
pool-1-thread-1在交易截止之前把 克拉克森,小拉林庵寺 交易出去
pool-1-thread-2在交易截止之前把 格里芬 交易出去
pool-1-thread-3在交易截止之前把 哈里斯 交易出去
pool-1-thread-4在交易截止之前把 科比 交易出去
pool-1-thread-1交易得到 格里芬
pool-1-thread-2交易得到 克拉克森,小拉林庵寺
pool-1-thread-3交易得到 科比
pool-1-thread-4交易得到 哈里斯

七、说说 CountDownLatch 与 CyclicBarrier 区别

  • CountDownLatch是线程组之间的等待,即一个或多个线程等待N个线程完成某件事后再进行;而CyclicBarrier则是线程组内的等待,即每个线程相互等待,N个线程被拦截后再一起依次执行
  • CountDownLatch是减计数方式,而CyclicBarrier是加计数方式
  • CountDownLatch计数为0无法重置,而CyclicBarrier计数达到初始值后可以重置
  • CountDownLatch不可以复用,而CyclicBarrier可以复用

八、ThreadLocal 原理分析

ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同的线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量

  • ThreadLocal.get():获取ThreadLocal中当前线程共享变量的值
  • ThreadLocal.set():设置ThreadLocal中当前线程共享变量的值
  • ThreadLocal.remove():移除ThreadLocal中当前线程共享变量的值
  • ThreadLocal.initialValue():ThreadLocal没有被当前线程赋值时或当前线程刚调用remove()后调用get(),返回此方法值
public class Beetle{
	private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){
		@Override
		protected Object initialValue() {
			// TODO Auto-generated method stub
			System.out.println("调用get()方法时,当前共享变量没有设置,调用initialValue获取默认值");
			return null;
		}		
	};
	public static void main(String[] args){
		new Thread(new MyIntegerTask("IntegerTask1")).start();
		new Thread(new MyStringTask("StringTask1")).start();
		new Thread(new MyIntegerTask("IntegerTask2")).start();
		new Thread(new MyStringTask("StringTask2")).start();
	}
	public static class MyIntegerTask implements Runnable{
		private String name;
		public MyIntegerTask(String name){
			this.name = name;
		}
		@Override
		public void run() {
			// TODO Auto-generated method stub
			for(int i=0;i<5;i++){
				if(null == Beetle.threadLocal.get()){
					Beetle.threadLocal.set(0);
					System.out.println("线程"+name+": 0");
				}else{
					int num = (Integer)Beetle.threadLocal.get();
					Beetle.threadLocal.set(num+1);
					System.out.println("线程"+name+": "+Beetle.threadLocal.get());
					if(i == 3)
						Beetle.threadLocal.remove();
				}
				try{
					Thread.sleep(1000);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		}	
	}
	public static class MyStringTask implements Runnable{
		private String name;
		public MyStringTask(String name){
			this.name = name;
		}
		@Override
		public void run() {
			// TODO Auto-generated method stub
			for(int i=0;i<5;i++){
				if(null == Beetle.threadLocal.get()){
					Beetle.threadLocal.set("a");
					System.out.println("线程"+name+": a");
				}else{
					String str = (String)Beetle.threadLocal.get();
					Beetle.threadLocal.set(str+"a");
					System.out.println("线程"+name+": "+Beetle.threadLocal.get());
				}
				try{
					Thread.sleep(1000);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		}
		
	}
}

输出
调用get()方法时,当前共享变量没有设置,调用initialValue获取默认值
调用get()方法时,当前共享变量没有设置,调用initialValue获取默认值
调用get()方法时,当前共享变量没有设置,调用initialValue获取默认值
线程StringTask1: a
调用get()方法时,当前共享变量没有设置,调用initialValue获取默认值
线程StringTask2: a
线程IntegerTask1: 0
线程IntegerTask2: 0
线程StringTask2: aa
线程IntegerTask1: 1
线程IntegerTask2: 1
线程StringTask1: aa
线程IntegerTask1: 2
线程StringTask1: aaa
线程IntegerTask2: 2
线程StringTask2: aaa
线程IntegerTask2: 3
线程IntegerTask1: 3
线程StringTask1: aaaa
线程StringTask2: aaaa
调用get()方法时,当前共享变量没有设置,调用initialValue获取默认值
线程IntegerTask1: 0
调用get()方法时,当前共享变量没有设置,调用initialValue获取默认值
线程IntegerTask2: 0
线程StringTask1: aaaaa
线程StringTask2: aaaaa

当调用ThreadLocal.get()时,实际上时从当前线程中获取ThreadLocalMap<ThreadLocal , Object>,然后根据当前ThreadLocal获取当前线程共享变量Object

九、讲讲线程池的实现原理

使用线程池的好处

  • 降低资源消耗:可以重复利用已创建的线程降低线程创建和销毁造成的消耗
  • 提供响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
  • 提供线程的可管理性:频繁地创建和销毁线程会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控

线程池的创建

public ThreadPoolExecutor(
      int corePoolSize,    // 线程池核心线程数量
      int maximumPoolSize, // 线程池最大线程数量
      long keepAliverTime, // 当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
      TimeUnit unit,       // 存活时间的单位
      BlockingQueue<Runnable> workQueue,  // 存放任务的队列
      RejectedExecutionHandler handler    // 超出线程范围和队列容量的任务的处理程序
)

三种类型的线程池

  • newFixedThreadPool,当线程池的线程数量达到corePoolSize后,即使线程池没有可执行任务时也不会释放线程。它的工作队列为无界队列LinkedBlockingQueue(队列容量为Integer.MAX_VALUE),因此它永远不会拒绝,即饱和策略失效。
public static ExecutorService newFixedThreadPool(int nThreads){
    return new ThreadPoolExecutor(nThreads,nThreads,0L,
TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
  • newSingleThreadExecutor,初始化的线程中只有一个线程,如果该线程异常结束后会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行。由于使用了无界队列,所以SingleThreadPool永远不会拒绝,即饱和策略失效
public static ExecutorService newSingleThreadExecutor(){
    return new FinalizableDelegateExecutorService(
        new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>())
);

}
  • newCachedThreadPool,线程池的线程数可达Integer.MAX_VALUE,内部使用SynchronousQueue作为阻塞队列。与newFixedThreadPool不同,newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源
public static ExecutorService newCachedThreadPool(){
    return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,
        TimeUnit.SECONDS,new SynchronousQueue<Runnable>()
    );
}

执行过程与前两种稍微不同:

(1)主线程调用SynchronousQueue的offer()方法放入task,倘若此时线程池中有空闲的线程尝试读取SynchronousQueue的task,即调用了SynchronousQueue的poll(),那么主线程将该task交给空闲线程执行,否则执行(2)

(2)当线程池为空或者没有空闲的线程则创建新的线程执行任务

(3)执行完成任务的线程倘若在60s内仍空闲则会终止。因此长时间空闲的CachedThreadPool不会持有任何线程资源

线程池的实现原理

提交一个任务到线程池中,线程池的处理流畅如下:

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

RejectedExecutionHandler:饱和策略

当队列和线程池都满了,说明线程池处于饱和状态,就要采用饱和策略。默认的策略是AbortPolicy,即无法处理新的任务而抛出异常。Java的四种策略:

  • AbortPolicy:直接抛出异常
  • CallerRunsPolicy:只用调用所在的线程运行任务
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
  • DiscardPolicy:不处理,丢弃掉

备注:

一般如果线程池任务队列采用LinkedBlockingQueue队列的话,那么不会拒绝任何任务(因为队列大小没有限制),这种情况下,ThreadPoolExecutor最多仅会按照最小线程数来创建线程,也就是说线程池大小被忽略了。

如果线程池任务队列采用ArrayBlockingQueue队列的话,那么ThreadPoolExecutor将会采取一个非常负责的算法,比如假定线程池的最小线程数为4,最大为8所用的ArrayBlockingQueue最大为10。随着任务到达并被放到队列中,线程池中最多运行4个线程(即最小线程数)。即使队列完全填满,也就是说有10个处于等待状态的任务,ThreadPoolExecutor也只会利用4个线程。如果队列已满,而又有新任务进来,此时才会启动一个新线程,这里不会因为队列已满而拒接该任务,相反会启动一个新线程。新线程会运行队列中的第一个任务,为新来的任务腾出空间。

十、线程池的几种方式与使用场景

  • newCachedThreadPool:当有新任务到来则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可有线程则执行,若没有则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小则该线程会被销毁。适用:执行很多短期异步的小程序或者负载较轻的服务器
  • newFixedThreadPool:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不再添加线程了;如果池中的所有线程在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)。适用:执行长期的任务,性能好很多
  • newSingleThreadExecutor:创建只有一个线程的线程池,且线程的存活时间是有限的;当该线程正繁忙时,对于新任务会进入阻塞队列(无界的阻塞队列)。适用:一个任务一个任务执行的场景
  • NewScheduledThreadPool:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构。适用:周期性执行任务的场景 
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

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

十一、线程的生命周期

Java线程具有5种基本状态:

  • 新建状态(New):当线程对象创建后,即进入新建状态,如:Thread t = new MyThread()
  • 就绪状态(Runnable):当调用线程对象的start()方法,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了start(),此线程立即执行
  • 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,即要进入运行状态前必须是就绪状态
  • 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行。此时进入阻塞状态,直到其进入就绪状态,才有机会再次被CPU调用进入运行状态。阻塞原因可以分为三种:
  1. 等待阻塞:运行状态中的线程执行wait(),使本线程进入到等待阻塞状态
  2. 同步阻塞:线程在获取synchronized同步锁失败,它会进入同步阻塞状态
  3. 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程会重新开始
  • 死亡状态(Dead):线程执行完了或者因异常退出了run(),该线程结束生命周期

注:

就绪状态转换为运行状态:当此线程得到处理器资源

运行状态转换为就绪状态:当此线程主动调用yield()或运行过程中失去处理器资源

运行状态转换为死亡状态:当此线程执行完毕或发生异常

猜你喜欢

转载自blog.csdn.net/haima95/article/details/84204513
今日推荐