Java多線程學習(三)——synchronized(下)

synchronized同步語句塊

用關鍵字synchronized聲明方法是有弊端的。好比線程A調用同步方法執行一個長時間任務,那麼線程B就要等較長時間才能調用。java

下面看一個例子:git

public class Task {

    private String getData1;

    private String getData2;

    public synchronized void longTimeTask(){
        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){
            e.printStackTrace();
        }
    }

}
複製代碼
public class Utils {
    public static long begainTime1;
    public static long endTime1;
    public static long begainTime2;
    public static long endTime2;


}
複製代碼
public class MyThread extends Thread{
    private Task task;
    private String name;

    public MyThread(Task task, String name){
        super();
        this.task=task;
        this.name=name;
        super.setName(name);
    }

    @Override
    public void run() {
        super.run();
        if ("A".equals(name)){
            Utils.begainTime1 = System.currentTimeMillis();
            task.longTimeTask();
            Utils.endTime1 = System.currentTimeMillis();
        }else {
            Utils.begainTime2 = System.currentTimeMillis();
            task.longTimeTask();
            Utils.endTime2 = System.currentTimeMillis();
        }
    }
}
複製代碼
public class Main {
    public static void main(String[] args){
        Task task = new Task();
        MyThread myThread = new MyThread(task, "A");
        MyThread myThread1 = new MyThread(task, "B");
        myThread.start();
        myThread1.start();
        try {
            Thread.sleep(10000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        long beginTime = Utils.begainTime1;
        if (Utils.begainTime2<Utils.begainTime1){
            beginTime = Utils.begainTime2;
        }
        long endTime = Utils.endTime1;
        if (Utils.endTime2>Utils.endTime1){
            endTime = Utils.endTime2;
        }
        System.out.println("耗時:" + (endTime-beginTime)/1000 + "s");


    }


}
複製代碼

輸出內容:github

begin task
長時間處理任務後從遠程返回的值1 threadName=A
長時間處理任務後從遠程返回的值2 threadName=A
end task
begin task
長時間處理任務後從遠程返回的值1 threadName=B
長時間處理任務後從遠程返回的值2 threadName=B
end task
耗時:6s
複製代碼

從運行時間上來看,synchronized方法的問題很明顯。可使用synchronized同步塊來解決這個問題。可是要注意synchronized同步塊的使用方式,若是synchronized同步塊使用很差的話並不會帶來效率的提高。緩存

將上文的Task.class文件修改以下:bash

public void longTimeTask(){
        try {
            System.out.println("begin task");
            Thread.sleep(3000);
            String data1 = "長時間處理任務後從遠程返回的值1 threadName=" + Thread.currentThread().getName();
            String data2 = "長時間處理任務後從遠程返回的值2 threadName=" + Thread.currentThread().getName();
            synchronized (this){
                getData1 = data1;
                getData2 = data2;
            }
            System.out.println(getData1);
            System.out.println(getData2);
            System.out.println("end task");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
複製代碼

輸出以下:微信

begin task
begin task
長時間處理任務後從遠程返回的值1 threadName=B
長時間處理任務後從遠程返回的值2 threadName=A
end task
長時間處理任務後從遠程返回的值1 threadName=A
長時間處理任務後從遠程返回的值2 threadName=A
end task
耗時:3s
複製代碼

從上面代碼能夠看出當一個線程訪問一個對象的synchronized同步代碼塊時,另外一個線程任然能夠訪問該對象非synchronized同步代碼塊。不在synchronized塊中的就是異步執行,在synchronized塊中就是同步執行。多線程

synchronized代碼塊之間的同步性

當一個線程訪問一個對象的synchronized(this)同步代碼塊時,其餘線程對同一個object中的其餘synchronized(this)同步代碼塊訪問將被阻塞。異步

若是在一個類中有不少個synchronized方法,這是雖然能實現同步,但會受到阻塞。若是使用同步代碼塊鎖非this對象,則synchronized(非this)代碼塊中的程序與同步方法是異步的,不與其餘this同步方法爭搶this鎖。ide

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

關鍵字synchronized還能夠在static方法是使用,是對當前的*.java文件的Class類進行加鎖。非靜態的synchronized關鍵字是給對象加鎖。ui

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");
    }
複製代碼
public class Main {

    public static void main(String[] args){
        Service service = new Service();
        new Thread(Service::printA, "A").start();
        new Thread(Service::printB, "B").start();
        new Thread(() -> service.printC(), "C").start();


    }
}
複製代碼

輸出內容:

線程名稱爲:A在1552262297299進入printA
線程名稱爲:C在1552262297300進入printC
線程名稱爲:C在1552262297300離開printC
線程名稱爲:A在1552262300301離開printA
線程名稱爲:B在1552262300301進入printB
線程名稱爲:B在1552262300301離開printB
複製代碼

從運行結果能夠看出:靜態同步synchronized方法與synchronized(class)代碼塊持有的鎖同樣,都是Class鎖,Class鎖對對象的全部實例起做用。synchronized關鍵字加到非static靜態方法上持有的是對象鎖。線程A,B和線程C持有的鎖不同,因此A和B運行同步,可是和C運行不一樣步。

數據類型String的常量池特性

JVM具備String常量池緩存的功能,將synchronized(string)與String聯合使用時會出現一些問題。

String s1 = "a";
    String s2="a";
    System.out.println(s1==s2);//true
複製代碼

好比兩個同步方法都是synchronized("abc"){}那麼多線程會持有相同的鎖,因此大多數同步代碼塊不用String做爲鎖。

本文代碼:GitHub


歡迎關注公衆號:

公衆號微信
相關文章
相關標籤/搜索