<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • Java多線程之線程同步【synchronized、Lock、volatitle】

    image

    線程同步

    線程同步:當有一個線程在對內存進行操作時,其他線程都不可以對這個內存地址進行操作,直到該線程完成操作, 其他線程才能對該內存地址進行操作,而其他線程又處于等待狀態,實現線程同步的方法有很多。

    為什么要創建多線程?

    在一般情況下,創建一個線程是不能提高程序的執行效率的,所以要創建多個線程。

    • 為什么要線程同步

      • 多個線程同時運行的時候可能調用線程函數,在多個線程同時對同一個內存地址進行寫入,由于CPU時間調度上的問題,寫入數據會被多次的覆蓋,所以就要使線程同步。

      • 例如:我們去銀行存錢,那肯定是我們銀行卡里原本的錢加上要存入的錢。但是在你存錢的同時你的朋友在給你的銀行卡轉錢,這是兩個線程,這兩個線程同時拿到了銀行卡的本金,那么這兩個線程最后都會返回一個總金額,那這兩個總金額都是不正確的,只有這兩次交易有一個先后順序才行,這就是線程同步的一個原因。

    線程同步是意思

    • 同步就是協同步調,按預定的先后次序進行運行。如:你做完,我再做
      • 錯誤理解:“同”字從字面上容易理解為一起動作,其實不是,“同”字應是指協同、協助、互相配合。
      • 正確理解: 所謂同步,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不返回,同時其它線程也不能調用這個方法。按照這個定義,其實絕大多數函數都是同步調用。但是一般而言,我們在說同步、異步的時候,特指那些需要其他部件協作或者需要一定時間完成的任務。
      • 在多線程編程里面,一些敏感數據不允許被多個線程同時訪問,此時就使用同步訪問技術,保證數據在任何時刻,最多有一個線程訪問,以保證數據的完整性。

    線程同步作用

    • 線程有可能和其他線程共享一些資源,比如,內存,文件,數據庫等。
    • 當多個線程同時讀寫同一份共享資源的時候,可能會引起沖突。這時候,我們需要引入線程“同步”機制,即各位線程之間要有個先來后到,不能一窩蜂擠上去搶作一團。
    • 線程同步的真實意思和字面意思恰好相反。線程同步的真實意思,其實是“排隊”:幾個線程之間要排隊,一個一個對共享資源進行操作,而不是同時進行操作

    基本上所有解決線程安全問題的方式都是采用“序列化臨界資源訪問”的方式,即在同一時刻只有一個線程操作臨界資源,操作完了才能讓其他線程進行操作,也稱作同步互斥訪問。

    在Java中一般采用synchronized和Lock來實現同步互斥訪問。

    常用方法

    Synchronized關鍵字

    首先我們先來了解一下互斥鎖,
    互斥鎖:就是能達到互斥訪問目的的鎖。

    如果對一個變量加上互斥鎖,那么在同一時刻,該變量只能有一個線程能訪問,即當一個線程訪問臨界資源時,其他線程只能等待。

    在Java中,每一個對象都有一個鎖標記(monitor),也被稱為監視器,當多個線程訪問對象時,只有獲取了對象的鎖才能訪問。

    在我們編寫代碼的時候,可以使用synchronized修飾對象的方法或者代碼塊,當某個線程訪問這個對象synchronized方法或者代碼塊時,就獲取到了這個對象的鎖,這個時候其他對象是不能訪問的,只能等待獲取到鎖的這個線程執行完該方法或者代碼塊之后,才能執行該對象的方法。

    synchronized添加到代碼塊位置

    我們先寫一個不加Synchronized的多線程代碼,這段代碼是創建兩個線程,這段代碼是創建兩個線程分別輸出五個數。
    多次運行可以發現結果每次不一樣,這就導致了不確定性。我們給他加上同步方法會發現一個輸出完之后另一個才會輸出,這就可以空值共享資源不能同時被兩個線程得到。

    package hello;
    
    public class Hello {
    
        public static void main(String[] args) throws Exception {
            MyThread myThread = new MyThread();
            Thread thread = new Thread(myThread);
            Thread thread1 = new Thread(myThread);
            thread.start();
            thread1.start();
        }
    }
    
    class OutputData{
        //定義輸出方法
        public void output(Thread thread){
            for (int i=0;i<5;i++){
                System.out.println(thread.getName()+":"+"輸出"+i);
            }
        }
    }
    
    class MyThread implements Runnable{
        OutputData inserData = new OutputData();
    
        public void run(){
            inserData.output(Thread.currentThread());
        }
    }
    
    直接添加到方法上

    我們在OutputData類里面加入synchronized之后在運行就可以看到結果每次都是0123401234

    class OutputData{
        //定義輸出方法
        public synchronized void output(Thread thread){
            for (int i=0;i<5;i++){
                System.out.println(thread.getName()+":"+"輸出"+i);
            }
        }
    }
    
    添加在代碼塊

    其實上面的代碼還可以這樣加,這個里面和上面的原理是一樣的。

    class OutputData{
        //定義輸出方法
        public void output(Thread thread){
            //this就是當前對象
            synchronized (this) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(thread.getName() + ":" + "輸出" + i);
                }
            }
        }
    }
    

    釋放鎖時機

    如果一個方法或者代碼塊被synchronized關鍵字修飾,當線程獲取到該方法或代碼塊的鎖,其他線程是不能繼續訪問該方法或代碼塊的。
    而其他線程要能訪問該方法或代碼塊,就必須要等待獲取到鎖的線程釋放這個鎖,而在這里釋放鎖只有兩種情況:

    • 線程執行完代碼塊,自動釋放鎖;
    • 程序報錯,jvm讓線程自動釋放鎖。

    Lock鎖同步

    上面我們已經說了synchronized鎖釋放的時機
    但是可能會有一種情況,當一個線程獲取到對象的鎖,然后在執行過程中因為一些原因(等待IO,調用sleep方法)被阻塞了,這個時候鎖還在被阻塞的線程手中,而其他線程這個時候除了等之外,沒有任何辦法,我們想一想這樣子會有多影響程序的效率。因此就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。

    在比如,當多個線程操作同一個文件的時候,同時讀寫是會沖突的,同時寫也是會沖突的,但是同時讀是不會發生沖突的,而我們如果用synchronized來實現同步,就會出現一個問題:

    如果多個線程都只是進行讀操作,所以當一個線程在進行讀操作時,其他線程只能等待無法進行讀操作。
    因此就需要一種機制來使得多個線程都只是進行讀操作時,線程之間不會發生沖突,而通過Lock就可以辦到。
    總的來說Lock要比synchronized提供的功能更多,可定制化的程度也更高,Lock不是Java語言內置的,而是一個類。

    方法解釋

    先看一下Lock接口的方法
    image

    • lock()、tryLock()和lockInterruptibly()方法是用來獲取鎖的
    • unlock()方法是用來釋放鎖的。
    • tryLock()顧名思義,是用來嘗試獲取鎖的,并且該方法有返回值,表示獲取成功與否,獲取成功返回true,失敗返回false,從方法可以發現,該方法如果沒有獲取到鎖時不會繼續等待的,而是會直接返回值。
    • tryLock()的重載方法tryLock(long time, TimeUnit unit)功能類似,只是這個方法會等待一段時間獲取鎖,如果過了等待時間還未獲取到鎖就會返回false,如果在等待時間之內拿到鎖則返回true。

    首先lock()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他線程獲取,則進行等待。

    由于在前面講到如果采用Lock,必須主動去釋放鎖,并且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,并且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。

    一般格式:

            Lock lock = ...;
            lock.lock();
            try {
                //處理任務
            }catch (Exception e){
                //捕捉異常
            }finally{
                lock.unlock();
            }
    

    使用Lock.lock()獲取鎖

    package hello;
    
    import javax.sound.sampled.FloatControl;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Hello {
    
    
        public static void main(String[] args) {
            Lock lock = new ReentrantLock();
    
            OutputData outputData = new OutputData();
            new Thread(){
                public void run(){
                    outputData.output(Thread.currentThread(),lock);
                };
            }.start();
    
    
            new Thread(){
                public void run(){
                    outputData.output(Thread.currentThread(),lock);
                };
            }.start();
    
    
        }
    }
    
    
    class OutputData{
        public void output(Thread thread,Lock lock){
            lock.lock();
            try {
                System.out.println(thread.getName()+"得到了鎖");
                Thread.sleep(100);//加這個睡眠是為了結果效果明顯
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                System.out.println(thread.getName()+"釋放了鎖");
                lock.unlock();
            }
    
        }
    }
    

    使用tryLock()獲取鎖

    把OutPutData類里面的output方法修改一下就可以了

    class OutputData{
        public void output(Thread thread,Lock lock){
            if(lock.tryLock()){
                try {
                    System.out.println(thread.getName()+"得到了鎖");
                    Thread.sleep(100);//加這個睡眠是為了結果效果明顯
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    System.out.println(thread.getName()+"釋放了鎖");
                    lock.unlock();
                }
            }else{
                System.out.println(thread.getName()+"獲取鎖失敗");
            }
    
        }
    }
    
    

    volatile同步實現

    volatile含義和特點
    我們知道每個線程運行的時候都有自己的工作內存,會把變量拷貝到自己的緩存中去,一般情況下你在自己緩存修改的變量不會立即重新寫入主內存,這就導致類多線程同步問題,那么volatile關鍵字的特點是:

    • 當一個共享變量被volatile修飾時,它就具備了“可見性”,即這個變量被一個線程修改時,這個改變會立即被其他線程知道。就是你在這個線程修改了這個變量,另外的線程會立刻知道,也改變。
    • 當一個共享變量被volatile修飾時,會禁止“指令重排序”

    volatile關鍵字會產生什么效果

    • 使用volatile關鍵字會強制將變量的修改的值立即寫至主內存;
    • 使用volatile關鍵字,當線程對某個變量(這個變量定義為V1)修改時,會強制將所有用到變量V1的線程對應的緩存中V1的緩存行置為無效。
    • 由于線程的V1緩存行無效,所以在運行時線程會讀取主存中V1變量的值。
    • 所以到最后線程讀取到的就是V1最新的值

    volatile應用示例

    這段代碼可以看出我們并沒給有加鎖,但是這個int變量sum還是按順序加的,說明他在改變之后立即就把主內存里的變量改變了。也算是一種同步方式吧。

    package hello;
    
    public class Hello {
    
    
        public static void main(String[] args) {
            AddClass addClass = new AddClass();
            new Thread() {
                public void run(){
                    for (int j=0;j<100;j++){
                        addClass.add();
                        System.out.println(Thread.currentThread().getName()+":"+addClass.sum);
                    }
                }
            }.start();
            new Thread(){
                public void run(){
                    for (int j=0;j<100;j++){
                        addClass.add();
                        System.out.println(Thread.currentThread().getName()+":"+addClass.sum);
                    }
                }
            }.start();
        }
    }
    
    class  AddClass{
        public volatile  int sum = 0;
        public void add(){
            sum++;
        }
    }
    
    

    多線程同步小應用

    火車站買票

    package hello;
    
    public class Hello {
    
    
        public static void main(String[] args) {
                //實例化站臺對象,
                Station station1=new Station();
                Station station2=new Station();
                Station station3=new Station();
    
                // 讓每一個站臺對象各自開始工作
                station1.start();
                station2.start();
                station3.start();
            }
        }
    
    
    class Station extends Thread  {
        static volatile int p = 20;
        static Object ob = new Object();
        public void run() {
            while (p > 0) {
                synchronized (ob) {
                    if (p > 0) {
                        System.out.println("賣出了第" + p + "張票");
    
                        p = p - 1;
                    }
                }
                synchronized (this) {
                    if (p == 0) {
                        System.out.println("票賣完了");
                    }
                }
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
    
                }
            }
    
        }
    }
    
    
    
    posted @ 2022-03-24 08:09  hjk-airl  閱讀(354)  評論(0編輯  收藏  舉報
    国产美女a做受大片观看