日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

并发编程之多线程线程安全(上)

發布時間:2023/12/10 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 并发编程之多线程线程安全(上) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1、為什么有線程安全問題?

當多個線程共享同一個全局變量或靜態變量,做寫的操作時,可能會發生數據沖突問題,也就是線程安全問題。但是做讀操作是不會發生數據沖突問題。

案例:現在有100張火車票,有兩個窗口同時搶火車票,請使用多線程模擬搶票效果。

代碼:

public?class?NewThread1?implements?Runnable{

????private?int?trainCount?=?100;

????@Override
????public?void?run()?{

????????while?(trainCount>0){
????????????try?{
????????????????Thread.sleep(100);
????????????}catch?(Exception?e){
????????????}
????????????save();
????????}

????}

????private?void?save()?{
????????if?(trainCount?>?0)?{
????????????System.out.println(Thread.currentThread().getName()?+?",出售第"?+?(100?-?trainCount?+?1)?+?"張票");
????????????trainCount--;
????????}
????}

????public?static?void?main(String[]?args){
????????NewThread1?newThread1?=?new?NewThread1();
????????Thread?thread1?=?new?Thread(newThread1,"①");
????????Thread?thread2?=?new?Thread(newThread1,"②");
????????thread1.start();
????????thread2.start();
????}
}

打印結果:

......
②,出售第97張票
①,出售第97張票
①,出售第99張票
②,出售第99張票

一號窗口和二號窗口同時出售火車第九九張,部分火車票會重復出售。

結論發現,多個線程共享同一個全局成員變量時,做寫的操作可能會發生數據沖突問題。

2、線程安全解決方法

2.1、如何解決多線程之間線程安全問題

兩種主流方式:

  • 內置鎖:使用多線程之間同步 synchronized 關鍵字
  • 顯示鎖:lock鎖
  • 內置鎖:

    內置鎖使用 synchronized 關鍵字實現,synchronized 關鍵字有兩種用法:

  • 修飾需要進行同步的方法(所有訪問狀態變量的方法都必須進行同步),此時充當鎖的對象為調用同步方法的對象

  • 同步代碼塊和直接使用 synchronized 修飾需要同步的方法是一樣的,但是鎖的粒度可以更細,并且充當鎖的對象不一定是 this ,也可以是其它對象,所以使用起來更加靈活。

  • 簡而言之,synchronize 分為同步方法、同步代碼塊。

    其中同步方法又分為:非靜態同步方法、靜態同步方法

    同步方法 [非靜態同步方法] :

    private?synchronized?void?save()?{
    ????if?(trainCount?>?0)?{
    ????????System.out.println(Thread.currentThread().getName()?+?",出售第"?+?(100?-?trainCount?+?1)?+?"張票");
    ????????trainCount--;
    ????}
    }

    同步代碼塊:

    就是將可能會發生線程安全問題的代碼,給包括起來。
    synchronized(同一個數據){
    ????可能會發生線程沖突問題
    }
    就是同步代碼塊?
    synchronized(對象)這個對象可以為任意對象?
    {?
    ????需要被同步的代碼?
    }?

    對象如同鎖,持有鎖的線程可以在同步中執行;

    沒持有鎖的線程即使獲取CPU的執行權,也進不去 ;

    同步的前提:

  • 必須要有兩個或者兩個以上的線程
  • 必須是多個線程使用同一個鎖
  • 必須保證同步中只能有一個線程在運行
  • 同步的優缺點:

    • 好處:解決了多線程的安全問題。
    • 弊端:多線程需要判斷鎖,較為消耗資源、搶鎖的資源。

    代碼:

    private?void?save()?{
    ????synchronized?(this){
    ????????if?(trainCount?>?0)?{
    ????????????System.out.println(Thread.currentThread().getName()?+?",出售第"?+?(100?-?trainCount?+?1)?+?"張票");
    ????????????trainCount--;
    ????????}
    ????}
    }

    關于 Lock 鎖下次再寫。

    2.2、為什么使用線程同步或使用鎖能解決線程安全問題呢?

    為了避免線程不安全問題,每次只讓當前一個線程進行執行,代碼執行完成后執行釋放鎖操作,然后才讓其他線程進行執行,這樣就解決了線程安全問題。

    2.3、什么是多線程同步

    當多個線程共享同一個資源,不會受到其他線程的干擾。

    2.4、什么是同步方法?

    在方法上修飾 sybchronized 稱為同步方法。

    代碼示例:

    private?synchronized?void?save()?{
    ????if?(trainCount?>?0)?{
    ????????System.out.println(Thread.currentThread().getName()?+?",出售第"?+?(100?-?trainCount?+?1)?+?"張票");
    ????????trainCount--;
    ????}
    }
    2.5、什么是靜態同步函數?

    方法加上 static 關鍵字,使用 synchronize 關鍵字修飾 或者使用 類.class 文件。

    靜態的同步函數使用的鎖是該函數所屬字節碼文件對象。

    可以使用 getClass 方法獲取,也可以用當前 類名.class 表示。

    代碼示例:

    private?static?void?save()?{
    ????synchronized?(NewThread1.class){
    ????????if?(trainCount?>?0)?{
    ????????????System.out.println(Thread.currentThread().getName()?+?",出售第"?+?(100?-?trainCount?+?1)?+?"張票");
    ????????????trainCount--;
    ????????}
    ????}
    }

    總結:

    synchronize 修飾方法使用鎖是當前 this 鎖。

    synchronize 修飾靜態方法使用鎖是當前類的字節碼文件。

    3、ThreadLocal

    什么是 ThreadLocal ?

    ThreadLocal 提高一個線程的局部變量,訪問某個線程擁有自己局部變量。

    當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。

    ThreadLocal 的 4 個方法:

  • void set(Object value):設置當前線程的線程局部變量的值。
  • public Object get():該方法返回當前線程所對應的線程局部變量。
  • public void remove():將當前線程局部變量的值刪除,目的是為了減少內存的占用,需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量并不是必須的操作,但它可以加快內存回收的速度。
  • protected Object initialValue():返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,并且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。
  • 案例:

    創建三個線程,每個線程生成自己獨立序列號。

    class?Res{

    ????private?ThreadLocal<Integer>?mThreadLocal?=?new?ThreadLocal<Integer>(){
    ????????@Override
    ????????protected?Integer?initialValue()?{
    ????????????return?0;
    ????????};
    ????};

    ????protected?Integer?getNumber()?{
    ????????int?count?=?mThreadLocal.get()?+?1;
    ????????mThreadLocal.set(count);
    ????????return?count;
    ????};

    }

    public?class?ThreadDemo1?extends?Thread{

    ????private?Res?res;

    ????public?ThreadDemo1(Res?res){
    ????????this.res?=?res;
    ????}

    ????@Override
    ????public?void?run()?{
    ????????for?(int?i?=?0;?i?<?3;?i++)?{
    ????????????System.out.println(Thread.currentThread().getName()+","+res.getNumber());
    ????????}
    ????}

    ????public?static?void?main(String[]?args){
    ????????Res?res?=?new?Res();
    ????????ThreadDemo1?threadDemo1?=?new?ThreadDemo1(res);
    ????????ThreadDemo1?threadDemo2?=?new?ThreadDemo1(res);
    ????????ThreadDemo1?threadDemo3?=?new?ThreadDemo1(res);
    ????????threadDemo1.start();
    ????????threadDemo2.start();
    ????????threadDemo3.start();
    ????}
    }

    4、本章總結

    線程安全的兩種主流方式:

  • 內置鎖:synchronized
  • 顯示鎖:lock鎖
  • ThreadLocal 提高一個線程的局部變量,訪問某個線程擁有自己局部變量。

    我創建了一個java相關的公眾號,用來記錄自己的學習之路,感興趣的小伙伴可以關注一下微信公眾號哈:niceyoo

    總結

    以上是生活随笔為你收集整理的并发编程之多线程线程安全(上)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。