一、为什么要使用多线程:
现在的电脑都是多核,即可以同时处理多个任务,开启多进程,所以使用多线程,可以有效的利用cpu资源,提升工作效率。
二、线程的生命周期:
三、线程池与多线程区别:
多线程:通过new Thread 可以创建多个线程,但是每次new对象性能较差,线程缺乏统一的管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom;也无法执行特殊的功能,如定时,周期执行,线程的中断等操作。
线程池:可重用线程,减少对象创建和销毁等开销,性能较好,同时控制有效的最大线程并发线程数,避免线程阻塞等,实现复杂任务。
四、常用多线程使用:
java.util.concurrent的Executors中有常用的创建线程池的方法。常用四种方法总结如下:
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
源码如图:
参数:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory and rejected execution handler.
* It may be more convenient to use one of the {@link Executors} factory
* methods instead of this general purpose constructor.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
构造方法的参数说明:
corePoolSize : 线程池的核心线程数。该参数并不是初始化时候写死了,线程池对象构造完成以后也能通过它提供的一些方法动态修改,ThreadPoolExecutor.setCorePoolSize。
maximumPoolSize : 线程池的最大线程数,只有当任务队列满了才会创建新的线程。该参数并不是初始化时候写死了,线程池对象构造完成以后也能通过它提供的一些方法动态修改,ThreadPoolExecutor.setMaximumPoolSize。
keepAliveTime : 线程池所维护的线程的活动时间。如果超过了该时间范围,而线程还是空闲的,那么该线程将会被回收。不再由线程池所维护。以下是官方的一些说明:
如果当前线程池数大于核心线程池数,且这些线程是空闲的超过所设定的存活时间keepAliveTime,那么这些多出来的线程则会被终止,可以降低线程资源消耗。通过设置尽可能大的过期时间来阻止空闲线程被销毁,可手动调用setKeepAliveTime来设置。默认情况下,该策略只能适用于大于核心线程数的线程,但是可以通过设置ThreadPoolExecutor.allowCoreThreadTimeOut(boolean),则该策略也适用于核心线程数。
unit : 和上面keepAliveTime所对应,表示活动时间的时间单位。如 毫秒、秒、分..
workQueue : 初始化一组任务,但是该组任务并不会马上执行,需要手动调用prestartCoreThread或者prestartAllCoreThreads来预先启动线程才会执行这些初始化的任务。否则只有当你提交第一个任务时候他才会执行,且可能是单线程执行(取决于你提交几次任务)..
threadFactory : 传入一个线程工厂。通过该线程工厂可以创建线程。它创建的所有线程都是通过同样的ThreadGroup和同样的NORM_PRIORITY和non-daemon状态。通过提供的不同ThreadFactory,可以掌握修改线程名字,线程组,优先级,守护状态等。如果线程池调用线程工厂创建一个线程失败时,则返回一个null。且executor会继续,但是可能不会执行任何任务。
如果我们自己重写封装了一遍线程工厂,还有个好处就是可以通过该线程工厂实例维护所有由它创建的线程。
开发者使用钩子方法如:ThreadPoolExecutor.beforeExecute and ThreadPoolExecutor.afterExecute。这些方法可在任务执行前后被执行,或者线程终止时执行。场景:重新初始化ThreadLocal。收集统计信息,添加日志记录,源码如下:
protected void beforeExecute(Thread t, Runnable r) { }
/* <pre> {@code
* class PausableThreadPoolExecutor extends ThreadPoolExecutor {
* private boolean isPaused;
* private ReentrantLock pauseLock = new ReentrantLock();
* private Condition unpaused = pauseLock.newCondition();
*
* public PausableThreadPoolExecutor(...) { super(...); }
*
* protected void beforeExecute(Thread t, Runnable r) {
* super.beforeExecute(t, r);
* pauseLock.lock();
* try {
* while (isPaused) unpaused.await();
* } catch (InterruptedException ie) {
* t.interrupt();
* } finally {
* pauseLock.unlock();
* }
* }
*
* public void pause() {
* pauseLock.lock();
* try {
* isPaused = true;
* } finally {
* pauseLock.unlock();
* }
* }
*
* public void resume() {
* pauseLock.lock();
* try {
* isPaused = false;
* unpaused.signalAll();
* } finally {
* pauseLock.unlock();
* }
* }
* }}</pre>
*/
protected void afterExecute(Runnable r, Throwable t) { }
/* <pre> {@code
* class ExtendedExecutor extends ThreadPoolExecutor {
* // ...
* protected void afterExecute(Runnable r, Throwable t) {
* super.afterExecute(r, t);
* if (t == null && r instanceof Future<?>) {
* try {
* Object result = ((Future<?>) r).get();
* } catch (CancellationException ce) {
* t = ce;
* } catch (ExecutionException ee) {
* t = ee.getCause();
* } catch (InterruptedException ie) {
* Thread.currentThread().interrupt(); // ignore/reset
* }
* }
* if (t != null)
* System.out.println(t);
* }
* }}</pre>
*/
protected void terminated() { }
工具类如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolUtil
{
private static volatile ThreadPoolUtil instance;
private static ExecutorService threadPool;
public static ThreadPoolUtil getInstance()
{
if (instance == null) {
synchronized (ThreadPoolUtil.class)
{
instance = new ThreadPoolUtil();
threadPool = Executors.newCachedThreadPool();
}
}
return instance;
}
public void excute(Runnable runnable)
{
threadPool.execute(runnable);
}
public void shutdown()
{
threadPool.shutdown();
}
public boolean isActive()
{
if (threadPool.isTerminated()) {
return false;
}
return true;
}
/*//方法调用
ThreadPoolUtil.getInstance().excute(new Runnable()
{
public void run()
{
//调用执行具体业务
}
});
*/
}
newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool : 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor : 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newSingleThreadScheduledExecutor: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
创建方式:
(1)Executors.newSingleThreadScheduledExecutor() ;
(2)Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory) ;//threadFactory创建线程的工厂
其余方法类似。
五、线程池执行过程
我们通过submit或execute提交到线程池中,线程池内部会调用内部类Worker,其实现了Runnable,封装了线程,它们是通过线程工厂创建线程时候把this,即Worker传入到Thread的构造参数,从代码上是线程池维护了一组Worker,通过Worker.thread.start()后,那么Worker所封装的线程就会运行Worker自身的run实现。最后在run实现里持续的从workQueue中获取任务来执行,由于workQueue是阻塞队列,所以如果队列中没有任务,那么该线程则会被挂起等待唤醒(有任务过来),Worker类里封装了Thread和任务Runnable,还有completedTasks。可以注意到创建一个Thread时候把this传入,这样的话如果我调用Worker.thread.start()就相当于该线程会执行Worker里的run方法了。completedTasks是用来记录该线程完成了多少个任务(非整个线程池)。
注意该Worker继承了AQS同步基础器,它主要实现了互斥锁的功能,但是这个互斥锁和ReentrantLock有点不同,该实现是不允许线程重入获取锁的。下面说说为什么要实现锁功能和非重入:
1.lock方法主要用在标明当前线程正在执行任务中,而private interruptIdleWorkers 方法需要使用tryLock来判断当前线程是否正在执行任务,如果非执行任务状态则表明可能是正在获取任务,那么该线程属于空闲状态,可以被中断。
2.看回答1可以知道这通过ReentrantLock也能实现,但是如果我们在提交一个任务给线程池(实现一个Runnable),如果该
任务里面调用了和interruptIdleWorkers相关的,需要中断当前可获取锁(代表空闲)的线程。如果我们不使用非重入锁,这个任务线程就会给中断,从而导致一些奇怪的问题。
源码如下:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}