3. Java多線程同步

Java Synchronized 塊

Synchronized塊是用來解決race condition。Synchronized塊是基于對(duì)象,作用在同一對(duì)象的同步快保證在同一時(shí)間只有一個(gè)線程處理。Synchronized關(guān)鍵字可以作用于一下四種情況:

  • 實(shí)例方法
  • 靜態(tài)方法
  • 實(shí)例方法內(nèi)的代碼塊
  • 靜態(tài)方法內(nèi)的代碼塊

Synchronized Instance Methods

  public synchronized void add(int value){
      this.count += value;
  }

實(shí)例方法加synchronized是同步的其擁有該方法的對(duì)象。如果有多個(gè)實(shí)例對(duì)象,那么每個(gè)對(duì)象在同一時(shí)間都只能有一個(gè)線程執(zhí)行同步方法。

Synchronized Static Methods

  public static synchronized void add(int value){
      this.count += value;
  }

靜態(tài)方法加synchronized是同步其擁有該方法的類。由于一個(gè)JVM里面只有一個(gè)類,所以在同一個(gè)類中只能有一個(gè)線程執(zhí)行同步方法。

Synchronized Blocks in Instance Methods

  public void add(int value){
    synchronized(this){
       this.count += value;   
    }
  }

實(shí)例方法內(nèi)的代碼塊通過指定加鎖對(duì)象(monitor object)來進(jìn)行同步。有時(shí)候不需要對(duì)整個(gè)方法加鎖,只需要對(duì)方法內(nèi)的一部分加鎖,可以使用該方法。

Synchronized Blocks in Static Methods

  public class MyClass {

    public static synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }

  
    public static void log2(String msg1, String msg2){
       synchronized(MyClass.class){
          log.writeln(msg1);
          log.writeln(msg2);  
       }
    }
  }

靜態(tài)方法內(nèi)的代碼塊同步的是擁有該方法的類。在上述例子中,一個(gè)線程在同一時(shí)刻只能執(zhí)行一個(gè)方法。如果log2同步快內(nèi)加鎖的是不同的類,那么一個(gè)線程可以同時(shí)執(zhí)行這兩個(gè)方法。

Java Volatile關(guān)鍵字

volatile關(guān)鍵字保證變量的可見性

非volatile變量讀取方法

多線程應(yīng)用在處理非volatile變量的時(shí)候,先把變量的值拷貝一份放到cpu cache里面,以此提高效率。

volatile變量保證每次從main memory里面讀取變量最新的值,每次修改以后將變量的值更新到main memory里面。Java5以后的volatile關(guān)鍵字提供了Happens Before Guarantee

  • 如果線程A寫了一個(gè)volatile變量,接著線程B讀取了同一個(gè)volatile變量。那么線程A更新volatile變量之前的所有變量,在線程B讀取volatile變量之后,都是可見的。
  • 讀寫volatile變量的操作不能被JVM重新排序,但是volatile變量之前和之后的操作可以被重新排序。
public class Exchanger {

    private Object   object       = null;
    private volatile hasNewObject = false;

    public void put(Object newObject) {
        while(hasNewObject) {
            //wait - do not overwrite existing new object
        }
        object = newObject;
        hasNewObject = true; //volatile write
    }

    public Object take(){
        while(!hasNewObject){ //volatile read
            //wait - don't take old object (or null)
        }
        Object obj = object;
        hasNewObject = false; //volatile write
        return obj;
    }
}

可以利用這個(gè)特性,不用每個(gè)變量都加volatile。上面的例子中,如果只有一個(gè)線程執(zhí)行put(), 另外一個(gè)線程執(zhí)行take(),那么不需要同步塊也能保證線程安全。

sharedObject.nonVolatile1 = 123;
sharedObject.nonVolatile2 = 456;
sharedObject.nonVolatile3 = 789;

sharedObject.volatile     = true; //a volatile variable

int someValue1 = sharedObject.nonVolatile4;
int someValue2 = sharedObject.nonVolatile5;
int someValue3 = sharedObject.nonVolatile6;

volatile保證了sharreObject.volatile的順序不被重排,但是前3個(gè)變量和后面3個(gè)變量的執(zhí)行順序可能被JVM重排。

僅僅volatile是不夠的

只要一個(gè)線程需要先讀一個(gè)volatile變量,然后在需要在這個(gè)變量值的基礎(chǔ)之上生成一個(gè)新的值,那么volatile變量不再試線程安全的。
當(dāng)有多個(gè)線程對(duì)volatile變量進(jìn)行寫操作的時(shí)候,就有可能造成race condition。

volatile變量不保證線程安全

什么時(shí)候volatile變量是可靠的

當(dāng)一個(gè)線程進(jìn)行volatile變量的讀寫,另外一個(gè)變量只進(jìn)行volatile變量的讀的時(shí)候,變量是線程安全的額。

volatile變量的性能

由于volatile變量需要從main memory里面讀取數(shù)據(jù),多以性能比直接從cpu 緩存中讀取數(shù)據(jù)要高。多以在必要的時(shí)候再使用volatile關(guān)鍵字。

ThreadLocal

ThreadLocal類允許生成一個(gè)只能被一個(gè)線程進(jìn)行讀寫的變量。即使兩個(gè)線程執(zhí)行同一段代碼,兩個(gè)線程也不能看到對(duì)方的ThreadLocal變量

public class ThreadLocalExample {

    public static class MyRunnable implements Runnable {

        private ThreadLocal<Integer> threadLocal =
               new ThreadLocal<Integer>();

        @Override
        public void run() {
            threadLocal.set( (int) (Math.random() * 100D) );
    
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }
    
            System.out.println(threadLocal.get());
        }
    }


    public static void main(String[] args) {
        MyRunnable sharedRunnableInstance = new MyRunnable();

        Thread thread1 = new Thread(sharedRunnableInstance);
        Thread thread2 = new Thread(sharedRunnableInstance);

        thread1.start();
        thread2.start();

        thread1.join(); //wait for thread 1 to terminate
        thread2.join(); //wait for thread 2 to terminate
    }

}

在上面的例子中,雖然是同一個(gè)ThreadLocal成員變量,但是兩個(gè)的值取出來是不相同的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,815評(píng)論 11 349
  • 一、引言 前幾天面試,被大師虐殘了,好多基礎(chǔ)知識(shí)必須得重新拿起來啊。閑話不多說,進(jìn)入正題。 二、為什么要線程同步 ...
    coffee_0ca0閱讀 549評(píng)論 0 0
  • Java-Review-Note——4.多線程 標(biāo)簽: JavaStudy PS:本來是分開三篇的,后來想想還是整...
    coder_pig閱讀 1,772評(píng)論 2 17
  • 每想你一次 天空飄落一粒沙 于是形成了撒哈拉 ...
    柒玖九閱讀 617評(píng)論 5 7
  • 手機(jī)里下載了個(gè)百度網(wǎng)盤的App,平時(shí)拍攝或下載的圖片,在網(wǎng)絡(luò)環(huán)境下實(shí)時(shí)上傳云端,手機(jī)進(jìn)水或主版燒壞,再也不擔(dān)心歷史...
    初刻杰閱讀 286評(píng)論 0 0

友情鏈接更多精彩內(nèi)容