Java 多線程:synchronized 關鍵字用法(修飾類,方法,靜態方法,代碼塊)

前言


在多線程生成的緣由(Java內存模型與i++操做解析) 中,介紹了Java的內存模型,從而可能致使的多線程問題。synchronized就是避免這個問題的解決方法之一。除了 synchronized 的方式,還有 lock,condition,volatile,threadlocal,atomicInteger,cas等方式。安全

synchronized 用法


它的修飾對象有幾種:多線程

  1. 修飾一個類,其做用的範圍是synchronized後面括號括起來的部分,做用的對象是這個類的全部對象。
  2. 修飾一個方法,被修飾的方法稱爲同步方法,其做用的範圍是整個方法,做用的對象是調用這個方法的對象;
  3. 修改一個靜態的方法,其做用的範圍是整個靜態方法,做用的對象是這個類的全部對象;
  4. 修飾一個代碼塊,被修飾的代碼塊稱爲同步語句塊,其做用的範圍是大括號{}括起來的代碼,做用的對象是調用這個代碼塊的對象;

修飾一個類


其做用的範圍是synchronized後面括號括起來的部分,做用的對象是這個類的全部對象,以下代碼:併發

class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}

修飾一個方法


Synchronized修飾一個方法很簡單,就是在方法的前面加synchronized,例如:函數

public synchronized void method()
{
   // todo
}

另外,有幾點須要注意:ui

  1. 在定義接口方法時不能使用synchronized關鍵字。
  2. 構造方法不能使用synchronized關鍵字,但可使用synchronized代碼塊來進行同步。
  3. synchronized 關鍵字不能被繼承。若是子類覆蓋了父類的 被 synchronized 關鍵字修飾的方法,那麼子類的該方法只要沒有 synchronized 關鍵字,那麼就默認沒有同步,也就是說,不能繼承父類的 synchronized。

修飾靜態方法


咱們知道靜態方法是屬於類的而不屬於對象的。一樣的,synchronized修飾的靜態方法鎖定的是這個類的全部對象。以下:this

public synchronized static void method() {
   // todo
}

修飾代碼塊


  • 當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程獲得執行。另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。
  • 當一個線程訪問object的一個synchronized(this)同步代碼塊時,另外一個線程仍然能夠訪問該object中的非synchronized(this)同步代碼塊。
  • 尤爲關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對object中全部其它synchronized(this)同步代碼塊的訪問將被阻塞。
  • 第三個例子一樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就得到了這個object的對象鎖。結果,其它線程對該object對象全部同步代碼部分的訪問都被暫時阻塞。
  • 以上規則對其它對象鎖一樣適用.

JDK中對 synchronized 的使用舉例


對於 synchronized ,我的以爲是一種比較粗糙的加鎖,尤爲是對整個對象,或者整個類進行加鎖的時候。例如,HashTable,它至關於 HashMap的線程安全版本。實現以下:atom

public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

能夠看到,不少方法都是使用了 synchronized 進行了修飾,那麼就意味若是還有別的同步方法x,這個x方法和get方法即便在沒有衝突的狀況下也須要等待執行。這樣效率上 必然會有必定的影響,因此會有 ConcurrentHashMap 進行分段加鎖。線程

另外,在JDK中,Collections有一個方法能夠把不是線程安全的集合轉化爲線性安全的集合,它是這樣實現的:code

public static <T> Collection<T> synchronizedCollection(Collection<T> c) {
        return new SynchronizedCollection<>(c);
    }
 static class SynchronizedCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 3053995032091335093L;

        final Collection<E> c;  // Backing Collection
        final Object mutex;     // Object on which to synchronize

        SynchronizedCollection(Collection<E> c) {
            this.c = Objects.requireNonNull(c);
            mutex = this;
        }

        SynchronizedCollection(Collection<E> c, Object mutex) {
            this.c = Objects.requireNonNull(c);
            this.mutex = Objects.requireNonNull(mutex);
        }

        public int size() {
            synchronized (mutex) {return c.size();}
        }

        ......

能夠看到 在構造函數中 有 mutex = this, 而後在具體的方法使用了 synchronized(mutex),這樣就會對調用該方法的對象進行加鎖。仍是會出現HashTable 出現的那種問題,也就是效率不高。對象

以上就是 JDK 對於 synchronized 用法的簡單舉例。

相關文章
相關標籤/搜索