多線程安全(一)

来源: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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...