java-多线程-线程池

  • ThreadPoolExecutor 类
//java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类
//ThreadPoolExecutor继承了AbstractExecutorService    
//AbstractExecutorService是一个抽象类,它实现了ExecutorService接口
//ExecutorService又是继承了Executor接口
//Executor是一个顶层接口,里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
}
 
// 参数说明:
1、corePoolSize:核心池的大小
2、maximumPoolSize:线程池最大线程数,表示在线程池中最多能创建多少个线程;
3、keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止
4、unit:参数keepAliveTime的时间单位
5、workQueue:一个阻塞队列,用来存储等待执行的任务
  • 创建线程池
  1. ThreadPoolExecutor:直接通过ThreadPoolExecutor的构造方法创建
  2. Executors:使用Executors工具类创建

注:《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

// 1
//直接用 ThreadPoolExecutor 类创建线程池
//注:不提倡直接使用ThreadPoolExecutor 创建线程池,因为手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5, 
                10, 
                200, 
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5));
 
// 2
//推荐使用Executors类中提供的几个静态方法来创建线程池,例如:
ExecutorService executor = Executors.newFixedThreadPool(5);
 
//其实他们的底层还是使用ThreadPoolExecutor 类来创建线程池,只不过是使用了默认参数
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}

1、newFixedThreadPool(): 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。但线程空闲时不会被释放,一致占用系统内存资源
2、newCachedThreadPool():创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
3、newScheduledThreadPool(): 创建一个定长任务线程池,支持定时及周期性任务执行。
4、newSingleThreadExecutor(): 创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  • 线程池使用
public static void main(String[] args) {
	ThreadLocal<String> thradLocalName = new ThreadLocal<>();
	thradLocalName.set("小明");


	// 创建一个线程池
	ExecutorService executor = Executors.newFixedThreadPool(5);
	// 执行任务
	executor.execute(new Runnable() {
		@Override
		public void run() {
			System.out.println("...");
		}
	});

}
  • 线程池5种运行状态

  1. RUNNING:接收新任务,处理队列中的任务
  2. SHUTDOWN:不接收新任务,但能处理队列中的任务
  3. STOP:不接收新任务,不处理队列中的任务,并且会中断正在处理的任务
  4. TIDYING:所有的任务都已终止,正在执行的任务数量为0
  5. TERMINATED:线程池彻底终止
  • 线程池原理(为什么能维持线程不释放,随时运行各种任务? )
// 执行过程
public void execute(Runnable command) {
	// 如果任务为空,则抛出异常
	if (command == null)
		throw new NullPointerException();
	// ctl:原子类变量,由线程池的运行状态和池内线程数量做运算得到
	// c:原子类变量的值
	int c = ctl.get();
	// 获取池内线程数量,如果其小于池的核心容量
	if (workerCountOf(c) < corePoolSize) {
        // 添加一个核心线程来执行它的第一个任务,如果成功则返回
		if (addWorker(command, true))
			return;
        // 没有成功,继续往下走
		c = ctl.get();
	}
	// 如果线程池是运行状态,且成功把任务添加到队列
	if (isRunning(c) && workQueue.offer(command)) {
		int recheck = ctl.get();
		// 二次检查,如果线程池不是运行状态或者清除了新增的任务,抛出拒绝添加任务异常
		if (! isRunning(recheck) && remove(command))
			reject(command);
		// 添加一个非核心线程,让它去队列中取任务执行
		else if (workerCountOf(recheck) == 0)
			addWorker(null, false);
	}
	// 添加一个非核心线程来执行任务,如果失败,抛出拒绝添加任务异常
	else if (!addWorker(command, false))
		reject(command);
}
  1. 添加工作线程
  2. 启动工作线程 
// https://blog.csdn.net/seasonLai/article/details/82624236
// addWorker()方法
// firstTask:第一个任务(根据新创建的线程而言)
// core:true表示创建核心线程,false表示创建非核心线程
private boolean addWorker(Runnable firstTask, boolean core) {
	// 循环标记
	retry:
	// 忙循环
	for (;;) {
		int c = ctl.get();
		// 线程池运行状态
		int rs = runStateOf(c);

		// Check if queue empty only if necessary.
		// 表示不允许创建新线程,返回false
		if (rs >= SHUTDOWN &&
			! (rs == SHUTDOWN &&
			   firstTask == null &&
			   ! workQueue.isEmpty()))
			return false;

		for (;;) {
			// 正在工作线程的数量
			int wc = workerCountOf(c);
			// 正在工作线程的数量达到限制值,返回false
			if (wc >= CAPACITY ||
				wc >= (core ? corePoolSize : maximumPoolSize))
				return false;
			// 调用CAS将工作线程数量+1,跳出循环
			if (compareAndIncrementWorkerCount(c))
				break retry;
			// 重新获取线程池运行状态,如果状态改变重新进入循环
			c = ctl.get();  // Re-read ctl
			if (runStateOf(c) != rs)
				continue retry;
			// else CAS failed due to workerCount change; retry inner loop
		}
	}

	boolean workerStarted = false;
	boolean workerAdded = false;
	Worker w = null;
	try {
		// 工作线程
		w = new Worker(firstTask);
		// 
		final Thread t = w.thread;
		if (t != null) {
			final ReentrantLock mainLock = this.mainLock;
			mainLock.lock();
			try {
				// Recheck while holding lock.
				// Back out on ThreadFactory failure or if
				// shut down before lock acquired.
				// 线程池运行状态
				int rs = runStateOf(ctl.get());
				/ 线程池是运行状态,或者
				if (rs < SHUTDOWN || SHUTDOWN 状态且任务为空
					(rs == SHUTDOWN && firstTask == null)) {
					// 如果执行线程处于就绪状态,抛出异常(因为这个时候我们还没有启动线程)
					if (t.isAlive()) // precheck that t is startable
						throw new IllegalThreadStateException();
					// 添加到工作线程集
					workers.add(w);
					// 工作线程数量
					int s = workers.size();
					// 记录线程池的线程峰值
					if (s > largestPoolSize)
						largestPoolSize = s;
					// 表示成功添加工作线程
					workerAdded = true;
				}
			} finally {
				mainLock.unlock();
			}
			// 启动工作线程
			if (workerAdded) {
				t.start();
				workerStarted = true;
			}
		}
	} finally {
		if (! workerStarted)
			addWorkerFailed(w);
	}
	return workerStarted;
}
  1. runWorker()方法:执行工作线程
  2. 获取任务、执行任务
  3. 如果有中断信号,则中断线程
// 启动工作线程后执行的方法
final void runWorker(Worker w) {
	Thread wt = Thread.currentThread();
	// 任务
	Runnable task = w.firstTask;
	w.firstTask = null;
	w.unlock(); // allow interrupts
	boolean completedAbruptly = true;
	try {
		// 如果任务不为空,获取从队列中取到了任务
		while (task != null || (task = getTask()) != null) {
			w.lock();
			// If pool is stopping, ensure thread is interrupted;
			// if not, ensure thread is not interrupted.  This
			// requires a recheck in second case to deal with
			// shutdownNow race while clearing interrupt
			if ((runStateAtLeast(ctl.get(), STOP) ||
				 (Thread.interrupted() &&
				  runStateAtLeast(ctl.get(), STOP))) &&
				!wt.isInterrupted())
				wt.interrupt();
			try {
				beforeExecute(wt, task);
				Throwable thrown = null;
				try {
					task.run();
				} catch (RuntimeException x) {
					thrown = x; throw x;
				} catch (Error x) {
					thrown = x; throw x;
				} catch (Throwable x) {
					thrown = x; throw new Error(x);
				} finally {
					afterExecute(task, thrown);
				}
			} finally {
				task = null;
				w.completedTasks++;
				w.unlock();
			}
		}
		completedAbruptly = false;
	} finally {
		processWorkerExit(w, completedAbruptly);
	}
}
  1.  getTask()方法:去队列拿任务
  2. 如果拿到任务则返回,如果拿不到任务则一直在这里循环,直到拿到任务为止
// getTask()方法
private Runnable getTask() {
	boolean timedOut = false; // Did the last poll() time out?

	for (;;) {
		int c = ctl.get();
		int rs = runStateOf(c);

		// Check if queue empty only if necessary.
		if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
			decrementWorkerCount();
			return null;
		}

		int wc = workerCountOf(c);

		// Are workers subject to culling?
		boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

		if ((wc > maximumPoolSize || (timed && timedOut))
			&& (wc > 1 || workQueue.isEmpty())) {
			if (compareAndDecrementWorkerCount(c))
				return null;
			continue;
		}

		try {
			Runnable r = timed ?
				workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
				workQueue.take();
			if (r != null)
				return r;
			timedOut = true;
		} catch (InterruptedException retry) {
			timedOut = false;
		}
	}
}
  •  线程池优化 
1、参考:https://www.cnblogs.com/dolphin0520/p/3932921.html
一般需要根据任务的类型来配置线程池大小:
如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1
如果是IO密集型任务,参考值可以设置为2*NCPU
  当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。
 
 
2、参考:https://blog.csdn.net/wangdongli_1993/article/details/81268216
有经验公式:Nthread=Ncpu*Ucpu*(1+W/C)
W/C:等待时间与计算时间的比值
Ncpu:CPU数量
Ucpu:目标cpu的使用率
 
Java中下面方法获取CPU数目:
int Ncpus=Runtime.getRuntime().availableProcessors();
System.out.println(Ncpus);
  • 线程池的优点
  1. 减少在创建和销毁线程上所花的时间以及系统资源的开销 
  2. 如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存
  • 线程组
  • 作用:批量的管理线程或者线程组对象
  • 用户创建的所有线程都属于指定线程组,如果没有显示指定属于哪个线程组,那么该线程就属于默认线程组(即main线程组)
  • 虽然线程组看上去很有用处,实际上现在的程序开发中已经不推荐使用它了,主要有两个原因:
  1. 线程组ThreadGroup对象中比较有用的方法是stop、resume、suspend等方法,由于这几个方法会导致线程的安全问题(主要是死锁问题),已经被官方废弃掉了,所以线程组本身的应用价值就大打折扣了。
  2. 线程组ThreadGroup不是线程安全的,这在使用过程中获取的信息并不全是及时有效的,这就降低了它的统计使用价值。
  • 虽然线程组现在已经不被推荐使用了,但是它在线程的异常处理方面还是做出了一定的贡献。当线程运行过程中出现异常情况时,在某些情况下JVM会把线程的控制权交到线程关联的线程组对象上来进行处理
//创建一个线程组对象
ThreadGroup threadGroup1 = new ThreadGroup("group1");

//获取当前线程所在线程组的名称
Thread.currentThread().getThreadGroup().getName()

//将一个线程放入线程组
Thread a = new Thread(group,new MyThread());

//复制线程组,将线程组group1中的线程复制到threads
Thread[] threads=new Thread[group1.activeCount()];
group1.enumerate(threads);

//线程组统一异常处理
//ThreadGroup内定义了一个方法:void uncaughtException(Thread t,Throwable e),该方法可以处理该线程组内的任意线程所抛出的未处理异常。 
        CountDownLatch seaphere=new CountDownLatch(1);
        ThreadGroup group1=new ThreadGroup("group1"){
            @Override
            //重写自己的异常处理
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("线程名称:"+t.getName());
                seaphere.countDown();
            }
        };

        Thread t1=new Thread(group1,new MyThread());
        t1.start();
        seaphere.await();


//常用操作方法
int activeCount():获取线程组中活动线程的数量
interrupt():中断线程组中所有线程
isDaemon():是否为后台线程组
setDaemon(boolean daemon):设置为后台线程组
setMaxPriority(int pri):设置线程组的最高优先级
  • 线程组和线程池的区别
  1. 线程组是为了方便线程的管理
  2. 线程池是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。

 

参考:

  1. Java并发编程:线程池的使用    https://www.cnblogs.com/dolphin0520/p/3932921.html
  2. 线程池为什么能维持线程不释放,随时运行各种任务    https://blog.csdn.net/cjh94520/article/details/70545202
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值