线程、多线程、线程池总结

 

先看几个概念: 线程:进程中负责程序执行的执行单元。一个进程中至少有一个线程。java

多线程:解决多任务同时执行的需求,合理使用CPU资源。多线程的运行是根据CPU切换完成,如何切换由CPU决定,所以多线程运行具备不肯定性。git

线程池:基本思想仍是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样能够避免反复建立线程对象所带来的性能开销,节省了系统的资源。程序员

若是对线程概念不清晰的话,不妨先看看我是一个线程这篇文章,拟人化的故事阐述线程的工做原理。github

● 线程

建立线程的两种方式:面试

1、继承Thread类,扩展线程。

class DemoThread extends Thread { @Override public void run() { super.run(); // Perform time-consuming operation... } } DemoThread t = new DemoThread(); t.start();
  • 继承Thread类,覆盖run()方法。
  • 建立线程对象并用start()方法启动线程。

面试题

  • 1)线程和进程有什么区别?

一个进程是一个独立(self contained)的运行环境,它能够被看做一个程序或者一个应用。而线程是在进程中执行的一个任务。线程是进程的子集,一个进程能够有不少线程,每条线程并行执行不一样的任务。不一样的进程使用不一样的内存空间,而全部的线程共享一片相同的内存空间。别把它和栈内存搞混,每一个线程都拥有单独的栈内存用来存储本地数据。算法

  • 2)如何在Java中实现线程?

建立线程有两种方式:
1、继承 Thread 类,扩展线程。
2、实现 Runnable 接口。
缓存

  • 3)Thread 类中的 start() 和 run() 方法有什么区别?

调用 start() 方法才会启动新线程;若是直接调用 Thread 的 run() 方法,它的行为就会和普通的方法同样;为了在新的线程中执行咱们的代码,必须使用 Thread.start() 方法。安全

扩展

Android 系统接口 HandlerThread 继承了 Thread,它是一个可使用 Handler 的 Thread,一个具备消息循环的线程。run()方法中经过 Looper.prepare() 来建立消息队列,经过 Looper.loop() 来开启消息循环。能够在 run() 方法中执行耗时的任务,而 HandlerThread 内部建立了消息队列外界须要经过 Handler 的方式来通知 HandlerThread 执行一个具体任务;HandlerThread 的 run() 方法是一个无限的循环,能够经过它的 quite() 或 quitSafely() 方法来终止线程的执行;多线程

2、实现Runnable接口。

public class DemoActivity extends BaseActivity implements Runnable { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Thread t = new Thread(this); t.start(); } @Override public void run() { } }

面试题

  • 1)用 Runnable 仍是 Thread ?

咱们都知道能够经过继承 Thread 类或者调用 Runnable 接口来实现线程,问题是,建立线程哪一种方式更好呢?什么状况下使用它?这个问题很容易回答,若是你知道Java不支持类的多重继承,但容许你调用多个接口。因此若是你要继承其余类,固然是调用Runnable接口更好了。并发

  • 2)Runnable 和 Callable 有什么不一样?

Runnable 和 Callable 都表明那些要在不一样的线程中执行的任务。Runnable 从 JDK1.0 开始就有了,Callable 是在 JDK1.5 增长的。它们的主要区别是 Callable 的 call() 方法能够返回值和抛出异常,而 Runnable 的 run() 方法没有这些功能。Callable 能够返回装载有计算结果的 Future 对象。

⚠注意:这面第二个面试题主要是为了引出下面的扩展,原谅我这样为难人的出场。

扩展

先看一下 Runnable 和 Callable 的源码

public interface Runnable { public void run(); } public interface Callable<V> { V call() throws Exception; }

能够得出:

1)Callable 接口下的方法是 call(),Runnable 接口的方法是 run()。
2)Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的。
3)call() 方法能够抛出异常,run()方法不能够的。
4)运行 Callable 任务能够拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。经过 Future 对象能够了解任务执行状况,可取消任务的执行,还可获取执行结果。

可是,可是,凡事都有可是嘛...

单独使用 Callable,没法在新线程中(new Thread(Runnable r))使用,Thread 类只支持 Runnable。不过 Callable 可使用 ExecutorService (又抛出一个概念,这个概念将在下面的线程池中说明)。

上面又提到了 Future,看一下 Future 接口:

public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }

Future 定义了5个方法:

1)boolean cancel(boolean mayInterruptIfRunning):试图取消对此任务的执行。若是任务已完成、或已取消,或者因为某些其余缘由而没法取消,则此尝试将失败。当调用 cancel() 时,若是调用成功,而此任务还没有启动,则此任务将永不运行。若是任务已经启动,则 mayInterruptIfRunning 参数肯定是否应该以试图中止任务的方式来中断执行此任务的线程。此方法返回后,对 isDone() 的后续调用将始终返回 true。若是此方法返回 true,则对 isCancelled() 的后续调用将始终返回 true。
2)boolean isCancelled():若是在任务正常完成前将其取消,则返回 true。
3)boolean isDone():若是任务已完成,则返回 true。 可能因为正常终止、异常或取消而完成,在全部这些状况中,此方法都将返回 true。
4)V get()throws InterruptedException,ExecutionException:若有必要,等待计算完成,而后获取其结果。
5)V get(long timeout,TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException: 若有必要,最多等待为使计算完成所给定的时间以后,获取其结果(若是结果可用)。

看来 Future 接口也不能用在线程中,那怎么用,谁实现了 Future 接口呢?答:FutureTask。

public class FutureTask<V> implements RunnableFuture<V> { ... } public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); }

FutureTask 实现了 Runnable 和 Future,因此兼顾二者优势,既能够在 Thread 中使用,又能够在 ExecutorService 中使用。

看完上面啰哩啰嗦的介绍后,下面咱们具体看一下具体使用的示例:

Callable<String> callable = new Callable<String>() { @Override public String call() throws Exception { return "我的博客:sunfusheng.com"; } }; FutureTask<String> task = new FutureTask<String>(callable); Thread t = new Thread(task); t.start(); // 启动线程 task.cancel(true); // 取消线程

使用 FutureTask 的好处是 FutureTask 是为了弥补 Thread 的不足而设计的,它可让程序员准确地知道线程何时执行完成并得到到线程执行完成后返回的结果。FutureTask 是一种能够取消的异步的计算任务,它的计算是经过 Callable 实现的,它等价于能够携带结果的 Runnable,而且有三个状态:等待、运行和完成。完成包括全部计算以任意的方式结束,包括正常结束、取消和异常。

查看具体操做代码请参考 《我的学习项目DroidStudy》,感谢您的关注。

● 多线程

多线程的概念很好理解就是多条线程同时存在,但要用好多线程确不容易,涉及到多线程间通讯,多线程共用一个资源等诸多问题。

使用多线程的优缺点:
优势:
1)适当的提升程序的执行效率(多个线程同时执行)。
2)适当的提升了资源利用率(CPU、内存等)。
缺点:
1)占用必定的内存空间。
2)线程越多CPU的调度开销越大。
3)程序的复杂度会上升。

对于多线程的示例代码感兴趣的能够本身写Demo啦,去运行体会,下面我主要列出一些多线程的技术点。

synchronized

同步块你们都比较熟悉,经过 synchronized 关键字来实现;全部加上 synchronized 的方法和块语句,在多线程访问的时候,同一时刻只能有一个线程可以访问。

wait()、notify()、notifyAll()

这三个方法是 java.lang.Object 的 final native 方法,任何继承 java.lang.Object 的类都有这三个方法。它们是Java语言提供的实现线程间阻塞和控制进程内调度的底层机制,平时咱们会不多用到的。

wait():
致使线程进入等待状态,直到它被其余线程经过notify()或者notifyAll唤醒,该方法只能在同步方法中调用。

notify():
随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态,该方法只能在同步方法或同步块内部调用。

notifyAll():
解除全部那些在该对象上调用wait方法的线程的阻塞状态,一样该方法只能在同步方法或同步块内部调用。

调用这三个方法中任意一个,当前线程必须是锁的持有者,若是不是会抛出一个 IllegalMonitorStateException 异常。

wait() 与 Thread.sleep(long time) 的区别

sleep():在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),该线程不丢失任何监视器的所属权,sleep() 是 Thread 类专属的静态方法,针对一个特定的线程。
wait() 方法使实体所处线程暂停执行,从而使对象进入等待状态,直到被 notify() 方法通知或者 wait() 的等待的时间到。sleep() 方法使持有的线程暂停运行,从而使线程进入休眠状态,直到用 interrupt 方法来打断他的休眠或者 sleep 的休眠的时间到。
wait() 方法进入等待状态时会释放同步锁,而 sleep() 方法不会释放同步锁。因此,当一个线程无限 sleep 时又没有任何人去 interrupt 它的时候,程序就产生大麻烦了,notify() 是用来通知线程,但在 notify() 以前线程是须要得到 lock 的。另个意思就是必须写在 synchronized(lockobj) {...} 之中。wait() 也是这个样子,一个线程须要释放某个 lock,也是在其得到 lock 状况下才可以释放,因此 wait() 也须要放在 synchronized(lockobj) {...} 之中。

volatile 关键字

volatile 是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺乏同步类的状况下,多线程对成员变量的操做对其它线程是透明的。volatile 变量能够保证下一个读取操做会在前一个写操做以后发生。线程都会直接从内存中读取该变量而且不缓存它。这就确保了线程读取到的变量是同内存中是一致的。

ThreadLocal 变量

ThreadLocal 是Java里一种特殊的变量。每一个线程都有一个 ThreadLocal 就是每一个线程都拥有了本身独立的一个变量,竞争条件被完全消除了。若是为每一个线程提供一个本身独有的变量拷贝,将大大提升效率。首先,经过复用减小了代价高昂的对象的建立个数。其次,你在没有使用高代价的同步或者不变性的状况下得到了线程安全。

join() 方法

join() 方法定义在 Thread 类中,因此调用者必须是一个线程,join() 方法主要是让调用该方法的 Thread 完成 run() 方法里面的东西后,再执行 join() 方法后面的代码,看下下面的"意思"代码:

Thread t1 = new Thread(计数线程一); Thread t2 = new Thread(计数线程二); t1.start(); t1.join(); // 等待计数线程一执行完成,再执行计数线程二 t2.start();

启动 t1 后,调用了 join() 方法,直到 t1 的计数任务结束,才轮到 t2 启动,而后 t2 才开始计数任务,两个线程是按着严格的顺序来执行的。若是 t2 的执行须要依赖于 t1 中的完整数据的时候,这种方法就能够很好的确保两个线程的同步性。

Thread.yield() 方法

Thread.sleep(long time):线程暂时终止执行(睡眠)必定的时间。
Thread.yield():线程放弃运行,将CPU的控制权让出。

这两个方法都会将当前运行线程的CPU控制权让出来,但 sleep() 方法在指定的睡眠时间内必定不会再获得运行机会,直到它的睡眠时间完成;而 yield() 方法让出控制权后,还有可能立刻被系统的调度机制选中来运行,好比,执行yield()方法的线程优先级高于其余的线程,那么这个线程即便执行了 yield() 方法也可能不能起到让出CPU控制权的效果,由于它让出控制权后,进入排队队列,调度机制将从等待运行的线程队列中选出一个等级最高的线程来运行,那么它又(极可能)被选中来运行。

扩展

线程调度策略

(1) 抢占式调度策略

Java运行时系统的线程调度算法是抢占式的。Java运行时系统支持一种简单的固定优先级的调度算法。若是一个优先级比其余任何处于可运行状态的线程都高的线程进入就绪状态,那么运行时系统就会选择该线程运行。新的优先级较高的线程抢占了其余线程。可是Java运行时系统并不抢占同优先级的线程。换句话说,Java运行时系统不是分时的。然而,基于Java Thread类的实现系统多是支持分时的,所以编写代码时不要依赖分时。当系统中的处于就绪状态的线程都具备相同优先级时,线程调度程序采用一种简单的、非抢占式的轮转的调度顺序。

(2) 时间片轮转调度策略

有些系统的线程调度采用时间片轮转调度策略。这种调度策略是从全部处于就绪状态的线程中选择优先级最高的线程分配必定的CPU时间运行。该时间事后再选择其余线程运行。只有当线程运行结束、放弃(yield)CPU或因为某种缘由进入阻塞状态,低优先级的线程才有机会执行。若是有两个优先级相同的线程都在等待CPU,则调度程序以轮转的方式选择运行的线程。

以上把这些技术点总结出来,后面我会把多线程这块的示例代码更新到 《我的学习项目DroidStudy》,感谢您的关注。

● 线程池

建立多个线程不光麻烦并且相对影响系统性能,接下来让我看看使用线程池来操做多线程。我把本身的 《我的学习项目DroidStudy》 中线程池转成一个 gif 效果图,你们能够实际把玩下去感觉线程池的原理,有兴趣的同窗也能够先 star 下,我会在接下来的几个月把这个学习的Demo工程完善好。


线程池.gif

线程池的优势

1)避免线程的建立和销毁带来的性能开销。
2)避免大量的线程间因互相抢占系统资源致使的阻塞现象。
3}可以对线程进行简单的管理并提供定时执行、间隔执行等功能。

再撸一撸概念

Java里面线程池的顶级接口是 Executor,不过真正的线程池接口是 ExecutorService, ExecutorService 的默认实现是 ThreadPoolExecutor;普通类 Executors 里面调用的就是 ThreadPoolExecutor。

照例看一下各个接口的源码:

public interface Executor { void execute(Runnable command); } public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); <T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task); ... } public class Executors { public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } ... }

下面我建立的一个线程池:

ExecutorService pool = Executors.newCachedThreadPool();

Executors 提供四种线程池:

  • 1)newCachedThreadPool 是一个可根据须要建立新线程的线程池,可是在之前构造的线程可用时将重用它们。对于执行不少短时间异步任务的程序而言,这些线程池一般可提升程序性能。调用 execute() 将重用之前构造的线程(若是线程可用)。若是现有线程没有可用的,则建立一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。所以,长时间保持空闲的线程池不会使用任何资源。注意,可使用 ThreadPoolExecutor 构造方法建立具备相似属性但细节不一样(例如超时参数)的线程池。

  • 2)newSingleThreadExecutor 建立是一个单线程池,也就是该线程池只有一个线程在工做,全部的任务是串行执行的,若是这个惟一的线程由于异常结束,那么会有一个新的线程来替代它,此线程池保证全部任务的执行顺序按照任务的提交顺序执行。

  • 3)newFixedThreadPool 建立固定大小的线程池,每次提交一个任务就建立一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就会保持不变,若是某个线程由于执行异常而结束,那么线程池会补充一个新线程。

  • 4)newScheduledThreadPool 建立一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

经过 ThreadPoolExecutor 的构造函数,撸一撸线程池相关参数的概念:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }
  • 1)corePoolSize:线程池的核心线程数,通常状况下无论有没有任务都会一直在线程池中一直存活,只有在 ThreadPoolExecutor 中的方法 allowCoreThreadTimeOut(boolean value) 设置为 true 时,闲置的核心线程会存在超时机制,若是在指定时间没有新任务来时,核心线程也会被终止,而这个时间间隔由第3个属性 keepAliveTime 指定。

  • 2)maximumPoolSize:线程池所能容纳的最大线程数,当活动的线程数达到这个值后,后续的新任务将会被阻塞。

  • 3)keepAliveTime:控制线程闲置时的超时时长,超过则终止该线程。通常状况下用于非核心线程,只有在 ThreadPoolExecutor 中的方法 allowCoreThreadTimeOut(boolean value) 设置为 true时,也做用于核心线程。

  • 4)unit:用于指定 keepAliveTime 参数的时间单位,TimeUnit 是个 enum 枚举类型,经常使用的有:TimeUnit.HOURS(小时)、TimeUnit.MINUTES(分钟)、TimeUnit.SECONDS(秒) 和 TimeUnit.MILLISECONDS(毫秒)等。

  • 5)workQueue:线程池的任务队列,经过线程池的 execute(Runnable command) 方法会将任务 Runnable 存储在队列中。

  • 6)threadFactory:线程工厂,它是一个接口,用来为线程池建立新线程的。

线程池的关闭

ThreadPoolExecutor 提供了两个方法,用于线程池的关闭,分别是 shutdown() 和 shutdownNow()。

shutdown():不会当即的终止线程池,而是要等全部任务缓存队列中的任务都执行完后才终止,但不再会接受新的任务。
shutdownNow():当即终止线程池,并尝试打断正在执行的任务,而且清空任务缓存队列,返回还没有执行的任务。

面试题

1)什么是 Executor 框架?

Executor框架在Java 5中被引入,Executor 框架是一个根据一组执行策略调用、调度、执行和控制的异步任务的框架。

无限制的建立线程会引发应用程序内存溢出,因此建立一个线程池是个更好的的解决方案,由于能够限制线程的数量而且能够回收再利用这些线程。利用 Executor 框架能够很是方便的建立一个线程池。

2)Executors 类是什么?

Executors为Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类提供了一些工具方法。Executors 能够用于方便的建立线程池。

查看具体操做代码请参考 《我的学习项目DroidStudy》,感谢您的关注。

关于我

我的邮箱:sfsheng0322@126.com
GitHub主页
简书主页
我的博客
新浪微博

做者:孙福生微博连接:http://www.jianshu.com/p/b8197dd2934c來源:简书著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。