多线程编程细节

多线程编程细节

Thread类

Thread类实现了Runnable接口,所以Thread对象也是可运行Runnable对象,同时Thread类也是线程类
构造器

Thread()//一般用于在Thread类中覆盖定义run方法,可以使用匿名内部类进行定义
		Thread(Runnable)//使用最多的情况,run方式是由Runnable参数对象提供
		Thread(String name) //自定义线程名称
		Thread(Runnable,String name)
		… …
		//常见简化写法
		Thread t = new Thread(()->{
		System.out.println(Thread.currentThread());
		});
		t.start();

常见方法:
在这里插入图片描述

Runnable接口

Runnable接口只定义了一个方法public void run(),这个方法要求实现Runnable接口的类实现,Runnable对象称为可运行对象,一个线程的运行就是执行该对象的run()方法

run()方法没有返回值void,而且不能抛出异常

class MyRunnable implements Runnable {
@Override
public void run()throws Exception {//语法报错,这里不允许抛出异常,如果其中有异常则需要使用
try.catch处理
//没有返回值,如果需要记录处理结果,需要自己编程处理
}}


//简化写法
	new Thread(()->{for(

	int i = 0;i<10;i++)
	{
		System.out.println("左手画一条龙...");
		try {
			Thread.sleep(30);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}).start();
Callable接口

继承Thread或实现Runnable接口这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。
如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。

call()方法有返回值,这个返回值可以通过泛型进行约束,允许抛出异常

class MyRunnable implements Callable<Number> {
		// <>中用于指定返回值类型,必须使用引用类型,不能使用简单类型
		public Number call() throws Exception {//允许抛出异常
		return null;
		}
		}
	// 简化写法
	new Thread(new FutureTask<>(()->

	{
		for (int i = 0; i < 10; i++) {
			System.out.println("右手画彩虹");
			Thread.sleep(30);// 因为call方法允许抛出异常
		}
		return null;
	})).start();
Future接口

Future表示一个任务的生命周期,并提供了方法来判断是否已经完成或取消以及获取任务的结果和取消任务等

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

  • cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返
    回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true
  • isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true
  • isDone方法表示任务是否已经完成,若任务完成,则返回true
  • get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回
  • get(long timeout, TimeUnit
    unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就抛出TimeoutException超时异常。
Future f = new FutureTask(() -> {
			for (int i = 0; i < 1000; i++) {
				System.out.println(Thread.currentThread() + "...start..." + i);
				Thread.sleep(10);
				System.out.println(Thread.currentThread() + "...end..." + i);
			}
			return null;
		});
		if (f instanceof Runnable)
			new Thread((Runnable) f).start();
		int counter = 0;
		while (true) {
			Thread.sleep(20);
			System.out.println("任务是否被取消:" + f.isCancelled() + "--" + counter);
			System.out.println("任务是否执行完毕:" + f.isDone());
			counter++;
			if (counter > 10)
				f.cancel(true);// 取消任务的执行
			if (counter > 12)
				break;
		}

Future f = new FutureTask(() -> {
			int res = 0;
			for (int i = 0; i < 1000; i++) {
				Thread.sleep(10);
				res += i;
			}
			return res;
		});
		if (f instanceof Runnable) {
			new Thread((Runnable) f).start();
			int counter = 0;
			long start = System.currentTimeMillis();
			// Object obj=f.get();
			Object obj = f.get(5, TimeUnit.SECONDS);// 参数1为超时时长,参数2为时长的单位,是一个枚举类型数据,超时TimeoutException
			long end = System.currentTimeMillis();
			System.out.println("get...执行时间为:" + (end - start) + "ms");
			System.out.println("线程执行结果为:" + obj);
		}
FutureTask

源代码

public class FutureTask<V> implements RunnableFuture<V>

具体使用

FutureTask<Integer> future = new FutureTask<Integer>(callable);
new Thread(future).start();

FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

FutureTask是一个可取消的异步计算,FutureTask 实现了Future的基本方法,提供start cancel 操作,可以查询计算是否已经完成,并且可以获取计算的结果。结果只可以在计算完成之后获取,get方法会阻塞当计算没有完成的时候,一旦计算已经完成, 那么计算就不能再次启动或是取消。

一个FutureTask 可以用来包装一个 Callable 或是一个Runnable对象。因为FurtureTask实现了Runnable方法,所以一个 FutureTask可以提交(submit)给一个Excutor执行(excution). 它同时实现了Callable, 所以也可以作为Future得到Callable的返回值。

ThreadPoolExecutor

先有一个大概了解,在集合框架后深入认识
ThreadPoolExecutor是线程池框架的一个核心类,线程池通过线程复用机制,并对线程进行统一管理

  • 降低系统资源消耗。通过复用已存在的线程,降低线程创建和销毁造成的消耗;
  • 提高响应速度。当有任务到达时,无需等待新线程的创建便能立即执行;
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗大量系统资源,还会降低系统的稳定性,使用线程池可以进行对线程进行统一的分配、调优和监控。

线程池的运行状态总共有5种,其值和含义分别如下:

  • RUNNING: 高3位为111,接受新任务并处理阻塞队列中的任务
  • SHUTDOWN: 高3位为000,不接受新任务但会处理阻塞队列中的任务
  • STOP:高3位为001,不会接受新任务,也不会处理阻塞队列中的任务,并且中断正在运行的任务
  • TIDYING: 高3位为010,所有任务都已终止,工作线程数量为0,线程池将转化到TIDYING状态,即将要执行
  • terminated()结束钩子方法TERMINATED: 高3位为011,terminated()方法已经执行结束

构造器中各个参数的含义:

1.corePoolSize

线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。

2.maximumPoolSize

线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize。

3.keepAliveTime

线程空闲时的存活时间。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,keepAliveTime参数也会起作用,直到线程池中的线程数为0。

4.unit

keepAliveTime参数的时间单位。

5.workQueue

任务缓存队列,用来存放等待执行的任务。如果当前线程数为corePoolSize,继续提交的任务就会被保存到任务缓存队列中,等待被执行。
一般来说,这里的BlockingQueue有以下三种选择:

  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。因此,如果线程池中始终没有空闲线程(任务提交的平均速度快于被处理的速度),可能出现无限制的线程增长。
  • LinkedBlockingQueue:基于链表结构的阻塞队列,如果不设置初始化容量,其容量Integer.MAX_VALUE,即为无界队列。因此,如果线程池中线程数达到了corePoolSize,且始终没有空闲线程(任务提交的平均速度快于被处理的速度),任务缓存队列可能出现无限制的增长。
  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务。
6.threadFactory

线程工厂,创建新线程时使用的线程工厂。

7.handler

任务拒绝策略,当阻塞队列满了,且线程池中的线程数达maximumPoolSize,如果继续提交任务,就会采取任务拒绝策略处理该任务,线程池提供了4种任务拒绝策略:

  • AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,默认策略;
  • CallerRunsPolicy:由调用execute方法的线程执行该任务;
  • DiscardPolicy:丢弃任务,但是不抛出异常;
  • DiscardOldestPolicy:丢弃阻塞队列最前面的任务,然后重新尝试执行任务(重复此过程)。

当然也可以根据应用场景实现RejectedExecutionHandler接口自定义饱和策略,如记录日志或持久化存储不能处理的任务。

Executors创建线程池

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收重用时则新建线程

  • 用来创建一个可以无限扩大的线程池,适用于服务器负载较轻,执行很多短期异步任务

newFixedThreadPool 创建一个固定大小的定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

  • 因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于可以预测线程数量的业务中,或者服务器负载较重,对当前线程数量进行限制

newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行

  • 可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements
ScheduledExecutorService

newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

  • 适用于需要保证顺序执行各个任务,并且在任意时间点,不会有多个线程是活动的场景

newWorkStealingPool:创建一个拥有多个任务队列的线程池,可以减少连接数

  • 创建当前可用cpu数量的线程来并行执行,适用于大耗时的操作,可以并行来执行
提交任务的方式

线程池框架提供了两种方式提交任务,submit()和execute(),通过submit()方法提交的任务可以返回任务执行的结果,通过execute()方法提交的任务不能获取任务执行的结果。

关闭线程池

shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表

shutdown:当调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务

ExecutorService es = Executors.newFixedThreadPool(2);
		es.submit(() -> {
			for (int i = 0; i < 10; i++) {
				System.out.println("Hello " + i);
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		System.out.println("main......");
		ExecutorService es = Executors.newFixedThreadPool(2);
		Future f = es.submit(() -> {
			for (int i = 0; i < 10; i++) {
				System.out.println("Hello " + i);
				Thread.sleep(200);
			}
			return 100;
		});
		Object obj = f.get(); // 阻塞当前main线程
		System.out.println("main......");

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值