談談多線程


前言:

  一直以來,對於多線程的理解總是趕在前一秒翻書時回憶起,后一秒放下書即忘。甚是可惱!今晚對多線程總結一下,也好有個了斷~

概念引入:

  首先,我們想了解的是:什么是線程,跟進程有什么關聯?

  其實是這樣的:線程是程序執行流的最小單元。其一般有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.經典生產者消費者問題

(抱歉,時間關系!后續補上關於生產者消費者的實例,敬請關注~~)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM