1. 程式人生 > >synchronized關鍵字需要註意的幾點

synchronized關鍵字需要註意的幾點

hang 計數器 自己 final 獲得 可重入 trace 獲取 ava

目錄

  • synchronized關鍵字需要註意的幾點
    • 臟讀
    • synchronized是可重入鎖
    • synchronized對於異常處理
    • 鎖對象的引用不能修改

synchronized關鍵字需要註意的幾點

臟讀

對業務寫方法加鎖,對業務讀方法不加鎖,容易產生臟讀。

public class Test1 {
    private String name;
    private double balance;
    public synchronized void set(String name, double balance) {
        this.name = name;
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
    }
    public double getBalance() {
        return this.balance;
    }

    public static void main(String[] args) throws InterruptedException {
        Test1 test = new Test1();
        //先是寫方法,雖然加鎖,但是對於讀沒有加鎖,所以其並不會等待寫的鎖釋放才能調用
        new Thread(() -> {
            test.set("zhangsna",100.0);
        }).start();
        //此時還沒有設置完成,所以讀取到的並不是正確的數據
        Optional.of(test.getBalance()).ifPresent(System.out::println);
        TimeUnit.SECONDS.sleep(2);
        Optional.of(test.getBalance()).ifPresent(System.out::println);
    }
}

synchronized是可重入鎖

可重入鎖:一個線程已經擁有某個對象的額鎖,再次申請的時候仍然會得到該對象的鎖

/**
     Thread-0 m1 start
     Thread-0 m2 start
     Thread-0 m2 end
     Thread-0 m1 end
     可重入鎖的機制是:每一個鎖關聯一個持有者和計數器,當計數器為0時表示該鎖沒有被任何線程持有,
     那麽任何線程都可能獲得該鎖而調用相應的方法;當某一線程請求成功後,JVM會記下鎖的持有線程,並且將計數器置為 1;
     此時其它線程請求該鎖,則必須等待;而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,
     同時計數器會遞增;當線程退出同步代碼塊時,計數器會遞減,如果計數器為 0,則釋放該鎖。
 */
public class Test2 {
    public synchronized void m1() {
        System.out.println(Thread.currentThread().getName() + " m1 start ");
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
        }
        /**
          調用m2的時候,是不需要等待自己釋放鎖的,所以從這點上可以證明synchronized是可重入鎖
         */
        m2();
        System.out.println(Thread.currentThread().getName() + " m1 end ");
    }

    public synchronized void m2() {
        System.out.println(Thread.currentThread().getName() + " m2 start ");
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
        }
        System.out.println(Thread.currentThread().getName() + " m2 end ");
    }

    public static void main(String[] args) {
        Test2 test = new Test2();
        new Thread(() -> test.m1()).start();
    }
}

synchronized對於異常處理

ynchronized 如果出現異常,默認情況鎖會被釋放。所以在並發處理過程中,有異常出現時需要小心,否則有可能會發現不一致的情況

public class Test3 {
    int count = 0;
    public synchronized void m1() {
        System.out.println(Thread.currentThread().getName() + " start ");
        while(true) {
            count++;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
            }
            if (count == 5) {
                //此處拋出異常,鎖就會被釋放,要想鎖不被釋放,
                //可以在這裏catch,然後讓循環繼續
               // try {
                    int i = 1/0;
                //}catch (ArithmeticException e){
                //    System.out.println("拋出異常");
                //}
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test3 test = new Test3();
        new Thread(() -> test.m1(),"t1").start();
        TimeUnit.SECONDS.sleep(3);
        new Thread(() -> test.m1(),"m2").start();
    }
}

鎖對象的引用不能修改

public class Test4 {
    /**
     對於作為鎖對象,對象的屬性可以修改,但是對象的引用不能再被修改,
     所以一般作為鎖對象,都需要將其設置為final。
     */
    Object o = new Object();
    public void m1() {
        synchronized (o) {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test4 test = new Test4();
        new Thread(() -> test.m1(),"t1").start();
        TimeUnit.SECONDS.sleep(2);
        //修改變量指向新的對象
        test.o = new Object();
        //如果不修改o引用,t2線程將只要t1不釋放鎖,將無法獲取鎖
        //但是將o指向另一個對象,使得t1的鎖對象和t2的鎖對象不是同一個
        new Thread(() -> test.m1(),"t2").start();
    }
}

所以也盡量不能使用字符串常量作為鎖對象去同步代碼。容易產生死鎖

private static final String Lock = "LOCK";
public void m2() {
    /**
      因為不同包不同類中的值相同的字符串常量引用的是同一個字符串對象。
      這就意味著外部任何的Class都可以包含指向同一個字符串對象的字符串常量,
      因此就有可能出現死鎖的情況!
      一旦出現這種情況的死鎖,是極難排查出來的。
     */
    synchronized (Lock) {
    }
}

synchronized關鍵字需要註意的幾點