Java多線程學習(二)synchronized關鍵字(2)

系列文章傳送門:java

Java多線程學習(一)Java多線程入門git

Java多線程學習(二)synchronized關鍵字(1)程序員

java多線程學習(二)synchronized關鍵字(2) github

Java多線程學習(三)volatile關鍵字面試

Java多線程學習(四)等待/通知(wait/notify)機制編程

Java多線程學習(五)線程間通訊知識點補充緩存

Java多線程學習(六)Lock鎖的使用微信

Java多線程學習(七)併發編程中一些問題多線程

系列文章將被優先更新於微信公衆號<font color="red">「Java面試通關手冊」</font>,歡迎廣大Java程序員和愛好技術的人員關注。併發

(2) synchronized同步語句塊

本節思惟導圖:
思惟導圖

思惟導圖源文件+思惟導圖軟件關注微信公衆號:「Java面試通關手冊」回覆關鍵字:「Java多線程」免費領取。

一 synchronized方法的缺點

使用<font color="red">synchronized關鍵字</font>聲明方法有些時候是有很大的弊端的,好比咱們有兩個線程一個線程A調用同步方法後得到鎖,那麼另外一個線程B就須要等待A執行完,可是若是說A執行的是一個很費時間的任務的話這樣就會很耗時。

先來看一個<font color="red">暴露synchronized方法的缺點實例</font>,而後在看看如何經過synchronized同步語句塊解決這個問題。

<font size="2">Task.java</font>

public class Task {

    private String getData1;
    private String getData2;

    public synchronized void doLongTimeTask() {
        try {
            System.out.println("begin task");
            Thread.sleep(3000);
            getData1 = "長時間處理任務後從遠程返回的值1 threadName="
                    + Thread.currentThread().getName();
            getData2 = "長時間處理任務後從遠程返回的值2 threadName="
                    + Thread.currentThread().getName();
            System.out.println(getData1);
            System.out.println(getData2);
            System.out.println("end task");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

<font size="2">CommonUtils.java</font>

public class CommonUtils {

    public static long beginTime1;
    public static long endTime1;

    public static long beginTime2;
    public static long endTime2;
}

<font size="2">MyThread1.java</font>

public class MyThread1 extends Thread {
    private Task task;
    public MyThread1(Task task) {
        super();
        this.task = task;
    }
    @Override
    public void run() {
        super.run();
        CommonUtils.beginTime1 = System.currentTimeMillis();
        task.doLongTimeTask();
        CommonUtils.endTime1 = System.currentTimeMillis();
    }
}

<font size="2">MyThread2.java</font>

public class MyThread2 extends Thread {
    private Task task;
    public MyThread2(Task task) {
        super();
        this.task = task;
    }
    @Override
    public void run() {
        super.run();
        CommonUtils.beginTime2 = System.currentTimeMillis();
        task.doLongTimeTask();
        CommonUtils.endTime2 = System.currentTimeMillis();
    }
}

<font size="2">Run.java</font>

public class Run {

    public static void main(String[] args) {
        Task task = new Task();

        MyThread1 thread1 = new MyThread1(task);
        thread1.start();

        MyThread2 thread2 = new MyThread2(task);
        thread2.start();

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long beginTime = CommonUtils.beginTime1;
        if (CommonUtils.beginTime2 < CommonUtils.beginTime1) {
            beginTime = CommonUtils.beginTime2;
        }

        long endTime = CommonUtils.endTime1;
        if (CommonUtils.endTime2 > CommonUtils.endTime1) {
            endTime = CommonUtils.endTime2;
        }

        System.out.println("耗時:" + ((endTime - beginTime) / 1000));
    }
}

<font size="2">運行結果:</font>
運行結果
從運行時間上來看,synchronized方法的問題很明顯。能夠<font color="red">使用synchronized同步塊來解決這個問題</font>。可是要注意synchronized同步塊的使用方式,若是synchronized同步塊使用很差的話並不會帶來效率的提高。

二 synchronized(this)同步代碼塊的使用

修改上例中的Task.java以下:

public class Task {

    private String getData1;
    private String getData2;

    public void doLongTimeTask() {
        try {
            System.out.println("begin task");
            Thread.sleep(3000);

            String privateGetData1 = "長時間處理任務後從遠程返回的值1 threadName="
                    + Thread.currentThread().getName();
            String privateGetData2 = "長時間處理任務後從遠程返回的值2 threadName="
                    + Thread.currentThread().getName();

            synchronized (this) {
                getData1 = privateGetData1;
                getData2 = privateGetData2;
            }
            
            System.out.println(getData1);
            System.out.println(getData2);
            System.out.println("end task");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

<font size="2">運行結果:</font>
運行結果
從上面代碼能夠看出<font color="red">當一個線程訪問一個對象的synchronized同步代碼塊時,另外一個線程任然能夠訪問該對象非synchronized同步代碼塊</font>。

時間雖然縮短了,可是你們考慮一下synchronized代碼塊真的是同步的嗎?它真的持有當前調用對象的鎖嗎?

<font color="red">是的。不在synchronized代碼塊中就異步執行,在synchronized代碼塊中就是同步執行。</font>

驗證代碼:synchronizedDemo1包下

三 synchronized(object)代碼塊間使用

<font size="2">MyObject.java</font>

public class MyObject {
}

<font size="2">Service.java</font>

public class Service {

    public void testMethod1(MyObject object) {
        synchronized (object) {
            try {
                System.out.println("testMethod1 ____getLock time="
                        + System.currentTimeMillis() + " run ThreadName="
                        + Thread.currentThread().getName());
                Thread.sleep(2000);
                System.out.println("testMethod1 releaseLock time="
                        + System.currentTimeMillis() + " run ThreadName="
                        + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

<font size="2">ThreadA.java</font>

public class ThreadA extends Thread {

    private Service service;
    private MyObject object;

    public ThreadA(Service service, MyObject object) {
        super();
        this.service = service;
        this.object = object;
    }

    @Override
    public void run() {
        super.run();
        service.testMethod1(object);
    }
}

<font size="2">ThreadB.java</font>

public class ThreadB extends Thread {
    private Service service;
    private MyObject object;

    public ThreadB(Service service, MyObject object) {
        super();
        this.service = service;
        this.object = object;
    }

    @Override
    public void run() {
        super.run();
        service.testMethod1(object);
    }

}

<font size="2"> Run1_1.java</font>

public class Run1_1 {

    public static void main(String[] args) {
        Service service = new Service();
        MyObject object = new MyObject();

        ThreadA a = new ThreadA(service, object);
        a.setName("a");
        a.start();

        ThreadB b = new ThreadB(service, object);
        b.setName("b");
        b.start();
    }
}

<font size="2">運行結果:</font>
運行結果
能夠看出以下圖所示,<font color="red">兩個線程使用了同一個「對象監視器」,因此運行結果是同步的。</font>
同一個對象監視器
<font color="red">那麼,若是使用不一樣的對象監視器會出現什麼效果呢?</font>

修改Run1_1.java以下:

public class Run1_2 {

    public static void main(String[] args) {
        Service service = new Service();
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();

        ThreadA a = new ThreadA(service, object1);
        a.setName("a");
        a.start();

        ThreadB b = new ThreadB(service, object2);
        b.setName("b");
        b.start();
    }
}

<font size="2">運行結果:</font>
運行結果:
能夠看出以下圖所示,<font color="red">兩個線程使用了不一樣的「對象監視器」,因此運行結果不是同步的了。</font>
不一樣的對象監視器

四 synchronized代碼塊間的同步性

當一個對象訪問synchronized(this)代碼塊時,其餘線程對同一個對象中全部其餘synchronized(this)代碼塊代碼塊的訪問將被阻塞,這說明<font color="red">synchronized(this)代碼塊使用的「對象監視器」是一個。</font>
也就是說<font color="red">和synchronized方法同樣,synchronized(this)代碼塊也是鎖定當前對象的。</font>

另外經過上面的學習咱們能夠得出<font color="red">兩個結論</font>。

  1. <font color="red">其餘線程執行對象中synchronized同步方法(上一節咱們介紹過,須要回顧的能夠看上一節的文章)和synchronized(this)代碼塊時呈現同步效果;</font>
  2. <font color="red">若是兩個線程使用了同一個「對象監視器」,運行結果同步,不然不一樣步.</font>

五 靜態同步synchronized方法與synchronized(class)代碼塊

<font color="red">synchronized關鍵字加到static靜態方法和synchronized(class)代碼塊上都是是給Class類上鎖,而synchronized關鍵字加到非static靜態方法上是給對象上鎖。</font>

<font size="2">Service.java</font>

package ceshi;

public class Service {

    public static void printA() {
        synchronized (Service.class) {
            try {
                System.out.println(
                        "線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printA");
                Thread.sleep(3000);
                System.out.println(
                        "線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    synchronized public static void printB() {
        System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printB");
        System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printB");
    }

    synchronized public void printC() {
        System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printC");
        System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printC");
    }

}

<font size="2">ThreadA.java</font>

public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.printA();
    }
}

<font size="2">ThreadB.java</font>

public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.printB();
    }
}

<font size="2">ThreadC.java</font>

public class ThreadC extends Thread {
    private Service service;
    public ThreadC(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.printC();
    }
}

<font size="2">Run.java</font>

public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();

        ThreadC c = new ThreadC(service);
        c.setName("C");
        c.start();
    }
}

<font size="2">運行結果:</font>
運行結果
從運行結果能夠看出:靜態同步synchronized方法與synchronized(class)代碼塊持有的鎖同樣,都是Class鎖,Class鎖對對象的全部實例起做用。synchronized關鍵字加到非static靜態方法上持有的是對象鎖。

線程A,B和線程C持有的鎖不同,因此A和B運行同步,可是和C運行不一樣步。
實例代碼:

六 數據類型String的常量池屬性

<font color="red">在Jvm中具備String常量池緩存的功能</font>

String s1 = "a";
    String s2="a";
    System.out.println(s1==s2);//true

上面代碼輸出爲true.<font color="red">這是爲何呢?</font>

字符串常量池中的字符串只存在一份! 即執行完第一行代碼後,常量池中已存在 「a」,那麼s2不會在常量池中申請新的空間,而是直接把已存在的字符串內存地址返回給s2。

由於數據類型String的常量池屬性,因此synchronized(string)在使用時某些狀況下會出現一些問題,好比兩個線程運行
synchronized("abc"){
}和
synchronized("abc"){
}修飾的方法時,這兩個線程就會持有相同的鎖,致使某一時刻只有一個線程能運行。因此儘可能不要使用synchronized(string)而使用synchronized(object)

參考:

《Java多線程編程核心技術》
《Java併發編程的藝術》

若是你以爲博主的文章不錯,歡迎轉發點贊。你能從中學到知識就是我最大的幸運。

歡迎關注個人微信公衆號:「Java面試通關手冊」(分享各類Java學習資源,面試題,以及企業級Java實戰項目回覆關鍵字免費領取)。另外我建立了一個Java學習交流羣(羣號:174594747),歡迎你們加入一塊兒學習,這裏更有面試,學習視頻等資源的分享。

相關文章
相關標籤/搜索