Java併發—synchronized關鍵字

synchronized關鍵字的做用是線程同步,而線程的同步是爲了防止多個線程訪問一個數據對象時,對數據形成的破壞。java

 

synchronized用法

一、 在須要同步的方法的方法簽名中加入synchronized關鍵字

synchronized public void getValue() {
    ...
}

上面的代碼修飾的synchronized是非靜態方法,若是修飾的是靜態方法(static)含義是徹底不同的。具體不同在哪裏,後面會詳細說清楚。 ide

synchronized static public void getValue() {
    ...
}

 

二、使用synchronized塊對須要進行同步的代碼段進行同步。 

複製代碼
public void synchronizedMethod() {
        try {
            synchronized (this) {
                System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                Thread.sleep(2000);
                System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
}
複製代碼

上面的代碼塊是synchronized (this)用法,還有synchronized (非this對象)以及synchronized (類.class)這兩種用法,這些使用方式的含義也是有根本的區別的。咱們先帶着這些問題繼續往下看。性能

 

對象鎖與類鎖

synchronized關鍵字的使用大體有五種狀況,其中三種是對象鎖,兩種是類鎖:this

  • synchronized修飾非靜態方法、同步代碼塊的synchronized (this)用法和synchronized (非this對象)的用法鎖的是對象鎖。
  • synchronized修飾靜態方法以及同步代碼塊的synchronized (類.class)用法鎖的是類鎖。

 

下面看一些例子,首先看一下線程不一樣步的狀況:spa

複製代碼
public class SynchronizedTest {
    public void synchronizedMethod() {
        try {
                System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                Thread.sleep(2000);
                System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class MyThread extends Thread {
    private SynchronizedTest synchronizedTest;

    public MyThread(SynchronizedTest synchronizedTest) {
        super();
        this.synchronizedTest = synchronizedTest;
    }

    @Override
    public void run() {
        super.run();
        synchronizedTest.synchronizedMethod();
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        SynchronizedTest synchronizedTest1 = new SynchronizedTest();

        Thread a = new MyThread(synchronizedTest1);
        a.setName("a");
        a.start();

        Thread b = new MyThread(synchronizedTest1);
        b.setName("b");
        b.start();
    }
}
複製代碼

運行結果:線程

Thread[a,5,main]begin at:2017-09-13 16:52:54
Thread[b,5,main]begin at:2017-09-13 16:52:54
Thread[a,5,main]end at:2017-09-13 16:52:56
Thread[b,5,main]end at:2017-09-13 16:52:56

 能夠看到兩個線程交叉執行,要讓這兩個線程依次執行,則須要使用對象鎖同步,能夠將SynchronizedTest類修改爲下面的三種方式來添加對象鎖:code

複製代碼
public class SynchronizedTest {
    synchronized public void synchronizedMethod() {
        try {
                System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                Thread.sleep(2000);
                System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
或者
public class SynchronizedTest {
    public void synchronizedMethod() {
        try {
            synchronized (this) {
                System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                Thread.sleep(2000);
                System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
或者
public class SynchronizedTest {
    
    Object object = new Object();
    
    public void synchronizedMethod() {
        try {
            synchronized (object) {
                System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                Thread.sleep(2000);
                System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

運行結果:orm

Thread[a,5,main]begin at:2017-09-13 16:59:12
Thread[a,5,main]end at:2017-09-13 16:59:14
Thread[b,5,main]begin at:2017-09-13 16:59:14
Thread[b,5,main]end at:2017-09-13 16:59:16

從上面能夠看出,synchronized代碼塊(後兩種方式)使用起來比synchronized方法(第一種方式)要靈活得多。由於也許一個方法中只有一部分代碼只須要同步,若是此時對整個方法用synchronized進行同步,會影響程序執行效率。而使用synchronized代碼塊就能夠避免這個問題,synchronized代碼塊能夠實現只對須要同步的地方進行同步。 對象

 

若是將Main類修改爲下面這樣,則對象鎖失效:blog

複製代碼
public class Main {
    public static void main(String[] args) throws InterruptedException {
        SynchronizedTest synchronizedTest1 = new SynchronizedTest();
        SynchronizedTest synchronizedTest2 = new SynchronizedTest();

        Thread a = new MyThread(synchronizedTest1);
        a.setName("a");
        a.start();

        Thread b = new MyThread(synchronizedTest2);
        b.setName("b");
        b.start();
    }
}
複製代碼

運行結果:

Thread[b,5,main]begin at:2017-09-13 17:03:26
Thread[a,5,main]begin at:2017-09-13 17:03:26
Thread[b,5,main]end at:2017-09-13 17:03:28
Thread[a,5,main]end at:2017-09-13 17:03:28

由於上面兩個線程調用的是兩個對象中的方法,對象鎖是不起做用的,這種狀況下應該使用類鎖,能夠將SynchronizedTest類修改爲下面的兩種方式來添加類鎖:

複製代碼
public class SynchronizedTest {
    public void synchronizedMethod() {
        try {
            synchronized (SynchronizedTest.class) {
                System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                Thread.sleep(2000);
                System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
或者
public class SynchronizedTest {
    synchronized public static void synchronizedMethod() {
        try {
                System.out.println(Thread.currentThread()+"begin at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                Thread.sleep(2000);
                System.out.println(Thread.currentThread()+"end at:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

運行結果:

Thread[a,5,main]begin at:2017-09-13 17:07:02
Thread[a,5,main]end at:2017-09-13 17:07:04
Thread[b,5,main]begin at:2017-09-13 17:07:04
Thread[b,5,main]end at:2017-09-13 17:07:06

 

須要特別說明:

對於同一個類A,線程1爭奪A對象實例的對象鎖,線程2爭奪類A的類鎖,這二者不存在競爭關係。也就說對象鎖和類鎖互不干預。

靜態方法則必定會同步,非靜態方法需在單例模式才生效,可是也不能都用靜態同步方法,總之用得很差可能會給性能帶來極大的影響。另外,有必要說一下的是Spring的bean默認是單例的。

相關文章
相關標籤/搜索