前言:
一直以來,對於多線程的理解總是趕在前一秒翻書時回憶起,后一秒放下書即忘。甚是可惱!今晚對多線程總結一下,也好有個了斷~
概念引入:
首先,我們想了解的是:什么是線程,跟進程有什么關聯?
其實是這樣的:線程是程序執行流的最小單元。其一般有3種狀態:就緒,執行和阻塞(因本文注重實例,就不對概念作過多的解釋~)。在計算機中,一個代碼塊(block)運行時產生一個或多個進程(process),而每一個進程又對應一至多個線程(thread),每一個線程又可以分為一至多個任務(task).
因此,以上的關系我們可以通過下面一張圖進行理解。

-
1.創建線程的兩種實現方式:
1)繼承Thread類
2)實現Runnable接口
1 package com.gdufe.thread; 2 3 public class ThreadTest { 4 5 public static void main(String[] args) { 6 7 Thread thread1 = new Thread(new TaskA(),"thread1"); 8 Thread thread2 = new Thread(new TaskB('a',100),"thread2"); 9 Thread thread3 = new Thread(new TaskB('b',100),"thread3"); 10 11 thread1.start();; 12 thread2.start(); 13 thread3.start(); 14 System.out.println("--End--"); 15 16 17 } 18 19 /* 20 * 任務A通過實現Runnable接口創建任務 21 */ 22 private static class TaskA implements Runnable{ 23 24 @Override 25 public void run() { 26 for(int i=0;i<100;i++){ 27 System.out.print(i+" "); 28 } 29 } 30 31 } 32 /* 33 * 任務B通過繼承Thread類並重寫run()方法來創建任務 34 */ 35 private static class TaskB extends Thread{ 36 37 private char ch; 38 private int times; 39 40 public TaskB(char ch,int times){ 41 this.ch=ch; 42 this.times=times; 43 } 44 @Override 45 public void run() { 46 for(int i=0;i<times;i++){ 47 System.out.print(ch+" "); 48 } 49 } 50 } 51 }
輸出結果:

-
2.線程池thread-pool
線程池指的是不需要每個任務用一個線程來存儲,而是初始就給出一個“池”。線程池首先會初始池的大小,即可供同時進入的“任務”的數量,這樣的話每當來一個新任務就直接往線程池里面扔,不需要再單獨創建一個個線程。
-
3.線程不安全
【不安全實例代碼】
1 package com.gdufe.thread; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 6 public class ThreadNotSafe { 7 static Account account = new Account(); 8 9 public static void main(String[] args) { 10 11 ExecutorService executor = Executors.newCachedThreadPool(); 12 for (int i = 0; i < 100; i++) { 13 executor.execute(new Deposit()); 14 } 15 executor.shutdown(); 16 while (!executor.isTerminated()) { 17 } 18 System.out.println("Finally, the account get the total balance:" 19 + account.getBalance()); 20 } 21 /* 22 * 存錢操作,每次存入1 23 */ 24 static class Deposit implements Runnable { 25 26 @Override 27 public void run() { 28 account.deposit(1); 29 } 30 } 31 32 static class Account { 33 private int balance = 0; 34 35 public int getBalance() { 36 return balance; 37 } 38 39 public void deposit(int value) { 40 int newBalance = balance + value; 41 try { 42 Thread.sleep(5); //故意將原有的簡單操作拆分兩步,並且中間延遲5毫秒 43 } catch (Exception e) { 44 } 45 balance = newBalance; 46 } 47 } 48 49 }
輸出結果:
Finally, the account get the total balance:3
(注意:執行多次的結果不一樣)
分析:
上述實例進行100個任務,每個任務都是往賬號里面存“1”,正確的結果應該輸出“100”,那為什么最終的結果好像“不正確”。原因是,當多個任務同時訪問一個資源時,就出現了所謂的資源“競爭”。
解決方法:
1)使用關鍵字“synchronized”對資源進行封鎖,此方式稱作“隱式加鎖”;
2)采用java內部工具類“Lock”進行“顯式加鎖”。
【方式1-代碼修改部分】:
1 static class Account { 2 private int balance = 0; 3 4 public int getBalance() { 5 return balance; 6 } 7 /* 8 * 增加關鍵字‘synchronized’,方法未執行完時,僅限一個任務訪問該方法 9 */ 10 public synchronized void deposit(int value) { 11 int newBalance = balance + value; 12 try { 13 Thread.sleep(5); //故意延遲5毫秒 14 } catch (Exception e) { 15 } 16 balance = newBalance; 17 } 18 }
【方式2-代碼修改部分】:
1 static class Account { 2 private int balance = 0; 3 private static Lock lock = new ReentrantLock(); 4 5 public int getBalance() { 6 return balance; 7 } 8 /* 9 * 采用顯示加鎖,方法開始執行時加鎖,執行結束前解鎖 10 */ 11 public void deposit(int value) { 12 lock.lock(); //加鎖 13 int newBalance = balance + value; 14 try { 15 Thread.sleep(5); //故意延遲5毫秒 16 balance = newBalance; 17 } catch (Exception e) { 18 19 }finally{ 20 lock.unlock(); //解鎖 21 } 22 } 23 }
輸出結果:
Finally, the account get the total balance:100
(多次執行,輸出結果總是100)
-
4.線程協作實例:
實例情境:
假如現在要對一個銀行賬號(Account)進行存(Deposit)取(Withdraw)錢操作。故確定了2個線程,這里的協作需要注意一點的是當銀行賬號的余額不足時,取錢操作必須等待。因此,除了前面設定的鎖之外,我們還得加一個信號量signal。信號量的作用是當取錢的數量大於當前賬號余額時,停止該操作,發出等待的信號量signal;存錢時,有多少即存多少就是了。不過,每進行一次存錢操作,都必須發出信號提醒還在等待的取錢操作,不然取錢操作將一直等下去...
代碼實現:
1 package com.gdufe.thread; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 import java.util.concurrent.locks.Condition; 6 import java.util.concurrent.locks.Lock; 7 import java.util.concurrent.locks.ReentrantLock; 8 9 public class ThreadCooperation { 10 private static Account account = new Account(); 11 12 public static void main(String[] args) { 13 //開啟大小為2線程池的,依次加入任務 14 ExecutorService executor = Executors.newFixedThreadPool(2); 15 executor.execute(new DepositTask()); 16 executor.execute(new WithdrawTask()); 17 //關閉線程池入口 18 executor.shutdown(); 19 System.out.println("Operation------Balance"); 20 } 21 //從賬號取出錢 22 static class WithdrawTask implements Runnable { 23 @Override 24 public void run() { 25 try { 26 while (true) { 27 account.withdraw((int) (Math.random() * 10)); 28 } 29 } catch (Exception e) { 30 } 31 } 32 } 33 //往賬號存錢 34 static class DepositTask implements Runnable { 35 @Override 36 public void run() { 37 try{ 38 while (true) { 39 account.deposit((int) (Math.random() * 10)); 40 Thread.sleep(1000); 41 } 42 }catch(Exception e){ 43 e.printStackTrace(); 44 } 45 } 46 } 47 //內部類,只有‘balance’屬性 48 static class Account { 49 private int balance = 0; 50 private static Lock lock = new ReentrantLock(); 51 private static Condition signal = lock.newCondition(); 52 53 public int getBalance() { 54 return balance; 55 } 56 //前后加鎖,解鎖 57 public void deposit(int amount) { 58 lock.lock(); 59 try { 60 balance += amount; 61 System.out.println("deposit "+amount+"----total:" + account.getBalance()); 62 signal.signalAll(); //not 'notifyAll()' 63 } finally { 64 lock.unlock(); 65 } 66 } 67 //同樣加鎖,解鎖 68 public void withdraw(int amount) { 69 lock.lock(); 70 try { 71 while (balance < amount) { 72 signal.await(); 73 System.out.println("signal await..."); 74 } 75 balance -= amount; 76 System.out.println("whthdraw "+amount+"----total:" + account.getBalance()); 77 } catch (Exception e) { 78 e.printStackTrace(); 79 } finally { 80 lock.unlock(); 81 } 82 } 83 } 84 85 }
輸出結果:

-
5.經典生產者消費者問題
(抱歉,時間關系!后續補上關於生產者消費者的實例,敬請關注~~)
