java -- 線程(二)

来源:https://www.cnblogs.com/paopaoT/archive/2023/04/14/17319628.html
-Advertisement-
Play Games

項目介紹與環境搭建 1.項目學習前置知識 Java基礎知識 javaweb MySQL SpringBoot SSM(Spring,SpringMVC,MyBatis) Maven 2.學習收穫 瞭解企業項目開發的完整流程,增長開發經驗 瞭解需求分析的過程,提高分析和設計能力 對所學的技術進行靈活應 ...


死鎖

死鎖是指兩個或兩個以上的線程在執行過程中,由於競爭同步鎖而產生的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的線程稱為死鎖。

死鎖的案例 : 同步代碼塊的嵌套
創建鎖對象:

public class Lock {
    public static final Lock lockA = new Lock();
    public static final  Lock lockB = new Lock();
}

測試類:

public class DeadLockTest {
    public static void main(String[] args) {
        while(true){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (Lock.lockA){
                        System.out.println("getlockA...");
                        synchronized (Lock.lockB){
                            System.out.println("getlockB...");
                        }
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (Lock.lockB){
                        System.out.println("getlockB...");
                        synchronized (Lock.lockA){
                            System.out.println("getlockA...");
                        }
                    }
                }
            }).start();
        }
    }
}

生產者與消費者

創建2個線程,一個線程表示生產者,另一個線程表示消費者

import java.util.ArrayList;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        Object o = new Object();
        // 生產者
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (o) {
                        if (list.size() > 0) {
                            try {
                                o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        list.add("aaaa");
                        System.out.println(list);
                        // 喚醒消費線程
                        o.notify();
                    }
                }
            }
        }).start();

        // 消費者
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (o) {
                        if (list.size() == 0) {
                            try {
                                o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        list.remove(0);
                        System.out.println(list);
                        o.notify();
                    }
                }
            }
        }).start();

    }
}

線程方法sleep和wait的區別

  • sleep()是Thread類靜態方法,不需要對象鎖。
  • wait()方法是Object類的方法,被鎖對象調用,而且只能出現在同步中。
  • 執行sleep()方法的線程不會釋放同步鎖。
  • 執行wait()方法的線程要釋放同步鎖,被喚醒後還需獲取鎖才能執行。

案例性能問題

wait()方法和notify()方法, 本地方法調用OS的功能,和操作系統交互,JVM找OS,把線程停止. 頻繁等待與喚醒,導致JVM和OS交互的次數過多.

Condition介面

java.util.concurrent.locks.Condition 是一個介面類, 因此要使用其實現類創建對象
Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象
以便通過將這些對象與任意 Lock 實現組合使用,為每個對象提供多個等待 set(wait-set)
其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用

// Condition常用方法:
public void await() // 線程等待
public void signal() // 喚醒一個等待的線程
public void singalAll() // 喚醒所有等待的線程
// 使用其實現類 ReentrantLock 的 newCondition方法獲取 Condition
public Condition newCondition()
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        // 創建Lock
        Lock l = new ReentrantLock();

        // 獲取 Condition 對象
        Condition con = l.newCondition();

        new Thread(new Runnable() {
            @Override
            public void run() {
                l.lock();
                System.out.println("開始等待");
                try {
                    con.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    l.unlock();
                }
            }
        }).start();

        Thread.sleep(2000);

        System.out.println("準備喚醒");
        l.lock();
        con.signal();
        l.unlock();
    }
}

Condition介面方法和Object類方法比較

  • Condition可以和任意的Lock組合,也就是實現了線程的分組管理。
    • 一個線程的案例中,可以使用多個Lock鎖,每個Lock鎖上可以結合Condition對象
    • synchronized同步中做不到線程分組管理
  • Object類wait()和notify()都要和操作系統交互,並通知CPU掛起線程,喚醒線程,效率低。
  • Condition介面方法await()不和操作系統交互,而是讓線程釋放鎖,並存放到線程隊列容器中,當被signal()喚醒後,從隊列中出來,從新獲取鎖後在執行。
  • 因此使用Lock和Condition的效率比Object要快很多

生產者和消費者案例改進

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class OverWriteWakeUpWaiting {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Lock l = new ReentrantLock();

        // 對線程進行分組管理
        Condition con1 = l.newCondition(); // 生產線程, 對象監視器
        Condition con2 = l.newCondition(); // 消費線程, 對象監視器

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    l.lock();
                    if (list.size() > 0) {
                        try {
                            con1.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    list.add("abc");
                    System.out.println(list);
                    con2.signal();
                    l.unlock();
                }
            }
        }).start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    l.lock();
                    if (list.size() == 0) {
                        try {
                            con2.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    list.remove(0);
                    System.out.println(list);
                    con1.signal();
                    l.unlock();
                }
            }
        }).start();
    }
}

java併發編程的三大特性

原子性

原子性,即一個操作或多個操作,要麼全部執行並且在執行的過程中不被打斷,要麼全部不執行
下麵具有原子性的操作有?

x = 1;
// x = 1,是一個單純的賦值操作,滿足原子性。
y=x;
// 實際是兩個操作,分別是 讀取x變數 ,將x賦值給y,這兩個操作分別來看都是原子性的,但是合起來就不是了
x++;
// 實際是三個操作 ,先讀取變數 ,在進行+1操作 ,再賦值給x,不滿足原子性
x=x+1;
// 同上,不滿足原子性

JAVA提供了原子性的技術保障有如下:

1、synchronized (互斥鎖)
2、Lock(互斥鎖)
3、原子類(CAS)
synchronized 和 Lock 都是通過互斥鎖實現,即同一時刻只允許一個線程操作該變數,保障了原子性

原子類AtomicInteger

/*
    java.util.concurrent.atomic.AtomicInteger
    構造方法
        public AtomicInteger()創建具有初始值 0 的新 AtomicInteger。
        public AtomicInteger(int initialValue) 創建具有給定初始值的新 AtomicInteger。
      方法
        int incrementAndGet()  以原子方式將當前值加 1。
        int getAndIncrement()  以原子方式將當前值加 1。

        int decrementAndGet()  以原子方式將當前值減 1。
        int getAndIncrement()  以原子方式將當前值減 1。


        int getAndAdd(int delta)  以原子方式將給定值與當前值相加。
        int addAndGet(int delta)  以原子方式將給定值與當前值相加。
        int get() 獲取當前值。
 */
public class Test02 {
    public static void main(String[] args) {
        AtomicInteger  ai = new AtomicInteger(1);

        ai.incrementAndGet(); //++ai    2
        ai.getAndIncrement(); //ai++    3


        System.out.println(ai.get());// 3
        System.out.println(ai.getAndIncrement()); // 3
        System.out.println(ai.get()); // 4
        System.out.println(ai.incrementAndGet()); // 5
        System.out.println(ai.get()); //5     
    }
}

CAS無鎖機制

CAS是英文單詞Compare And Swap的縮寫,翻譯過來就是比較並替換。當多條線程嘗試使用CAS同時更新同一個變數時,只有其中一條線程能更新變數的值,而其他線程都失敗,失敗的線程並不會被掛起,而是告知這次競爭失敗,並可以再次嘗試.
CAS的缺點:

CAS雖然很高效的解決了原子操作問題,但是CAS仍然存在三大問題。

  1. 迴圈時間長開銷很大。

    CAS 通常是配合無限迴圈一起使用的,如果 CAS 失敗,會一直進行嘗試。如果 CAS 長時間一直不成功,可能會給 CPU 帶來很大的開銷。
    
  2. 只能保證一個變數的原子操作。

    當對一個變數執行操作時,我們可以使用迴圈 CAS 的方式來保證原子操作,但是對多個變數操作時,CAS 目前無法直接保證操作的原子性。
    
  3. ABA問題。

    第一條線程獲取到V位置的值  假設是 1
    第二條線程獲取到V位置的值  也是1
    第一條線程cas成功 將值改為 0
    第一條線程又cas成功 將值改回 1
    這時第二條線程cas 發現值沒變 還是1 cas成功   
    實際上當第二條線程cas時 V位置的值已經從 1-0-1
    這就是ABA問題 
    如何解決 每次獲取V位置的值時,帶上一個版本號.這樣就可以避免ABA問題 java中AtomicStampedReference這個類在cas時就是通過版本號來解決的
    

可見性

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

public class Test {
    public static  boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1號線程啟動....執行while迴圈");

                long num = 0;
                while(flag){
                    num++;
                }

                System.out.println(num);
            }
        }).start();

        Thread.sleep(2000);

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("2號線程啟動....修改flag的值為false,停止迴圈");
                flag = false;
            }
        }).start();

    }
}

通過如上案例 發現修改flag 的值並沒有使迴圈結束

1.加鎖,比如使用synchronized.

JMM關於synchronized的兩條規定:
  1)線程解鎖前,必須把共用變數的最新值刷新到主記憶體中
  2)線程加鎖時,將清空工作記憶體中共用變數的值,從而使用共用變數時需要從主記憶體中重新獲取最新的值
public class Test {

    //使用同步方法獲取flag的值
    public static synchronized boolean getFlag(){
        return flag;
    }

    public static  boolean flag = true;
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1號線程啟動....執行while迴圈");
                long num = 0;
                    /*
                        線程調用getFlag方法時 先獲取鎖 也就是加鎖
                        這時會先清空本地記憶體中共用副本的值,那麼在使用值就需要從
                        主記憶體中重新獲取 ,線程釋放鎖時,也就是解鎖,會把共用變數flag
                        的值重新更新到主記憶體中
                     */
                    while(getFlag()){
                        num++;
                    }
                System.out.println(num);
            }
        }).start();

        Thread.sleep(2000);

        new Thread(new Runnable() {
            @Override
            public void run() {

                System.out.println("2號線程啟動....修改flag的值為false,停止迴圈");
                flag = false;
            }
        }).start();
    }
}

2.使用volatile關鍵字保證可見性

public class Test {
    public static volatile boolean flag = true;
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1號線程啟動....執行while迴圈");
                long num = 0;

                    while(flag){
                        num++;
                    }
                System.out.println(num);
            }
        }).start();
        Thread.sleep(2000);
        new Thread(new Runnable() {
            @Override
            public void run() {

                System.out.println("2號線程啟動....修改flag的值為false,停止迴圈");
                flag = false;
            }
        }).start();
    }
}

volatile緩存可見性實現原理

底層實現主要是通過彙編lock首碼指令,會鎖住這塊區域的緩存,並寫回主記憶體.

1.會將當前處理器緩存的行數據立即寫回系統記憶體

2.這個寫回記憶體的操作導致CPU的緩存該記憶體地址的數值失效(MESI協議)

volatile只能保證可見性,但是不能保證原子性,如果要保證原子性,請使用鎖

有序性

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

int a = 10; 	//語句1
int b = 20;     //語句2
int c = 20;     //語句3
c= a + b;  //語句4

CPU可能會對沒有依賴關係的語句進行重排,比如 2134,3124 但是不會對有依賴關係的數據進行重排比如 3和4 改為4和3 這樣就會對結果造成影響.這種重排對單線程是沒有任何影響的,但是如果是多線程就可能會出現問題.

驗證CPU是否會進行指令重排:

public class Test {

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 500000; i++) {
            Test.State state = new Test.State();

            ThreadA t1 = new ThreadA(state);
            ThreadB t2 = new ThreadB(state);

            t1.start();
            t2.start();

        }

    }


    static class ThreadA extends Thread{
        private final Test.State  state;
        ThreadA(Test.State state){
            this.state =state;
        }


        public void run(){
            state.a=1;
            state.b=1;
            state.c=1;
            state.d=1;


        }

    }
    static class ThreadB extends Thread{
        private final Test.State  state;
        ThreadB(Test.State state){
            this.state =state;
        }


        public void run(){

            if( state.b== 1 && state.a ==0){
                System.out.println("b= " + state.b);
            }

            if(state.c == 1 &&(state.b==0|| state.a ==0)){
                System.out.println("c = " + state.c);
            }

            if(state.d==1 &&(state.a==0||state.b==0||state.c==0)){
                System.out.println("d " + state.d);
            }
        }

    }

    static  class  State{
        int a = 0;
        int b = 0;
        int c = 0;
        int d = 0;
    }
}
/*
c = 1
說明,CPU進行了重排,讓c在b或者a前面進行了賦值.
改變順序可能導致執行結果不同,因此需要禁止重排序。
*/

使用volatile關鍵字後 就不會出現剛纔的情況

static  class  State{
        volatile int  a = 0;
        volatile int b = 0;
        volatile int c = 0;
        volatile int d = 0;
 }

由此可見:volatile關鍵字有兩個作用1.保證可見性.2禁止重排序

單例設計模式

設計模式 : 不是技術,是以前的人開發人員,為瞭解決某些問題實現的寫代碼的經驗.
Java的設計模式有23種,分為3個類別,創建型,行為型,功能型
單例代表單個實例,保證一個類的對象永遠只有一個!

餓漢式

優點: 簡單 多線程下沒有任何問題
缺點:

  • 當類載入時 對象就會被直接創建
  • 若不被使用 對象就白創建了
public class danliDemo1 {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Single1.getInstance());
                }
            }).start();
        }
    }
}
class Single1 {
    private static Single1 s = new Single1();

    public Single1() {
    }

    public static Single1 getInstance(){
        return s;
    }

}

懶漢式

優點: 延遲載入 什麼時候調用方法 什麼時候創建對象
缺點: 多線程時 代碼有問題

public class danliDemo2 {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Single2.getInstance());
                }
            }).start();
        }
    }
}
class Single2 {
    private static Single2 s;
    public Single2() { }

    public static Single2 getInstance(){

        if (s == null) {
            s = new Single2();
        }
        return s;
    }
}

安全問題

一個線程判斷完變數 s=null,還沒有執行new對象,被另一個線程搶到CPU資源,同時有2個線程都進行判斷變數,對象創建多次

性能問題

第一個線程獲取鎖,創建對象,返回對象. 第二個線程調用方法的時候,變數s已經有對象了,根本就不需要在進同步,不要在判斷空,直接return才是最高效的.
雙重的if判斷,提高效率 Double Check Lock(DCL)
DCL雙檢查鎖機制單例,效率高,線程安全,多線程操作原子性。

class Single2 {
    private static Single2 s;
    public Single2() { }

    public static Single2 getInstance(){

        if (s == null) {
            synchronized (Single2DCL.class) {
                if (s == null) {
                    s = new Single2DCL();
                }
            }
        }
        return s;
    }
}

面試題

DCL單例是否需要使用volatile關鍵字?
需要,單例的模式, 不使用volatile關鍵字,可能線程會拿到一個尚未初始化完成的對象(半初始化)


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

-Advertisement-
Play Games
更多相關文章
  • Flask-SQLAlchemy MySQL是免費開源軟體,大家可以自行搜索其官網(https://www.MySQL.com/downloads/) 測試MySQL是否安裝成功 在所有程式中,找到MySQL→MySQL Server 5.6下麵的命令行工具,然後單擊輸入密碼後回車,就可以知道MyS ...
  • 前言 在上一篇文章中,我們介紹了~運算符的高級用法,本篇文章,我們將介紹<< 運算符的一些高級用法。 一、人物簡介 第一位閃亮登場,有請今後會一直教我們C語言的老師 —— 自在。 第二位上場的是和我們一起學習的小白程式猿 —— 逍遙。 二、計算2的整數次冪 代碼示例 #include <stdio. ...
  • 布隆過濾器是一個精巧而且經典的數據結構。 你可能沒想到: RocketMQ、 Hbase 、Cassandra 、LevelDB 、RocksDB 這些知名項目中都有布隆過濾器的身影。 對於後端程式員來講,學習和理解布隆過濾器有很大的必要性。來吧,我們一起品味布隆過濾器的設計之美。 1 緩存穿透 我 ...
  • Java + Jpcap實現監控 IP包流量 說明:本設計是電腦網路課程的課設,因為代碼是提前實現的,本博客於後期補上,又因為代碼沒寫註釋自己也看不懂了,所以,僅供參考,就當提供一種實現方式。 文中提供的《Jpcap中文API文檔》來源於網路,本文僅用於學習交流,如有侵權,可聯繫我進行刪除。 效果 ...
  • 是什麼 迴圈隊列, FIFO先進先出 怎麼用 初始化 //C11 deque<int> deq{1,2,3,4,5}; //拷貝構造,可以拷貝deque queue<int> que(deq); //100個5 queue<int> que2(100,5); //運算符重載 que2 = que; ...
  • 集合的理解和好處 數組一旦定義,長度即固定,不能修改。要添加新元素需要新建數組,然後迴圈拷貝,非常麻煩 集合可以動態保存任意多個對象,使用比較方便 提供餓了一系列方便的操作對象的方法:add、remove、set、get等 使用集合添加、刪除新元素的示意代碼,簡潔明瞭 集合主要是兩組(單列集合,雙列 ...
  • 本文已經收錄到Github倉庫,該倉庫包含電腦基礎、Java基礎、多線程、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構、校招社招分享等核心知識點,歡迎star~ Github地址 如果訪問不了Github,可以訪 ...
  • 繼承(Inheritance) Odoo的一個強大方面是它的模塊化。模塊專用於業務需求,但模塊也可以相互交互。這對於擴展現有模塊的功能非常有用。例如,在我們的房地產場景中,我們希望在常規用戶視圖中直接顯示銷售人員的財產列表。 在介紹特定的Odoo模塊繼承之前,讓我們看看如何更改標準CRUD(創建、檢 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...