什麼是高併發?
高併發(High Concurrency)是一種系統運行過程中遇到的一種“短時間內遇到大量操作請求”的情況,主要發生在web系統集中大量訪問收到大量請求(例如:12306的搶票情況;天貓雙十一活動)。該情況的發生會導致系統在這段時間內執行大量操作,例如對資源的請求,數據庫的操作等。
高併發的處理指標?
高併發相關常用的一些指標有:
1.響應時間(Response Time)
響應時間:系統對請求做出響應的時間。例如系統處理一個HTTP請求需要200ms,這個200ms就是系統的響應時間
2.吞吐量(Throughput)
吞吐量:單位時間內處理的請求數量。
3.每秒查詢率QPS(Query Per Second)
QPS:每秒響應請求數。在互聯網領域,這個指標和吞吐量區分的沒有這麼明顯。
4.併發用戶數
併發用戶數:同時承載正常使用系統功能的用戶數量。例如一個即時通訊系統,同時在線量一定程度上代表了系統的併發用戶數。
高併發和多線程的關係和區別
“高併發和多線程”總是被一起提起,給人感覺兩者好像相等,實則 高併發 ≠ 多線程
1.多線程
多線程是java的特性,因為現在cpu都是多核多線程的,可以同時執行幾個任務,為了提高jvm的執行效率,java提供了這種多線程的機制,以增強數據處理效率。多線程對應的是cpu,高併發對應的是訪問請求,可以用單線程處理所有訪問請求,也可以用多線程同時處理訪問請求。
在過去單CPU時代,單任務在一個時間點只能執行單一程序。之後發展到多任務階段,計算機能在同一時間點並行執行多任務或多進程。雖然並不是真正意義上的“同一時間點”,而是多個任務或進程共享一個CPU,並交由操作系統來完成多任務間對CPU的運行切換,以使得每個任務都有機會獲得一定的時間片運行。
再後來發展到多線程技術,使得在一個程序內部能擁有多個線程並行執行。一個線程的執行可以被認為是一個CPU在執行該程序。當一個程序運行在多線程下,就好像有多個CPU在同時執行該程序。
總之,多線程即可以這麼理解:多線程是處理高併發的一種編程方法,即併發需要用多線程實現。
2.高併發
高併發不是JAVA的專有的東西,是語言無關的廣義的,為提供更好互聯網服務而提出的概念。
典型的場景,例如:12306搶火車票,天貓雙十一秒殺活動等。該情況的發生會導致系統在這段時間內執行大量操作,例如對資源的請求,數據庫的操作等。如果高併發處理不好,不僅僅降低了用戶的體驗度(請求響應時間過長),同時可能導致系統宕機,嚴重的甚至導致OOM異常,系統停止工作等。
如果要想系統能夠適應高併發狀態,則需要從各個方面進行系統優化,包括,硬件、網絡、系統架構、開發語言的選取、數據結構的運用、算法優化、數據庫優化等……而
多線程只是其中解決方法之一。多線程併發技術
java多線程編程將會涉及到如下技術點:
1.併發編程三要素
- 原子性原子,即一個不可再被分割的顆粒。在Java中原子性指的是一個或多個操作要麼全部執行成功要麼全部執行失敗。
- 有序性程序執行的順序按照代碼的先後順序執行。(處理器可能會對指令進行重排序)
- 可見性當多個線程訪問同一個變量時,如果其中一個線程對其作了修改,其他線程能立即獲取到最新的值。
2. 線程的五大狀態
- 創建狀態當用 new 操作符創建一個線程的時候
- 就緒狀態調用 start 方法,處於就緒狀態的線程並不一定馬上就會執行 run 方法,還需要等待CPU的調度
- 運行狀態CPU 開始調度線程,並開始執行 run 方法
- 阻塞狀態線程的執行過程中由於一些原因進入阻塞狀態比如:調用 sleep 方法、嘗試去得到一個鎖等等
- 死亡狀態run 方法執行完 或者 執行過程中遇到了一個異常
3.悲觀鎖與樂觀鎖
- 悲觀鎖:每次操作都會加鎖,會造成線程阻塞。
- 樂觀鎖:每次操作不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止,不會造成線程阻塞。
4.線程之間的協作:wait/notify/notifyAll等
5.synchronized 關鍵字
6.CAS
CAS全稱是Compare And Swap,即比較替換,是實現併發應用到的一種技術。操作包含三個操作數 —— 內存位置(V)、預期原值(A)和新值(B)。 如果內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新為新值 。否則,處理器不做任何操作。
7.線程池
如果我們使用線程的時候就去創建一個線程,雖然簡單,但是存在很大的問題。如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因為頻繁創建線程和銷燬線程需要時間。線程池通過複用可以大大減少線程頻繁創建與銷燬帶來的性能上的損耗。
高併發技術方案:提高高併發能力
1.分佈式緩存:redis、memcached等,結合CDN來解決圖片文件等訪問。
2.消息隊列中間件:activeMQ等,解決大量消息的異步處理能力。
3.應用拆分:一個工程被拆分為多個工程部署,利用dubbo解決多工程之間的通信。
4.數據庫垂直拆分和水平拆分(分庫分表)等。
5.數據庫讀寫分離,解決大數據的查詢問題。
6.還可以利用nosql ,例如mongoDB配合mysql組合使用。
7.還需要建立大數據訪問情況下的服務降級以及限流機制等。
6個併發問題引發的血案
1、synchronized的CPU原語級別是如何實現的?2、一千萬個數,如何高效求和。3、不用數學庫,求2開平方的值,精確到小數點兒後10位。4、線程A不斷打印1-10的數字,打印到第5個數字時通知線程B,請完成編碼。5、下列三種業務,應該如何使用線程池:高併發、任務執行時間短的業務併發不高、任務執行時間長的業務併發高、業務執行時間長的業務6、你如何來設計12306網站,能夠撐住最高百萬級別TPS(淘寶最高54萬TPS)?
以上6 個併發問題,一個都答不上來?強烈建議來看看這本馬士兵的「多線程與高併發」書籍
給大家看看這本書籍的大概內容
目錄:
- 第一節:線程的基本概念
- 第二節:volatile與CAS
- 第三節:Atomic類和線程同步新機制
- 第四節:LockSupport、淘寶面試題與源碼閱讀方法論
- 第五節:AQS源碼閱讀與強軟弱虛4種引用以及ThreadLocal原理與源碼
- 第六節:併發容器
- 第七節:線程池
- 第八節:線程池與源碼閱讀
- 第九節:JMH與Disruptor
第一節:線程的基本概念
線程與高併發大概講六大塊
- 第一:基本的概念,從什麼是線程開始
- 第二:JUC同步工具,就是各種同步鎖
- 第三:同步容器
- 第四:線程池
- 第五:高頻面試加分項的一些面試用的東西,包括纖程
- 第六:Disruptor,不知道有多少同學聽說過這個框架的,這個框架它也是一個MQ框架
- (Message Queue)叫做消息隊列,消息隊列非常多,後面還會給大家講Kafka、RabbitMQ,
- Redis等這些都是消息隊列。Disruptor是目前大家公認的在單機環境上效率最高的、性能最快的
- MQ。
我們先說一下為什麼要講多線程和高併發?
- 原因是,你想拿到一個更高的薪水,在面試的時候呈現出了兩個方向的現象:
- 第一個是上天
- 項目經驗
- 高併發 緩存 大流量 大數據量的架構設計
- 第二個是入地
- 各種基礎算法,各種基礎的數據結構
- JVM OS 線程 IO等內容
- 多線程和高併發,就是入地裡面的內容。
基本概念
我們先從線程的基本概念開始,給大家複習一下,不知道有多少同學是基礎不太好,說什麼是線程都不
知道的,如果這樣的話,花時間去補初級內容的課
第二節:volatile與CAS
volatile
我們先來看這個volatile的概念,volatile它是什麼意思,現在像大的互聯網企業的面試,基本上volatile
是必會的,有時候他也不會太問,認為你應該會,但是中小企業也就開始問這方面的問題。
我們來看一下這個小程序,寫了一個方法啊,首先定義了一個變量布爾類型等於true,這裡模擬的是一
個服務器的操作,我的值為true你就給我不間斷的運行,什麼時候為false你再停止。 測試new Thread
啟動一個線程,調用m方法,睡了一秒,最後running等於false,運行方法他是不會停止的。 如果你要
把volatile打開,那麼結果就是啟動程序一秒之後他就會m end停止。(volatile就是不停的追蹤這個
值,時刻看什麼時候發生了變化)
CAS
cas號稱是無鎖優化,或者叫自旋。這個名字無所謂,理解它是幹什麼的就行,概念這個東西是人為了
描述問題解決問題而定義出來的,所以怎麼定義不是很重要,重點是在解決問題上
我們通過Atomic類(原子的)。由於某一些特別常見的操作,老是來回的加鎖,加鎖的情況特別多,所
以乾脆java就提供了這些常見的操作這麼一些個類,這些類的內部就自動帶了鎖,當然這些鎖的實現並
不是synchronized重量級鎖,而是CAS的操作來實現的(號稱無鎖)。
我們來舉例幾個簡單的例子,凡是以Atomic開頭的都是用CAS這種操作來保證線程安全的這麼一些個
類。AtomicInteger的意思就是裡面包了一個Int類型,這個int類型的自增 count++ 是線程安全的,還有
拿值等等是線程安全的,由於我們在工作開發中經常性的有那種需求,一個值所有的線程共同訪問它往
上遞增 ,所以jdk專門提供了這樣的一些類。使用方法AtomicInteger如下代碼
<code>**/<code>
<code>* 解決同樣的問題的高效方法,使用AtomXXX類/<code>
<code>* AtomXXX類的本身方法都是原子性的,但不能保證多個方法連續調用都是原子性的/<code>
<code>* @author mashibing/<code>
<code>*//<code>
<code>package com.mashibing.juc.c_018_00_AtomicXXX;/<code>
<code>import java.util.ArrayList;/<code>
<code>import java.util.List;/<code>
<code>import java.util.concurrent.atomic.AtomicInteger;/<code>
<code>好,我們來分許分析,它的內部實現的原理,主要是聊原理,它的用法看看API都會用。這個原理叫/<code>
<code>CAS操作,incrementAndGet() 調用了getAndAddInt/<code>
<code>當然這個也是一個CompareAndSetInt操作/<code>
<code>public class T01_AtomicInteger {/<code>
<code>/*volatile*/ //int count1 = 0;/<code>
<code>AtomicInteger count = new AtomicInteger(0);/<code>
<code>/*synchronized*/ void m() {/<code>
<code>for (int i = 0; i < 10000; i++)/<code>
<code>//if count1.get() < 1000/<code>
<code>count.incrementAndGet(); //count1++/<code>
<code>}/<code>
<code>public static void main(String[] args) {/<code>
<code>T01_AtomicInteger t = new T01_AtomicInteger();/<code>
<code>List<thread> threads = new ArrayList<thread>();/<thread>/<thread>/<code>
<code>for (int i = 0; i < 10; i++) {/<code>
<code>threads.add(new Thread(t::m, "thread-" + i));/<code>
<code>}/<code>
<code>threads.forEach((o) -> o.start());/<code>
<code>threads.forEach((o) -> {/<code>
<code>try {/<code>
<code>o.join();/<code>
<code>} catch (InterruptedException e) {/<code>
<code>e.printStackTrace();/<code>
<code>}/<code>
<code>});/<code>
<code>System.out.println(t.count);/<code>
<code>}/<code>
<code>}/<code>
第三節:Atomic類和線程同步新機制
今天,我們繼續講一個Atomic的問題,然後開始講除synchronized之外的別的鎖。在前面內容我們講
了synchronized、volatile、Atomic和CAS,Atomic我們只是講了一個開頭還沒有講完,今天我們繼
續。
像原來我們寫m++你得加鎖,在多線程訪問的情況下,那現在我們可以用AtomicInteger了,它內部就
已經幫我們實現了原子操作,直接寫 count.incrementAndGet(); //count1++ 這個就相當於count++。原
來我們對count是需要加鎖的,現在就不需要加鎖了。
我們看下面小程序,模擬,我們計一個數,所有的線程都要共同訪問這個數count值,大家知道如果所
有線程都要訪問這個數的時候,如果每個線程給它往上加了10000,你這個時候是需要加鎖的,不加鎖
會出問題。但是,你把它改成AtomicInteger之後就不用在做加鎖的操作了,因為incrementAndGet內
部用了cas操作,直接無鎖的操作往上遞增,有同學可能會講為什麼要用無鎖操作啊,原因是無鎖的操
作效率會更高。
第四節:LockSupport、淘寶面試題與源碼閱讀方法論
首先我們簡單回顧一下前面三節課講的內容,分別有線程的基本概念、synchronized、volatile、
AtomicXXX、各種JUC同步框架(ReentrantLock、CountDownLatch、CyclicBarrier、Phaser、
ReadWriteLock-StampedLock、Semaphore、Exchanger、LockSupport),其中synchornized重點講
了一下,包括有synchornized的底層實現原理、鎖升級的概念(四種狀態:無鎖、偏向鎖、輕量級鎖、
重量級鎖),volatile我們講了可見性和禁止指令重排序如何實現。
synchronized和ReentrantLock的不同?
synchronized:系統自帶、系統自動加鎖,自動解鎖、不可以出現多個不同的等待隊列、默認進行
四種鎖狀態的升級
ReentrantLock:需要手動枷鎖,手動解鎖、可以出現多個不同的等待隊列、CIS的實現
本章我們補一個小漏洞,它叫LockSupport,然後我們分析兩道面試題,緊接著我會教大家閱讀源碼的
技巧,源碼層出不窮,生生不息,掌握了源碼的閱讀技巧,大家培養出了閱讀源碼興趣的時候,之後好
多代碼,你需要自己去摳,摳出來才是你自己的,最後我們會分析AQS源碼,以上是我們本章主講的內
容概述。
LockSupport
我們會以幾個小程序為案例,展開對LockSupport的講解,在以前我們要阻塞和喚醒某一個具體的線程
有很多限制比如:
1、因為wait()方法需要釋放鎖,所以必須在synchronized中使用,否則會拋出異常
IllegalMonitorStateException
2、notify()方法也必須在synchronized中使用,並且應該指定對象
3、synchronized()、wait()、notify()對象必須一致,一個synchronized()代碼塊中只能有一個線程調
用wait()或notify()
以上諸多限制,體現出了很多的不足,所以LockSupport的好處就體現出來了。
在JDK1.6中的java.util.concurrent的子包locks中引了LockSupport這個API,LockSupport是一個比較
底層的工具類,用來創建鎖和其他同步工具類的基本線程阻塞原語。java鎖和同步器框架的核心 AQS:
AbstractQueuedSynchronizer,就是通過調用 LockSupport .park()和 LockSupport .unpark()的方
法,來實現線程的阻塞和喚醒的。我們先來看一個小程序:
後續的內容給大家大概截個圖,由於內容太多了
第五節:AQS源碼閱讀與強軟弱虛4種引用以及ThreadLocal原理與源碼
第六節:併發容器
今天是第六天了,這節課本來想上一個大而全的課,後來發現這個實在目標太大了,大而全的概念就是
上節課講到的那張容器圖中的每一個都講的非常的細緻,然後去談他們的源碼。但是如果這麼講的話我
們高併發的課就講不完了,所以也彆著急,後面單獨開一門課來講集合,集合的發展歷程,現在為什麼
講這個併發容器呢,主要是為了線程池做準備,線程池裡有一個參數就是用併發容器來做你工作任務的
容器。
第七節:線程池
第八節:線程池與源碼閱讀
第九節:JMH與Disruptor
如何獲取?
轉發這篇文章,關注我,私信回覆“學習”即可獲取電子書籍,以上 spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構
如何私信?
關注我後,在手機,點進我的主頁,主頁上方右上角有個私信,點擊私信,如何回覆關鍵字“學習”即可
閱讀更多 JAVA架構公社師長 的文章