多線程安全(一)

来源:https://www.cnblogs.com/liclBlog/archive/2018/07/27/9375275.html
-Advertisement-
Play Games

首先感謝授課XXX老師。 1.什麼是線程安全問題 當多個線程共用同一個全局變數,做寫的操作時候,可能會受到其他線程的干擾,導致數據出現問題,這種現象就叫做線程安全問題。做讀的時候不會產生線程安全問題。 什麼安全:多個線程同時共用一個全局變數,做寫操作的時候會發生線程安全。 多個線程共用同一個局部變數 ...


首先感謝授課XXX老師。

1.什麼是線程安全問題

  當多個線程共用同一個全局變數,做寫的操作時候,可能會受到其他線程的干擾,導致數據出現問題,這種現象就叫做線程安全問題。做讀的時候不會產生線程安全問題。

什麼安全:多個線程同時共用一個全局變數,做寫操作的時候會發生線程安全。

多個線程共用同一個局部變數做寫的操作時候,不會發生線程安全問題。

2.如何解決簡單的線程安全問題(鎖)

  所謂簡單就是不涉及分散式,集群等行列。

因為用以下幾種解決方法完全得不到解決(實際上也就是兩種)!!!

① 使用 synchronized 代碼塊

② 使用 synchronized 函數

③ 使用 靜態同步代碼塊 

搶奪cpu資源高的線程會首先拿到鎖,然後進入邏輯中進行操作,其他線程只能進行等待,然後這個線程執行完畢後釋放鎖,這個時候其他線程會爭奪這個鎖,也就會產生資源爭奪的問題,所以synchronized 的效率是很低的,然後搶奪cpu資源高的線程再次進入邏輯進行操作,以此類推。

 

案例

以搶車票為例,代碼會儘量簡潔。

100張火車票,兩個售票視窗。同時出售火車票,在不解決安全問題的情況下看一看會出現什麼問題。

 1 package com.mydream.cn;
 2 
 3 class Train extends Thread {
 4     int count = 1;    // 售票次數
 5     @Override
 6     public  void run() {
 7         while (count<=100) {
 8             try {
 9                 Thread.currentThread().sleep(30);//為了讓程式滿足併發條件,讓他進入睡眠狀態。好讓後面同時進行
10             } catch (InterruptedException e) {
11                 e.printStackTrace();
12             }
13             sale();
14         }
15     }
16     // 售票方法邏輯
17     public void sale() { 
18         System.out.println(count);
19         count++;
20     }
21 }
22 public class TrainDemo {
23     public static void main(String[] args) {
24             Train t = new Train();
25             Thread th2 = new Thread(t);
26             Thread th1 = new Thread(t);
27             th1.start();
28             th2.start();
29     }
30 }

 

 運行結果是出現了101張票!並且在開始時候還出現了重覆的票數,例如1,1,3,這種情況,並不是我們想象的1~100。

那麼是什麼原因呢???

 

思路分析

  首先定義了一個全局變數count然後開啟兩個線程但是是一個對象,也就是同時操作一個全局變數。在主函數main中運行他們,th1線程和th2線程因為有一個sleep函數會等待30毫秒,這樣就有機會創造出代碼併發的可能性。

分析一

  1,1,3,這種不按順序走的邏輯思維。首先兩個線程都得到釋放,那麼就同時進入system.out列印方法中,然後就會列印出1 , 1 ,3這種不安順序的代碼。這種想起來應該很簡單的。也就是說同時進入的時候因為都是>=100,第一次一起進入你列印  1  我也列印  1  

分析二 

  第101張票出現的情況,按照上面思路,當運行到第100張的時候,第一個線程列印出了100,然後進行了+1操作,而在判斷程式中第二個線程已經進入到sale()邏輯方法中了,此時他是100,因為地換一個線程的+1操作,導致他變成了101,然後因為判斷是>=100所以後面的進不來了。才導致會列印出101的情況。

 

解決方法一

 1 // 售票方法邏輯
 2     public void sale() { 
 3         synchronized(this){
 4             // 再次判斷邏輯,因為進入到這裡可能都是100,再次進行判斷即可
 5             if (count<=100) {
 6                 System.out.println(count);
 7                 count++;
 8             }
 9         }
10     }

synchronized 代碼塊,在售票方法邏輯中加入代碼塊,並且寫入判斷邏輯,可能有人會說為什麼還要在寫入判斷邏輯?我直接寫入判斷邏輯不加synchronized 不是一樣麽,請記住synchronized 是用來進行線程安全同步的,既然是同步那麼就會安全,可是synchronized 已經進入了sale()方法中了,那麼我要是同時都是100進入那也沒毛病啊~~~~。所以在此加入邏輯就會防止101的出現。

synchronized 代碼塊中括弧中的變數該放什麼呢?答案是放什麼都可以,只要線程中使用的是同一把鎖就可以了。

例如:

 1 private Object obj =  new Object();
 2     
 3     // 售票方法邏輯
 4     public void sale() { 
 5         synchronized(obj){
 6             // 再次判斷邏輯,因為進入到這裡可能都是100,再次進行判斷即可
 7             if (count<=100) {
 8                 System.out.println(count);
 9                 count++;
10             }
11         }
12     }

 

解決方法二

 

1     // 售票方法邏輯
2     public synchronized void sale() { 
3         if (count<=100) {
4             System.out.println(count);
5             count++;
6         }
7     }

 

synchronized 函數,synchronized 原理就是使用this當鎖,我們可以假設一種情況,也是兩個線程,分別用不同的鎖,這裡不同的鎖就是 synchronized 函數 和 synchronized 代碼塊用this當鎖,如果發現是同步的,那麼就證明瞭 synchronized 函數 是用this當鎖的,相反就不是,在這裡答案是是的。

解決方式三

static synchronized 函數  就是在synchronized 函數 前面 加上 static。如果一旦加入了static 那麼 synchronized 代碼塊this鎖 不會 和 它同步了。

static 關鍵字是比所有代碼都先編譯的,所以也就不會有this的說法,難道你能在static修飾的方法中調用this麽?可能是不可以的了。

那麼 這種鎖 用的是什麼呢?答案是 .class 文件 。如何測試呢?相同的方法,把 synchronized 代碼塊 鎖換成 類名.class 測試是否同步。答案是同步的。

 

 

 

 

模擬 this 和 同步函數是否相同

 1 class ThreadTrain2 implements Runnable {
 2     // 總共有一百張火車 當一變數被static修飾的話存放在永久區,當class文件被載入的時候就會被初始化。
 3     private static int train1Count = 100;
 4     private Object oj = new Object();
 5     public boolean flag = true;
 6     @Override
 7     public void run() {
 8         // 為了能夠模擬程式一直在搶票的話。 where
 9         if (flag) {
10                   //執行同步代碼塊this鎖
11                   while (train1Count > 0) {
12             
13                       synchronized (this) {
14                           if(train1Count>0){
15                               try {
16                                   Thread.sleep(50);
17                               } catch (Exception e) {
18                                   // TODO: handle exception
19                               }
20                               System.out.println(Thread.currentThread().getName()+ ",出售第" + (100 - train1Count + 1) + "票");
21                               train1Count--;
22                           }
23                       }
24                     
25                 }
26         }
27         else{
28              // 同步函數
29             while (train1Count > 0) {
30             
31                 // 出售火車票
32                 sale();
33             }
34         }
35         
36     }
37 
38     public   synchronized void sale() {
39 
40         // 同步代碼塊 synchronized 包裹需要線程安全的問題。
41         // synchronized (oj) {
42         if (train1Count > 0) {
43             try {
44                 Thread.sleep(50);
45             } catch (Exception e) {
46                 // TODO: handle exception
47             }
48             System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - train1Count + 1) + "票");
49             train1Count--;
50         }
51         // }
52 
53     }
54 }
55 
56 public class ThreadDemo2 {
57 
58     public static void main(String[] args) throws InterruptedException {
59         ThreadTrain2 threadTrain2 = new ThreadTrain2();
60         Thread t1 = new Thread(threadTrain2, "視窗①");
61         Thread t2 = new Thread(threadTrain2, "視窗②");
62         t1.start();
63         Thread.sleep(40);
64         threadTrain2.flag=false;
65         t2.start();
66     }
67 
68 }

 

有多線程安全的解決那麼就會出現一些問題,出現死鎖狀態,也就是說兩個鎖的狀態是你等我解鎖,我等你解鎖。直接上代碼

 

package com.itmayiedu;

class ThreadTrain3 implements Runnable {
    // 總共有一百張火車 當一變數被static修飾的話存放在永久區,當class文件被載入的時候就會被初始化。
    private static int train1Count = 100;
    private Object oj = new Object();
    public boolean flag = true;

    @Override
    public void run() {
        // 為了能夠模擬程式一直在搶票的話。 where
        if (flag) {
            // 執行同步代碼塊this鎖
            while (true) {
                synchronized (oj) {
                    sale();
                }
            }
        } else {
            // 同步函數
            while (true) {

                // 出售火車票
                sale();
            }
        }

    }

    public synchronized void sale() {

        // 同步代碼塊 synchronized 包裹需要線程安全的問題。
        synchronized (oj) {
            if (train1Count > 0) {
                try {
                    Thread.sleep(50);
                } catch (Exception e) {
                    // TODO: handle exception
                }
                System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - train1Count + 1) + "票");
                train1Count--;
            }
        }

    }
}

public class ThreadDemo3 {

    public static void main(String[] args) throws InterruptedException {
        ThreadTrain3 threadTrain3 = new ThreadTrain3();
        Thread t1 = new Thread(threadTrain3, "視窗①");
        Thread t2 = new Thread(threadTrain3, "視窗②");
        t1.start();
        Thread.sleep(40);
        threadTrain3.flag = false;
        t2.start();
    }

}

 

什麼是死鎖?同步中嵌套同步,導致鎖無法釋放

 

上面鎖中用到了兩個鎖,一個是this一個是obj,分析運行程式,現進入true然後馬上進入false

true   得到obj鎖 進入sale() 得到 this 鎖

false   得到this0鎖 進入sale() 得到 obj 鎖

然後就可能會出現   交叉想等待,這種想法要好好理解,並且重要! 抽象一點就是this被ojb鎖掉了 另一個 ojb被this鎖掉了 然後互相等待解鎖,可是完全等不到,就會出現多線程死鎖情況。

 

 

 

多線程有三大特性

原子性、可見性、有序性

什麼是原子性

即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。

一個很經典的例子就是銀行賬戶轉賬問題: 

比如從賬戶A向賬戶B轉1000元,那麼必然包括2個操作:從賬戶A減去1000元,往賬戶B加上1000元。這2個操作必須要具備原子性才能保證不出現一些意外的問題。

我們操作數據也是如此,比如i = i+1;其中就包括,讀取i的值,計算i,寫入i。這行代碼在Java中是不具備原子性的,則多線程運行肯定會出問題,所以也需要我們使用同步和lock這些東西來確保這個特性了。 

原子性其實就是保證數據一致、線程安全一部分,

什麼是可見性

當多個線程訪問同一個變數時,一個線程修改了這個變數的值,其他線程能夠立即看得到修改的值。

若兩個線程在不同的cpu,那麼線程1改變了i的值還沒刷新到主存,線程2又使用了i,那麼這個i值肯定還是之前的,線程1對變數的修改線程沒看到這就是可見性問題。 

什麼是有序性

程式執行的順序按照代碼的先後順序執行。

一般來說處理器為了提高程式運行效率,可能會對輸入代碼進行優化,它不保證程式中各個語句的執行先後順序同代碼中的順序一致,但是它會保證程式最終執行結果和代碼順序執行的結果是一致的。如下:

int a = 10;    //語句1

int r = 2;    //語句2

a = a + 3;    //語句3

r = a*a;     //語句4

則因為重排序,他還可能執行順序為 2-1-3-4,1-3-2-4

但絕不可能 2-1-4-3,因為這打破了依賴關係。

顯然重排序對單線程運行是不會有任何問題,而多線程就不一定了,所以我們在多線程編程時就得考慮這個問題了。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 函數式編程是一種解決問題的思路。我們熟悉的命令式編程把程式看作”一系列改變狀態的指令“;而函數式編程把程式看作”一系列數學函數映射的組合“。 ...
  • 最近轉入零售行業開發了一系列產品,包含便利店收銀軟體、會員系統、供應鏈系統。為了追趕潮流,收銀軟體使用了electron平臺開發,界面效果、開發效率確實不錯;但是涉及到串口通訊時遇到了麻煩,electron不能直接使用node.js的串口模塊。網上有一些文章推薦編譯electron源碼來實現串口模塊 ...
  • 一,效果圖。 二,代碼。 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Todo</title> <meta name="viewport" content="initial-scale=1, maximum-scale=1, ...
  • 線程池 ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory thre ...
  • 1.工廠模式 工廠模式是我們最常用的實例化對象模式了,是用工廠方法代替new操作的一種模式。著名的Jive論壇 ,就大量使用了工廠模式,工廠模式在Java程式系統可以說是隨處可見。因為工廠模式就相當於創建實例對象的new,我們經常要根據類Class生成實例對象,如A a=new A() 工廠模式也是 ...
  • 前言 之前學多線程的時候沒有學習線程的同步工具類(輔助類)。ps:當時覺得暫時用不上,認為是挺高深的知識點就沒去管了.. 在前幾天,朋友發了一篇比較好的Semaphore文章過來,然後在瀏覽博客的時候又發現面試還會考,那還是挺重要的知識點。於是花了點時間去瞭解一下。 Java為我們提供了 三個同步工 ...
  • #include #include //提供malloc()原型 /* 線性表需要的方法: 1、 List MakeEmpty():初始化一個空線性表 2、 EementType FindKey(int K, List L):根據位序K,返回相應元素 3、 int Find(ElementType ... ...
  • 前言 這次分析信號量Semaphore,為什麼稱之為信號量呢?是因為它可以控制同時訪問某個資源的操作數量或是同時執行某個指定操作的數量。就好比它像一個租賃汽車的公司,租賃公司的汽車的數量是固定的,用完需要歸還,用之前需要去租借(acquire 前提是還有可用的汽車),如果汽車都被租出去了,那隻能等到 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...