其實在單個處理器的時期,操做系統就能處理多線程併發任務。處理器給每一個線程分配 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 時期的文件讀寫操做、圖像處理以及大數據分析等。