Java 多線程中篇

異步

public class PrintObject {
    public void printString(){
        System.out.println("begin");
        if(Thread.currentThread().getName().equals("a")){
            PrintStream printStream = System.out;
            printStream.println("線程 a 運行");
        }
        if(Thread.currentThread().getName().equals("b")){
            System.out.println("線程 b 運行");
        }
        System.out.println("end");
    }
}
public static void main(String[] a) {
    PrintObject pb = new PrintObject();

    Thread thread1 = new Thread(pb::printString);
    thread1.setName("a");
    thread1.start();

    Thread thread2 = new Thread(pb::printString);
    thread2.setName("b");
    thread2.start();
}

咱們建立兩個線程來調用同一業務對象的相同功能時, 能夠看到下面輸出.java

begin
begin
線程 a 運行
end
線程 b 運行
end

兩個線程在一塊兒執行 printString 方法, 而且交叉打印. 也就是說當咱們啓動一個線程執行某個方法的時候就是異步執行, 至於爲啥要這樣演示, 是由於下面的同步.數據庫

同步

synchronized public void printString() 方法上加入 synchronized 關鍵字, 來使方法同步. 安全

執行結果:網絡

begin
線程 a 運行
end
begin
線程 b 運行
end

那麼爲何加入 synchronized 關鍵字後就會同步呢? 這是由於關鍵字 synchronized 會取得一把對象鎖, 而不是把一段代碼或方法當作鎖; 哪一個線程先執行帶 synchronized 關鍵字的方法, 哪一個線程就持有該方法所屬的對象的鎖 Look, 那麼其餘線程只能呈等待狀態.異步

這裏有個前提是多個線程訪問同一個對象, 下面演示的是多個線程訪問不一樣的對象.this

public class PrintObject {
    synchronized public void printString(){
        System.out.println("begin");
        if(Thread.currentThread().getName().equals("a")){
            PrintStream printStream = System.out;
            printStream.println("線程 a 運行");
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                
            }
        }
        if(Thread.currentThread().getName().equals("b")){
            System.out.println("線程 b 運行");
        }
        System.out.println("end");
    }
}
public static void main(String[] a) {
        PrintObject pb = new PrintObject();
        PrintObject pb1 = new PrintObject();

        Thread thread1 = new Thread(pb::printString);
        thread1.setName("a");
        thread1.start();

        Thread thread2 = new Thread(pb1::printString);
        thread2.setName("b");
        thread2.start();
    }

執行結果線程

begin
線程 a 運行
begin
線程 b 運行
end

讓 a 線程睡眠 100000 毫秒, 能夠看到 a 線程並無執行完, b 線程就運行了. 這也可以證實 synchronized 關鍵字取得是對象鎖.code

另外還須要注意一點, 咱們使用兩個線程執行同一對象的不一樣同步方法時, 若是線程 a 在睡眠, 那麼線程 b 也會一直等待, 線程 a 執行完畢後再去執行.orm

注: 同步方法必定是線程安全的.

synchronized 鎖重入

若是一個獲取鎖的線程調用其它的synchronized修飾的方法, 會發生什麼?對象

在一個線程使用synchronized方法時調用該對象另外一個synchronized方法, 即一個線程獲得一個對象鎖後再次請求該對象鎖, 是永遠能夠拿到鎖的.

在Java內部, 同一個線程調用本身類中其餘synchronized方法/塊時不會阻礙該線程的執行, 同一個線程對同一個對象鎖是可重入的, 同一個線程能夠獲取同一把鎖屢次, 也就是能夠屢次重入. 緣由是Java中線程得到對象鎖的操做是以線程爲單位的, 而不是以調用爲單位的.

這種狀況也能夠發生在繼承中, 也就是說子類的同步方法調用父類的同步方式時, 時能夠鎖重入的.
可是, 若是子類重寫了父類的方法, 並無使用 synchronized 關鍵字, 則同步就失效了. 由於子類重寫父類的方法, 當咱們調用方法執行代碼時, 執行的是子類的方法, 因此變成了異步執行.

synchronized 同步代碼塊

public class PrintObject {
     public synchronized void printString(){
        try {
            System.out.println(Thread.currentThread().getName() + " 執行");
            System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫");
            // 讓線程休眠, 模擬出網絡延時
            Thread.sleep(5000);

            System.out.println(Thread.currentThread().getName() + " 共享數據減1");
            Thread.sleep(5000);

            System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫");
            Thread.sleep(5000);

            if (Thread.currentThread().getName().equals("b")) {
                SimpleDateFormat df = new SimpleDateFormat("mm:ss");
                System.out.println(df.format(new Date()));
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
PrintObject pb = new PrintObject();

Thread thread1 = new Thread(pb::printString);
thread1.setName("a");

Thread thread2 = new Thread(pb::printString);
thread2.setName("b");

SimpleDateFormat df = new SimpleDateFormat("mm:ss");
System.out.println(df.format(new Date()));

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

執行結果

47:34
a 執行
a 插入數據到數據庫
a 共享數據減1
a 插入數據到數據庫
b 執行
b 插入數據到數據庫
b 共享數據減1
b 插入數據到數據庫
48:04

咱們上面這段程序兩個線程所有執行完所用的時間爲 30 秒, 這裏能夠看出同步方法存在一個很大的弊端.

就是說咱們的某個線程開始執行方法時, 不管咱們操做的是否是共享數據, 別的線程都會等待此線程釋放鎖. 而後繼續執行.

但是咱們在插入數據到數據庫的時候, 並非在操做共享數據, 那麼咱們有沒有什麼辦法, 只同步操做共享數據的那部分代碼呢?

咱們就可使用 synchronized 同步代碼塊, 將程序修改爲下面樣子.

public class PrintObject {
     public void printString(){
        try {
            System.out.println(Thread.currentThread().getName() + " 執行");
            System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫");
            // 讓線程休眠, 模擬出網絡延時
            Thread.sleep(5000);

            synchronized(this) {
                System.out.println(Thread.currentThread().getName() + " 共享數據減1");
                Thread.sleep(5000);
            }


            System.out.println(Thread.currentThread().getName() + " 插入數據到數據庫");
            Thread.sleep(5000);

            if (Thread.currentThread().getName().equals("b")) {
                SimpleDateFormat df = new SimpleDateFormat("mm:ss");
                System.out.println(df.format(new Date()));
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

執行結果

54:12
b 執行
a 執行
b 插入數據到數據庫
a 插入數據到數據庫
a 共享數據減1
a 插入數據到數據庫
b 共享數據減1
b 插入數據到數據庫
54:32

減小了10秒的執行時間, 提升了執行效率.

同步方法和同步代碼塊的鎖都是同一把鎖. 同步方法獲取的是該方法的對象鎖, 而同步代碼塊獲取中的參數是 this, 表示當前對象. 因此獲取的是同一把鎖.

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

synchronized 關鍵字能夠應用在 static 靜態方法上, 表示當前的 *.java 文件對應的 Class 類進行持鎖.

雖然運行結果與 synchronized 關鍵字加到非 static 靜態方法上的結果相似, 可是是對 Class 類進行加鎖, 而 Class 鎖能夠對類的全部對象起做用.

synchronized (DemoApplication.class) {
            
}
相關文章
相關標籤/搜索