JAVA线程池

一、线程池

  • 创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。
  • 从JDK1.5开始,Java API提供了Executor框架可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)
    new Thread的弊端
    每次new Thread新建对象性能差。线程缺乏统一管理,可能无限制新建线程,相互之间竞争及可能占用过多系统资源导致死机或oom(OutOfMemoryError)。

二、线程池的工作原理

  • 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则执行第二步。
  • 线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里进行等待。如果工作队列满了,则执行第三步。
  • 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
    corePoolSize 线程池的核心大小
    maximumPoolSize 线程池中允许的最大线程数
    workQueue 阻塞任务队列
// 创建线程池
ExecutorService es = Executors.newCachedThreadPool();
// 提交任务到线程池中
Future f = es.submit(() -> {
	int sum = 0;
	for (int i = 0; i <= 100; i++)
		sum += i;
	return sum;
	});
Object res = f.get();
System.out.println(res);

常见问题总结:

  • 一般创建线程对象时不建议使用extends Thread方法,因为单根继承;
  • 一般没有返回结果时建议使用Runnable接口,如果有返回值一般建议使Callable接口;
  • 如果使用线程比较频繁建议使用线程池
    daemon线程(守护线程)
  • 守护线程是为其它线程提供服务的线程。
  • 守护进程的特点是当程序中主线程结束时,守护线程会自动中止。
    如何将一个子线程设置为守护线程。
  • 在一个线程调用start启动之前,调用方法thread.setDaemon(true);就可以将thread线程设置为守护线程。
  • 守护线程一般应该是一个独立的线程,它的run()方法是一个无限循环
  • 守护线程与其它线程的区别是,如果守护线程是唯一运行着的线程,程序会自动退出。
public class T2 {
	public static void main(String[] args) {
		System.out.println("开始程序------");
		Thread t = new Thread() {
			public void run() {
				while (true) {
					System.out.println("守护线程~~~");
				}
			}
		};
		t.setDaemon(true);
		t.start();
		System.out.println("程序结束-----");
	}
}

注意:
有时虽然主线程结束了,但是守护线程还是会执行几次。是因为 JAVA中线程不能立即停止。

线程组
1、所有线程都隶属于一个线程组。那可以是一个默认线程组【main】,亦可是一个创建线程时明确指定的组。
2、在创建之初,线程被限制到一个组里,而且不能改变到一个不同的组
3、若创建多个线程而不指定一个组,它们就会与创建它的线程属于同一个组。

ThreadGroup myThreadGroup = new ThreadGroup("My Group");//创建线程组
Thread myThread = new Thread(myThreadGroup,"a thread"); //定义线程时指定对应的线程组


theGroup = myThread.getThreadGroup();//获取线程对象所属的线程组
ThreadGroup group=new ThreadGroup("yan1");
Thread t1=new Thread(group,
	()->{
		for(int i=0;i<100;i++)
			System.err.println(Thread.currentThread());
		}
	);
t1.start();

1.corePoolSize
线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。
2.maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize。
3.keepAliveTime
FutureTask future = new FutureTask(callable);newThread(future).start();线程空闲时的存活时间。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,keepAliveTime参数也会起作用,直到线程池中的线程数为

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创建线程池
1、newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收重用时则新建线程用来创建一个可以无限扩大的线程池,适用于服务器负载较轻,执行很多短期异步任务。
2、newFixedThreadPool 创建一个固定大小的定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3、newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4、newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
5、newWorkStealingPool 创建一个拥有多个任务队列的线程池,可以减少连接数

提交任务的方式
submit()和execute(),
通过submit()方法提交的任务可以返回任务执行的结果,
通过execute()方法提交的任务不能获取任务执行的结果。

关闭线程池
1、shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表
2、shutdown:当调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务。

volatile关键字
volatile是java提供的一种同步手段,只不过它是轻量级的同步,为什么这么说,因为volatile只能保证多线程的内存可见性,不能保证多线程的执行原子性。而最彻底的同步要保证有序性、可见性和原子性的synchronized任何被volatile修饰的变量,都不拷贝副本到工作内存;
任何修改都及时写在主存。因此对于volatile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改是原子的。

特性:
1、保证可见性:当写一个 volatile 变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存,使其他线程立即可见.
2、保证有序性:当变量被修饰为 volatile 时,JMM 会禁止读写该变量前后语句的大部分重排序优化,以保证变量赋值操作的顺序与程序中的执行顺序一致。
3、部分原子性:对任意单个 volatile 变量的读/写具有原子性,但类似于 volatile++ 这种复合操作不具有原子性

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值