Synchronized和Volatile的區別

1.多線程三大特性?

原子性、可見性、有序性
(1)什麼是原子性?

        即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。
一個很經典的例子就是銀行賬戶轉賬問題:
比如從賬戶A向賬戶B轉1000元,那麼必然包括2個操作:從賬戶A減去1000元,往賬戶B加上1000元。這2個操作必須要具備原子性才能保證不出現一些意外的問題。
        我們操作數據也是如此,比如i = i+1;其中就包括,讀取i的值,計算i,寫入i。這行代碼在Java中是不具備原子性的,則多線程運行肯定會出問題,所以也需要我們使用同步和lock這些東西來確保這個特性了。
原子性其實就是保證數據一致、線程安全一部分.

(2) 什麼是可見性

        當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
        若兩個線程在不同的cpu,那麼線程1改變了i的值還沒刷新到主存,線程2又使用了i,那麼這個i值肯定還是之前的,線程1對變量的修改線程沒看到這就是可見性問題。
這是產生線程不安全最主要的問題,假如我們有A,B兩個線程共享同一變量,變量放在方法區,也就是主內存,JVM體系結構中堆和方法區是線程共享的,A,B線程存放着主內存變量的副本,即各自對自己所在線程的主內存變量的副本讀寫,假如變量是10,A對變量進行減1,這時值爲9,但是沒來及刷新到主內存,B保存的是變量的副本10,B這時也進行減1,這時值也爲9,導致數據不一致。
具體原理可以參考:java內存模型

(3)什麼是有序性

程序執行的順序按照代碼的先後順序執行。
一般來說處理器爲了提高程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行先後順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。 如下:
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,因爲這打破了依賴關係。
顯然重排序對單線程運行是不會有任何問題,而多線程就不一定了,所以我們在多線程編程時就得考慮這個問題了。

2.Synchronized關鍵字的兩種用法?

說明:Java提供了一種內置的鎖機制來支持原子性
每一個Java對象都可以用作一個實現同步的鎖,稱爲內置鎖,線程進入同步代碼塊之前自動獲取到鎖,代碼塊執行完成正常退出或代碼塊中拋出異常退出時會釋放掉鎖
內置鎖爲互斥鎖,即線程A獲取到鎖後,線程B阻塞直到線程A釋放鎖,線程B才能獲取到同一個鎖
        內置鎖使用synchronized關鍵字實現,synchronized關鍵字有兩種用法:
        1.修飾需要進行同步的方法(所有訪問狀態變量的方法都必須進行同步),此時充當鎖的對象爲調用同步方法的對象
        2.同步代碼塊和直接使用synchronized修飾需要同步的方法是一樣的,但是鎖的粒度可以更細,並且充當鎖的對象不一定是this,也可以是其它對象,所以使用起來更加靈活
同步代碼塊synchronized
就是將可能會發生線程安全問題的代碼,給包括起來。

synchronized(同一個數據){
 可能會發生線程衝突問題
}
就是同步代碼塊 
synchronized(對象)//這個對象可以爲任意對象 
{ 
    需要被同步的代碼 
} 

if (flag) {
            while (trainCount > 0) {
                synchronized (oj) {
                    try {
                        Thread.sleep(10);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                    if (trainCount > 0) {
                        System.out
                                .println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票");
                        trainCount--;
                    }
                }

            }
        } 

synchronized修飾方法
1.在方法上修飾synchronized 稱爲同步方法,使用的是this鎖
2.方法上加上static關鍵字,使用synchronized 關鍵字修飾 稱爲靜態同步方法,使用鎖是當前類的字節碼文件,可以用 getClass方法獲取,也可以用當前 類名.class 表示。

證明同步方法使用的是this鎖,this指樣例中的SaleTickets對象

/**
 * @author Administrator
 */
class SaleTickets implements Runnable {
    private int trainTickets = 100;
    private Object obj = new Object();
    private boolean flag = true;


    @Override
    public void run() {
        if (flag) {
            while (trainTickets > 0) {
                synchronized (obj) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (trainTickets > 0) {
                        System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainTickets + 1) + "張票");
                        trainTickets--;
                    }
                }
            }
        } else {
            while (trainTickets > 0) {
                sale();
            }
        }
    }

    public synchronized void sale() {
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (trainTickets > 0) {
            System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainTickets + 1) + "張票");
            trainTickets--;
        }
    }

    public static class Test {
        public static void main(String[] args) throws Exception {
            SaleTickets saleTickets = new SaleTickets();
            Thread thread1 = new Thread(saleTickets, "1號窗口");
            Thread thread2 = new Thread(saleTickets, "2號窗口");
            thread1.start();
            Thread.sleep(40);
            saleTickets.flag = false;
            thread2.start();
        }
    }
}

此時synchronized (obj),鎖爲任意對象;
在這裏插入圖片描述

此時synchronized (this),鎖爲this;
在這裏插入圖片描述
總結: 同步代碼塊爲obj鎖時發生線程不安全,爲this鎖時線程安全,證明同步方法爲this鎖

Volatile關鍵字用法?

可見性也就是說一旦某個線程修改了該被volatile修飾的變量,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,可以立即獲取修改之後的值。
在Java中爲了加快程序的運行效率,對一些變量的操作通常是在該線程的寄存器或是CPU緩存上進行的,之後纔會同步到主存中,而加了volatile修飾符的變量則是直接讀寫主存。
Volatile 保證了線程間共享變量的及時可見性,但不能保證原子性
在這裏插入圖片描述

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