Java多線程之synchronized的小介紹

首先先看看下面的代碼:安全

public class checkSynchronized extends Thread{
    static volatile int i = 0;
    
    public static void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
    }
    
    public static void main(String args[]) throws InterruptedException {
        Thread t1 = new checkSynchronized();
        Thread t2 = new checkSynchronized();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("i : " + i);
    }
}
複製代碼

執行上述代碼,你會發現大部分狀況下i的最終值都會小於2000000的bash

爲何加了volatile關鍵詞的變量依舊會出現線程安全問題呢?多線程

這是由於volatile只保證可見性,不保證原子性ide

圖爲兩條線程同時對i進行寫入時,一個線程的結果會覆蓋另外一線程的結果,形成線程安全問題。 解決此問題就應該在線程甲進行寫入值時,線程乙不只不能寫入、並且還不能讀取值,若是讀取值的話就會讀取到一箇舊值,依舊會形成線程安全問題。那該如何實現呢?

在這裏就要引出今天的主角了:"Synchronized"

關鍵字synchronized的做用就是實現線程間的同步問題,它能將同步區的代碼進行加鎖,一次只能容許一條線程進入同步區,以保證同步區中的線程安全問題。 下面就來測試下該關鍵字的做用:性能

public class checkSynchronized extends Thread{
    Object lock;
    checkSynchronized(Object lock) {
        this.lock = lock;
    }
    static volatile int i = 0;

    public static void increase() {
        i++;
    }

    @Override
    public void run() {
        synchronized (lock) {
            for (int j = 0; j < 1000000; j++) {
                increase();
            }
        }
    }

    public static void main(String args[]) throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new checkSynchronized(lock);
        Thread t2 = new checkSynchronized(lock);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("i : " + i);
    }
}
複製代碼

你們能夠看到在run()方法里加了synchronized,而且指定了鎖對象。測試

運行結果:i : 2000000this

在這裏簡單地整理下synchronized的多種用法:

  • 指定鎖對象,進入前須先得到給定對象的鎖(如上述代碼所述)
  • 加在實例方法,進入前須得到當前實例的鎖
  • 加在靜態方法,進入前須得到當前類的鎖

在這裏給出一段錯誤代碼,你們須要明白:

public static void main(String args[]) throws InterruptedException {
        Object lock1 = new Object();    //生成第一個鎖
        Object lock2 = new Object();    //生成第二個鎖
        Thread t1 = new checkSynchronized(lock1);
        Thread t2 = new checkSynchronized(lock2);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("i : " + i);
    }
複製代碼

這段代碼表示出各自線程最終使用了各自的鎖,線程安全是沒法保證的。spa

對於做用在實例方法上的也要注意,由於其可能會發生和上述相同緣由的線程安全錯誤。

public class checkSynchronized implements Runnable{

    static volatile int i = 0;

    public synchronized void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
    }

    public static void main(String args[]) throws InterruptedException {
        checkSynchronized onlyOne = new checkSynchronized();
        Thread t1 = new Thread(onlyOne);
        Thread t2 = new Thread(onlyOne);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("i : " + i);
    }
}
複製代碼

上述代碼的synchronized做用在實例方法上,鎖爲當前實例。因此在main方法中只實例了一個checkSynchronized實例,由於鎖只須要一個、多了就會出現安全問題。線程

若是實例了兩個對象,則會出現安全問題(鎖多了和不加鎖就沒啥區別了),以下述代碼:code

public static void main(String args[]) throws InterruptedException {
        checkSynchronized onlyOne = new checkSynchronized();
        checkSynchronized wrong = new checkSynchronized();
        Thread t1 = new Thread(onlyOne);
        Thread t2 = new Thread(wrong);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("i : " + i);
    }
複製代碼

解決的辦法很是容易,只需在加了鎖的實例方法加上"static"關鍵字就行。

public static synchronized void increase() 這樣鎖就做用在類方法上了。當線程要執行該同步方法時是請求當前類的鎖並不是實例的鎖,因此再多的實例線程之間依舊能正確同步。

synchronized不只用於線程同步、確保線程安全問題外,還能保證線程之間的可見性和有序性問題。至關因而volatile的升級版。但被synchronized限制的多線程之間是串行執行的,所帶來的性能消耗是很大的。

相關文章
相關標籤/搜索