併發編程的目的是爲了讓程序運行的更快,可是並非啓動更多的線程就能讓程序最大限度的併發執行。在進行併發編程時,若是但願經過多線程執行任務讓程序運行的更快,會面臨很是多的挑戰,如上下文切換問題、死鎖問題、以及受限於硬件和軟件的資源限制問題。java
一、1上下文切換算法
即便是單核處理器也支持多線程執行代碼,cpu經過給每一個線程分配cpu時間片來實現這個機制。時間片是cpu分配給各個線程的執行時間,由於時間片很是短,因此cpu經過不停切換線程執行,讓咱們感到多個線程是同時執行的,時間片通常是幾十毫秒。數據庫
cpu經過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個任務。可是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,能夠加載這個任務的狀態,因此任務從保存到再加載的過程就是一次上下文切換。編程
一、一、1多線程必定快嗎多線程
下面的代碼演示串行和併發執行並累加操做時間,請分析:併發執行必定比串行執行快嗎?併發
/** * 併發和串行執行測試 */ public class ConcurrencyTest { /** 執行次數 */ private static final long count = 100000l; public static void main(String[] args) throws InterruptedException { // 併發計算 concurrency(); // 單線程計算 serial(); } private static void concurrency() throws InterruptedException { long start = System.currentTimeMillis(); Thread thread = new Thread(new Runnable() { @Override public void run() { int a = 0; for (long i = 0; i < count; i++) { a += 5; } System.out.println("concurrency :a=" + a); } }); thread.start(); int b = 0; for (long i = 0; i < count; i++) { b--; } thread.join(); long time = System.currentTimeMillis() - start; System.out.println("concurrency :" + time + "ms,b=" + b); } private static void serial() { long start = System.currentTimeMillis(); int a = 0; for (long i = 0; i < count; i++) { a += 5; } int b = 0; for (long i = 0; i < count; i++) { b--; } long time = System.currentTimeMillis() - start; System.out.println("serial:" + time + "ms,b=" + b + ",a=" + a); } }
運行上述程序發現,當併發執行累加操做不超過百萬時,速度比串行執行累加操做要慢。那麼爲何會慢呢,這是由於線程有建立和上下文切換的開銷。ide
一、一、2如何減小上下文切換工具
減小上線文切換的方法有無鎖併發編程、CAS算法、使用最少線程和使用協程。測試
無鎖併發編程:多線程競爭鎖時,會引起上下文切換,因此多線程處理數據時,能夠用一些辦法來避免使用鎖,如將數據的ID按hash算法取模分段,不一樣的線程處理不一樣段的數據。線程
CAS算法:java併發包中的Atomic使用CAS算法來更新數據,而不須要加鎖。
使用最少線程:避免建立不須要的線程,好比任務不多,可是建立了不少線程來處理,這樣會形成大量線程都處於等待狀態。
使用協程:在單線程裏實現多任務的調度,並在單線程裏維持多個任務間的切換。
一、2死鎖
鎖時很是有用的工具,運用場景很是多,但同時也會帶來一些困擾,那就是可能引發死鎖問題,一旦產生死鎖,會形成系統功能不可用。下面這段代碼會引發死鎖,是線程t1和線程t2互相等待對方釋放鎖。
/** * 死鎖例子 */ public class DeadLockDemo { /** A鎖 */ private static String A = "A"; /** B鎖 */ private static String B = "B"; public static void main(String[] args) { new DeadLockDemo().deadLock(); } private void deadLock() { Thread t1 = new Thread(new Runnable() { @Override public void run() { synchronized (A) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (B) { System.out.println("1"); } } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { synchronized (B) { synchronized (A) { System.out.println("2"); } } } }); t1.start(); t2.start(); } }
一旦出現死鎖,業務是可感知的,由於不能繼續提供服務了,只能dump線程查看到底哪一個線程出現了問題。
避免死鎖的幾個常見方法:
避免一個線程同時得到多個鎖
避免一個線程在鎖內同時佔用多個資源,儘可能保證每一個資源只佔用一個資源
嘗試使用定時鎖,使用Lock.tryLock(time)來替代使用內部鎖機制
對於數據庫鎖,加鎖和解鎖必須在一個數據庫鏈接裏,不然會出現解鎖失敗的狀況
一、3資源限制的挑戰