其实在单个处理器的时期,操做系统就能处理多线程并发任务。处理器给每一个线程分配 CPU 时间片(Time Slice),线程在分配得到的时间片内执行任务。java
CPU 时间片是 CPU 分配给每一个线程执行的时间段,通常为几十毫秒。在这么短的时间内线程互相切换,咱们根本感受不到,因此看上去就好像是同时进行的同样。编程
时间片决定了一个线程能够连续占用处理器运行的时长。当一个线程的时间片用完了,或者因自身缘由被迫暂停运行了,这个时候,另一个线程(能够是同一个线程或者其它进程的线程)就会被操做系统选中,来占用处理器。这种一个线程被暂停剥夺使用权,另一个线程被选中开始或者继续运行的过程就叫作上下文切换(Context Switch)。多线程
具体来讲,一个线程被剥夺处理器的使用权而被暂停运行,就是“切出”;一个线程被选中占用处理器开始或者继续运行,就是“切入”。在这种切出切入的过程当中,操做系统须要保存和恢复相应的进度信息,这个进度信息就是“上下文”了。并发
上下文切换就是一个工做的线程被另一个线程暂停,另一个线程占用了处理器开始执行任务的过程。系统和 Java 程序自发性以及非自发性的调用操做,就会致使上下文切换,从而带来系统开销。ide
开始以前,先看下系统线程的生命周期状态。性能
结合图示可知,线程主要有“新建”(NEW)、“就绪”(RUNNABLE)、“运行”(RUNNING)、“阻塞”(BLOCKED)、“死亡”(DEAD)五种状态。测试
在多线程编程中,执行调用如下方法或关键字,经常就会引起自发性的上下文切换。大数据
sleep()、wait()、yield()、join()、park()、synchronized、lockthis
咱们总说上下文切换会带来系统开销,接下来我使用一段代码,来对比串联执行和并发执行的速度:操作系统
public class DemoApplication { public static void main(String[] args) { //运行多线程 MultiThreadTester test1 = new MultiThreadTester(); test1.Start(); //运行单线程 SerialTester test2 = new SerialTester(); test2.Start(); } static class MultiThreadTester extends ThreadContextSwitchTester { @Override public void Start() { long start = System.currentTimeMillis(); MyRunnable myRunnable1 = new MyRunnable(); Thread[] threads = new Thread[4]; //建立多个线程 for (int i = 0; i < 4; i++) { threads[i] = new Thread(myRunnable1); threads[i].start(); } for (int i = 0; i < 4; i++) { try { //等待一块儿运行完 threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } long end = System.currentTimeMillis(); System.out.println("多线程运行时间: " + (end - start) + "ms"); System.out.println("计数: " + counter); } // 建立一个实现Runnable的类 class MyRunnable implements Runnable { public void run() { while (counter < 100000000) { synchronized (this) { if(counter < 100000000) { increaseCounter(); } } } } } } //建立一个单线程 static class SerialTester extends ThreadContextSwitchTester{ @Override public void Start() { long start = System.currentTimeMillis(); for (long i = 0; i < count; i++) { increaseCounter(); } long end = System.currentTimeMillis(); System.out.println("单线程运行时间: " + (end - start) + "ms"); System.out.println("计数: " + counter); } } //父类 static abstract class ThreadContextSwitchTester { public static final int count = 100000000; public volatile int counter = 0; public int getCount() { return this.counter; } public void increaseCounter() { this.counter += 1; } public abstract void Start(); } }
执行以后,看一下二者的时间测试结果:
经过数据对比咱们能够看到:串联的执行速度比并发的执行速度要快。这就是由于线程的上下文切换致使了额外的开销,通常来讲使用 Synchronized 锁关键字,致使了资源竞争,从而引发了上下文切换,但即便不使用 Synchronized 锁关键字,并发的执行速度也没法超越串联的执行速度,这是由于多线程一样存在着上下文切换。Redis、NodeJS 的设计就很好地体现了单线程串行的优点。
线程越多,系统的运行速度不必定越快。那么咱们平时在并发量比较大的状况下,何时用单线程,何时用多线程呢?
通常在单个逻辑比较简单,并且速度相对来很是快的状况下,咱们可使用单线程。例如,咱们前面讲到的 Redis,从内存中快速读取值,不用考虑 I/O 瓶颈带来的阻塞问题。而在逻辑相对来讲很复杂的场景,等待时间相对较长又或者是须要大量计算的场景,我建议使用多线程来提升系统的总体性能。例如,NIO 时期的文件读写操做、图像处理以及大数据分析等。