Java-Synchronized(三)-多線程訪問同步方法的七種情況

七種情況

  1. 兩個線程同時訪問一個對象的同步方法
  2. 兩個線程訪問的是兩個對象的同步方法
  3. 兩個線程訪問的是synchronized修飾的靜態方法
  4. 同時訪問同步方法與非同步方法
  5. 訪問一個對象的不同的普通同步方法
  6. 同時訪問靜態synchronized方法和非靜態的synchronized方法
  7. 方法拋出異常後,會釋放鎖

一 | 兩個線程同時訪問一個對象的同步方法

  1. 兩個線程t1和t2
  2. 同一個對象,它們都是Runnabl的同一個實例instance
  3. 一個對象的同步方法,被synchronizd修飾的method

結論:串行

併發與串行其實是不同的概念,在單核CPU裏面,所有指令都是串行的,併發指的在某一個時間段內,多個線程執行。與串行相對應的是並行,之後用串行和並行來區別。

public class SynchronizedObjectMethod3 implements Runnable {

    static  SynchronizedObjectMethod3 instance = new SynchronizedObjectMethod3();

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
//        t1.join();
//        t2.join();
        while(t1.isAlive() || t2.isAlive()) {}
        System.out.println("finished");
    }

    @Override
    public void run() {
        method();
    }

    public synchronized void method() {
        System.out.println("I'm object method lock " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("I'm object method lock " + Thread.currentThread().getName() + " is over");
    }
}

二 | 兩個線程訪問的是兩個對象的同步方法

  1. 兩個線程t1、t2
  2. 兩個對象instance1和instance2

儘管同步代碼塊對象鎖是this,但這個this在起作用是兩個不同的實例,用lock1也是一樣的。

結論:並行

public class SynchronizedObjectCodeBlock2 implements Runnable {

    static  SynchronizedObjectCodeBlock2 instance1 = new SynchronizedObjectCodeBlock2();
    static  SynchronizedObjectCodeBlock2 instance2 = new SynchronizedObjectCodeBlock2();

    private final Object lock1 = new Object();
    private final Object lock2 = new Object();

    public static void main(String[] args)  throws InterruptedException{
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
//        t1.join();
//        t2.join();
        while(t1.isAlive() || t2.isAlive()) {}
        System.out.println("finished");
    }

    @Override
    public void run() {
        synchronized (this) {
            System.out.println("I'm object code block lock1 " + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("I'm object code block lock1 " + Thread.currentThread().getName() + " is over");
        }

//        synchronized (lock2) {
//            System.out.println("I'm object code block lock2 " + Thread.currentThread().getName());
//            try {
//                Thread.sleep(3000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println("I'm object code block lock2 " + Thread.currentThread().getName() + " is over");
//        }
    }
}

三 | 兩個線程訪問的是synchronized修飾的靜態方法

public class SynchronizedStaticMethod4 implements Runnable {

    static  SynchronizedStaticMethod4 instance1 = new SynchronizedStaticMethod4();
    static  SynchronizedStaticMethod4 instance2 = new SynchronizedStaticMethod4();

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
//        t1.join();
//        t2.join();
        while(t1.isAlive() || t2.isAlive()) {}
        System.out.println("finished");
    }

    @Override
    public void run() {
        method();
    }

    public static synchronized void method() {
        System.out.println("I'm object method lock " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("I'm object method lock " + Thread.currentThread().getName() + " is over");
    }
}

結論:串行

四 | 同時訪問同步方法與非同步方法

結論當然是非同步方法不會受到影響

public class SynchronizedYesOrNo6 implements Runnable{

    static SynchronizedYesOrNo6 instance = new SynchronizedYesOrNo6();

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
//        t1.join();
//        t2.join();
        while(t1.isAlive() || t2.isAlive()) {}
        System.out.println("finished");
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")) {
            method1();
        } else {
            method2();
        }
    }

    public synchronized void method1() {
        System.out.println("I'm lock " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("I'm lock " + Thread.currentThread().getName() + " is over");
    }

    public void method2() {
        System.out.println("I'm not lock " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("I'm not lock " + Thread.currentThread().getName() + " is over");
    }
}

試想一下如果本該作爲臨界資源的變量i,除了在同步代碼塊內,還在普通方法的地方被修改,會發生什麼呢?

public class DisappearRequest1 implements Runnable {

    static DisappearRequest1 instance = new DisappearRequest1();

    static int i = 0;

    public static void main(String[] args) throws InterruptedException{
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }

    @Override
    public  void run() {
        if(Thread.currentThread().getName().equals("Thread-0")) {
            method1();
        } else {
            method2();
        }
    }

    public synchronized void method1() {
        for(int j=0; j<100000; j++) {
            i++;
        }
    }

    public void method2() {
        for(int j=0; j<100000; j++) {
            i++;
        }
    }
}

i的最後結果是小於200000的…所以臨界資源必須進行互斥操作。

五 | 訪問一個對象的不同的普通同步方法

public class SynchronizedDifferentMethod7 implements Runnable{

    static SynchronizedDifferentMethod7 instance = new SynchronizedDifferentMethod7();

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
//        t1.join();
//        t2.join();
        while(t1.isAlive() || t2.isAlive()) {}
        System.out.println("finished");
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")) {
            method1();
        } else {
            method2();
        }
    }

    public synchronized void method1() {
        System.out.println("I'm lock " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("I'm lock " + Thread.currentThread().getName() + " is over");
    }

    public synchronized void method2() {
        System.out.println("I'm not lock " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("I'm not lock " + Thread.currentThread().getName() + " is over");
    }

}

結論串行:其實相當於是synchronized鎖了同一個this對象

六 | 同時訪問靜態synchronized方法和非靜態的synchronized方法

預測:並行

public class SynchronizedStaticAndNormal8 implements Runnable{
    static SynchronizedStaticAndNormal8 instance = new SynchronizedStaticAndNormal8();

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
//        t1.join();
//        t2.join();
        while(t1.isAlive() || t2.isAlive()) {}
        System.out.println("finished");
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")) {
            method1();
        } else {
            method2();
        }
    }

    public static synchronized void method1() {
        System.out.println("I'm static lock " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("I'm static lock " + Thread.currentThread().getName() + " is over");
    }

    public synchronized void method2() {
        System.out.println("I'm not static " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("I'm not static " + Thread.currentThread().getName() + " is over");
    }
}

結論:並行

原因:static加鎖的對象是該類的類類型,而普通方法鎖加鎖的對象是this,這顯然是兩把不同的鎖。

章七 | 方法拋出異常後,會釋放鎖

要使用RuntimeException來拋出異常

public class SynchronizedException9 implements Runnable {
    static SynchronizedException9 instance = new SynchronizedException9();

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
//        t1.join();
//        t2.join();
        while(t1.isAlive() || t2.isAlive()) {}
        System.out.println("finished");
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")) {
            method1();
        } else {
            method2();
        }
    }

    public  synchronized void method1() {
        System.out.println("I'm static lock " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
//            throw new Exception();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        catch (Exception e) {
//            e.printStackTrace();
//        }
        throw new RuntimeException();
//        System.out.println("I'm static lock " + Thread.currentThread().getName() + " is over");
    }

    public synchronized void method2() {
        System.out.println("I'm not static " + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("I'm not static " + Thread.currentThread().getName() + " is over");
    }
}

總結

  1. 一把鎖只能被一個線程所獲取,其他線程只能等待
  2. 每個實例都對應有自己的一把鎖,類鎖就是所有實例的類類型的鎖,有同一個類派生而來的實例自然只有一個類類型
  3. 無論發放還是正常執行完畢還是拋出異常,synchronized修飾的都將釋放鎖
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章