Synchronized關鍵字

synchronized關鍵字的使用

多線程的不安全性,所以有了鎖的概念,在ThreadA操作對象時,其他線程都不能操作此對象,待ThreadA釋放鎖後,其他線程中的某一個線程才被允許操作此對象。
synchronized是JVM層面的鎖,因爲synchronized是關鍵字,JVM封裝了他所具有的功能。
下面我們就看一看synchronized的使用
1、修飾實例方法
2、修飾靜態方法
3、修飾代碼塊

	//靜態方法
  	public static synchronized void incr(){
        count ++;
    }
    //實例方法
    public synchronized void incr2(){
        count ++;
    }
    //代碼塊
    public void incr3(){
        synchronized (SyncDemo.class){
            count ++;
        }
    }

synchroinzed的使用總結:

  • 兩種作用範圍(類鎖 / 對象鎖)
  • 兩種表現形式(方法上 / 代碼塊)
    區別:跨對象跨線程訪問

兩種特性:共享條件(鎖共享) / 互斥條件(代碼塊執行互斥)

線程給我們帶來的好處

多線程可以提高現在多核多線程CPU的利用率。
多線程可以幫助我們更好的優化程序,提高程序的運行效率。

多線程的不安全性

既要保證效率又要保證安全的時代,線程的不安全性問題比較突出。多個線程同時訪問一個共享變量時,就可能達不到想要的預期。

public class SyncDemo {

    private static int count = 0;

    public static void incr(){
        count ++;
    }

    public static void main(String[] args) throws InterruptedException {
        
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                SyncDemo.incr();
            }).start();
        }
        
        TimeUnit.SECONDS.sleep(1);
        //不等於1000
        System.out.println(count); 
    }
}

鎖的升級

JDK1.6之前synchronized鎖屬於重量級鎖,重量級鎖會掛起等待線程,掛起CPU開銷比較大,所以在JDK1.6後做了優化,引入了偏向鎖、輕量級鎖的概念。注意鎖智能升級不能降級。

鎖的升級順序如下:
無鎖 >> 偏向鎖 >> 輕量級鎖 >> 重量級鎖
假如現在有兩個線程ThreadA / ThreadB

  1. 只用ThreadA線程去訪問 – 偏向鎖

  2. ThreadA和ThreadB兩個線程交替訪問 – 輕量級鎖

  3. 多個線程同時訪問 – 重量級鎖

    偏向鎖和輕量級鎖相當無沒有鎖,爲什麼鎖這兩種鎖沒有鎖呢?

偏向鎖

偏向鎖可以使用JVM參數去關閉,因爲在現實場景中很少會出現這種情況。
偏向鎖在JVM中的實現:
線程ThreadA首次獲得鎖對象的時候,會修改鎖對象中的對象頭,
記錄一下:現在鎖的狀態是偏向鎖和自己的線程ID,待ThreadA再次訪問時,就不用再獲得鎖了,可以直接執行被鎖的代碼塊。
在這裏插入圖片描述

輕量級鎖

絕大多部分情況在線程獲得鎖以後,在非常短的時間內會釋放鎖,線程交替等待時,使用自旋獲得鎖。
在這裏插入圖片描述
自旋會佔用CPU資源,所以在指定的自選次數之後,如果還沒有獲得輕量級鎖,鎖會膨脹成重量級鎖 (BLOCKED 阻塞狀態)

輕量級鎖是怎麼存儲的呢?
ThreadA線程在獲得鎖後,會創建以個線程棧幀存儲鎖的對象頭,然後修改對象頭中存儲該線程棧幀的hash值標識自己。如果ThradB獲得鎖後,也是同樣。
在這裏插入圖片描述

重量級鎖

沒有獲得到鎖的線程會被阻塞,等待CPU再次調度。

爲什麼重量級鎖會比較消耗CPU?

wait、notify、notifyAll對線程的影響

wait 實現線程的阻塞、會釋放當前同步鎖
notify/notifyAll會喚醒線程

ThreadA

public class ThreadA extends Thread{

    private Object lock = null;

    public ThreadA(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("ThreadA start");

            try {
               //ThreadA.sleep(222222222);
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("ThreadA end");
        }
    }
}

ThreadB

public class ThreadB extends Thread{

    private Object lock = null;

    public ThreadB(  Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("ThreadB start");

            lock.notify();


            System.out.println("ThreadB end");
        }
    }
}

共用同一把鎖

public class WaitNotifyTest {

    private static Object lock = new Object();

    public static void main(String[] args) {
        ThreadA threadA  = new ThreadA(lock);
        ThreadB threadB  = new ThreadB(lock);
        threadA.start();
        threadB.start();
    }
}

輸出結果:
ThreadA start
ThreadB start
ThreadB end
ThreadA end

ThradA獲得鎖 ——> .wait()後阻塞,釋放鎖 ——>ThreadB獲得鎖  ——> 喚醒ThreadA  ——> ThreadB釋放鎖  ——> ThreadA重新獲得鎖執行代碼

wait和sleep
wait會釋放鎖資源,並且釋放CPU
sleep不會釋放鎖資源,但是會出讓CPU

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章