點擊關注"故里學Java"
右上角"設為星標"好文章不錯過
雙十一前的一個多月,所有的電商相關的系統都在進行壓測,不斷的優化系統,我們的電商ERP系統也進行了一個多月的壓測和優化的程序,在這其中,我們發現了大量的超時報警,通過工具分析,我們發現是cs指標很高,然后分析日志,我們發現有大量wait()相關的Exception,這個時候我們懷疑是在多執行緒并發處理的時候,出現了大量的執行緒處理不及時導致的這些問題,后來我們通過減小執行緒池最大執行緒數,再進行壓測發現系統的性能有了不小的提升,
我們都知道,在并發編程中,并不是執行緒越多就效率越高,執行緒數太少可能導致資源不能充分利用,執行緒數太多可能導致競爭資源激烈,然后背景關系切換頻繁造成系統的額外開銷,
什么是背景關系切換
我們都知道,在處理多執行緒并發任務的時候,處理器會給每個執行緒分配CPU時間片,執行緒在各自分配的時間片內執行任務,每個時間片的大小一般為幾十毫秒,所以在一秒鐘就可能發生幾十上百次的執行緒相互切換,給我們的感覺就是同時進行的,
執行緒只在分配的時間片內占用處理器,當一個執行緒分配的時間片用完了,或者自身原因被迫暫停運行的時候,就會有另外一個執行緒來占用這個處理器,這種一個執行緒讓出處理器使用權,另外一個執行緒獲取處理器使用權的程序就叫做背景關系切換,
一個執行緒讓出處理器使用權,就是“切出”;另外一個執行緒獲取處理器使用權,就是“切入”,在這個切入切出的程序中,作業系統會保存和恢復相關的進度資訊,這個進度資訊就是我們常說的“背景關系”,背景關系中一般包含了暫存器的存盤內容以及程式計數器存盤的指令內容,
背景關系切換的原因
多執行緒編程中,我們知道執行緒間的背景關系切換會導致性能問題,那么是什么原因造成的執行緒間的背景關系切換,我們先看一下執行緒的生命周期,從中看一下找找答案,
執行緒的五種狀態我們都非常清楚:NEW、RUNNABLE、RUNNING、BLOCKED、DEAD,對應的Java中的六種狀態分別為:NEW、RUNABLE、BLOCKED、WAINTING、TIMED_WAITING、TERMINADTED,
圖中,一個執行緒從RUNNABLE到RUNNING的程序就是執行緒的背景關系切換,RUNNING狀態到BLOCKED、再到RUNNABLE、再從RUNNABLE到RUNNING的程序就是一個背景關系切換的程序,一個執行緒從RUNNING轉為BLOCKED狀態時,我們叫做執行緒的暫停,執行緒暫停了,這個處理器就會有別的執行緒來占用,作業系統就會保存相應的背景關系,為了這個執行緒以后再進入RUNNABLE狀態時可以接著之前的執行進度繼續執行,當執行緒從BLOCKED狀態進入到RUNNABLE時,也就是執行緒的喚醒,此時執行緒將獲取上次保存的背景關系資訊,
我們看到,多執行緒的背景關系切換實際上就是多執行緒兩個運行狀態的相互切換導致的,
我們知道兩種情況可以導致背景關系切換:一種是程式本身觸發的切換,這種我們一般稱為自發性背景關系切換,另一種是系統或者虛擬機導致的背景關系切換,我們稱之為非自發性背景關系切換,
自發性背景關系是執行緒由Java程式呼叫導致切出,一般是在編碼的時候,呼叫一下幾個方法或關鍵字:
sleep()
wait()
yield()
join()
park();
synchronized
lock
非自發的背景關系切換常見的有:執行緒被分配的時間片用完,虛擬機垃圾回收導致,或者執行優先級的問題導致,
小測驗發現背景關系切換
我們通過一個例子來看一下并發執行和串行執行的速度對比;
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println("multi thread exce time: " + (end - start) + "s");
System.out.println("counter: " + 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("serial exec time: " + (end - start) + "s");
System.out.println("counter: " + 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();
}
}
執行結果;
multi thread exce time: 5149s
counter: 100000000
serial exec time: 956s
counter: 100000000
通過執行的結果對比我們可以看到,串行的執行速度比并發執行的速度更快,這其中就是因為多執行緒的背景關系切換導致了系統額外的開銷,使用的synchronized關鍵字,導致了鎖競爭,導致了執行緒背景關系切換,這個地方如果不使用synchronized關鍵字,并發的執行效率也比不上串行執行的速度,因為沒有鎖競爭多執行緒的背景關系切換依然存在,
系統開銷在背景關系切換的哪些環節:
作業系統保存和恢復背景關系
處理器高速快取加載
調度器進行調度
背景關系切換可能導致的高速緩沖區被沖刷
總結
背景關系就是一個釋放處理器的使用權,另外一個執行緒獲取處理器的使用權,自發和非自發的呼叫操作,都會導致背景關系切換,會導致系統資源開銷,執行緒越多不一定執行的速度越快,在單個邏輯比較簡單的時候,而且速度相對來說非常快的情況下,我們推薦是使用單執行緒,如果邏輯非常復雜,或者需要進行大量的計算的地方,我們建議使用多執行緒來提高系統的性能,
- END -
閑聊:天氣越來越冷,大家可以關注公眾號,即可添加我的個人微信號,拉你進技術交流群,一起抱團取暖吧~
好文推薦(點擊可閱讀):
高并發場景下的資料庫事務調優
為什么我不建議你使用Java序列化
今天再來聊聊單例設計模式
大家好,我是"故里學Java"公號作者,你可以叫我"故里",
一直堅信技術能改變生活,愿保持初心,加油技術人!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/221122.html
標籤:其他
上一篇:實戰檔案:徹底搞懂JVM+Linux+MySQL+Netty+Tomcat+并發編程
下一篇:愛奇藝資料倉庫平臺和服務建設實踐
