1. 程式人生 > >java並發2--進階

java並發2--進階

原生 art lex rup 申請 語句 nbsp 繼續 同步控制

五、互斥同步

Java 提供了兩種鎖機制來控制多個線程對共享資源的互斥訪問,第一個是 JVM 實現的 synchronized,而另一個是 JDK 實現的 ReentrantLock。

1.1 synchronized

1. 同步一個代碼塊

public void func() {

    synchronized (this) {

        // ...

    }

}

它只作用於同一個對象,如果調用兩個對象上的同步代碼塊,就不會進行同步。

對於以下代碼,使用 ExecutorService 執行了兩個線程,由於調用的是同一個對象的同步代碼塊,因此這兩個線程會進行同步,當一個線程進入同步語句塊時,另一個線程就必須等待。

public class SynchronizedExample {

 

    public void func1() {

        synchronized (this) {

            for (int i = 0; i < 10; i++) {

                System.out.print(i + " ");

            }

        }

    }

}

public static void main(String[] args) {

    SynchronizedExample e1 = new SynchronizedExample();

    ExecutorService executorService 
= Executors.newCachedThreadPool(); executorService.execute(() -> e1.func1()); executorService.execute(() -> e1.func1()); } 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

對於以下代碼,兩個線程調用了不同對象的同步代碼塊,因此這兩個線程就不需要同步。從輸出結果可以看出,兩個線程交叉執行。

public static void main(String[] args) {

    SynchronizedExample e1 
= new SynchronizedExample(); SynchronizedExample e2 = new SynchronizedExample(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> e1.func1()); executorService.execute(() -> e2.func1()); } 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

2. 同步一個方法

public synchronized void func () {

    // ...

}

它和同步代碼塊一樣,作用於同一個對象。

3. 同步一個類

public void func() {

    synchronized (SynchronizedExample.class) {

        // ...

    }

}

作用於整個類,也就是說兩個線程調用同一個類的不同對象上的這種同步語句,也會進行同步。

public class SynchronizedExample {

 

    public void func2() {

        synchronized (SynchronizedExample.class) {

            for (int i = 0; i < 10; i++) {

                System.out.print(i + " ");

            }

        }

    }

}

public static void main(String[] args) {

    SynchronizedExample e1 = new SynchronizedExample();

    SynchronizedExample e2 = new SynchronizedExample();

    ExecutorService executorService = Executors.newCachedThreadPool();

    executorService.execute(() -> e1.func2());

    executorService.execute(() -> e2.func2());

}

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

4. 同步一個靜態方法

public synchronized static void fun() {

    // ...

}

作用於整個類。

1.2 ReentrantLock

ReentrantLock 是 java.util.concurrent(J.U.C)包中的鎖。

public class LockExample {
 
    private Lock lock = new ReentrantLock();
 
    public void func() {
        lock.lock();
        try {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        } finally {
            lock.unlock(); // 確保釋放鎖,從而避免發生死鎖。
        }
    }
}
public static void main(String[] args) {
    LockExample lockExample = new LockExample();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> lockExample.func());
    executorService.execute(() -> lockExample.func());
}
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

1.3 比較

鎖的實現

synchronized 是 JVM 實現的,而 ReentrantLock 是 JDK 實現的。

性能

新版本 Java 對 synchronized 進行了很多優化,例如自旋鎖等,synchronized 與 ReentrantLock 大致相同。

等待可中斷

當持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待,改為處理其他事情。

ReentrantLock 可中斷,而 synchronized 不行。

公平鎖

公平鎖是指多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖。

synchronized 中的鎖是非公平的,ReentrantLock 默認情況下也是非公平的,但是也可以是公平的。

鎖綁定多個條件

一個 ReentrantLock 可以同時綁定多個 Condition 對象。

使用選擇

除非需要使用 ReentrantLock 的高級功能,否則優先使用 synchronized。這是因為 synchronized 是 JVM 實現的一種鎖機制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。並且使用 synchronized 不用擔心沒有釋放鎖而導致死鎖問題,因為 JVM 會確保鎖的釋放。

六、線程之間的協作

當多個線程可以一起工作去解決某個問題時,如果某些部分必須在其它部分之前完成,那麽就需要對線程進行協調。

1.4 join()

在線程中調用另一個線程的 join() 方法,會將當前線程掛起,而不是忙等待,直到目標線程結束。

對於以下代碼,雖然 b 線程先啟動,但是因為在 b 線程中調用了 a 線程的 join() 方法,b 線程會等待 a 線程結束才繼續執行,因此最後能夠保證 a 線程的輸出先於 b 線程的輸出。

public class JoinExample {
 
    private class A extends Thread {
        @Override
        public void run() {
            System.out.println("A");
        }
    }
 
    private class B extends Thread {
 
        private A a;
 
        B(A a) {
            this.a = a;
        }
 
        @Override
        public void run() {
            try {
                a.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B");
        }
    }
 
    public void test() {
        A a = new A();
        B b = new B(a);
        b.start();
        a.start();
    }
}
public static void main(String[] args) {
    JoinExample example = new JoinExample();
    example.test();
}
A
B

1.5 wait() notify() notifyAll()

調用 wait() 使得線程等待某個條件滿足,線程在等待時會被掛起,當其他線程的運行使得這個條件滿足時,其它線程會調用 notify() 或者 notifyAll() 來喚醒掛起的線程。

它們都屬於 Object 的一部分,而不屬於 Thread。

只能用在同步方法或者同步控制塊中使用,否則會在運行時拋出 IllegalMonitorStateExeception。

使用 wait() 掛起期間,線程會釋放鎖。這是因為,如果沒有釋放鎖,那麽其它線程就無法進入對象的同步方法或者同步控制塊中,那麽就無法執行 notify() 或者 notifyAll() 來喚醒掛起的線程,造成死鎖。

public class WaitNotifyExample {
 
    public synchronized void before() {
        System.out.println("before");
        notifyAll();
    }
 
    public synchronized void after() {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after");
    }
}
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    WaitNotifyExample example = new WaitNotifyExample();
    executorService.execute(() -> example.after());
    executorService.execute(() -> example.before());
}
before
after

wait() sleep() 的區別

  • wait() 是 Object 的方法,而 sleep() 是 Thread 的靜態方法;
  • wait() 會釋放鎖,sleep() 不會。

1.6 await() signal() signalAll()

java.util.concurrent 類庫中提供了 Condition 類來實現線程之間的協調,可以在 Condition 上調用 await() 方法使線程等待,其它線程調用 signal() 或 signalAll() 方法喚醒等待的線程。

相比於 wait() 這種等待方式,await() 可以指定等待的條件,因此更加靈活。

使用 Lock 來獲取一個 Condition 對象。

public class AwaitSignalExample {
 
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
 
    public void before() {
        lock.lock();
        try {
            System.out.println("before");
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
 
    public void after() {
        lock.lock();
        try {
            condition.await();
            System.out.println("after");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    AwaitSignalExample example = new AwaitSignalExample();
    executorService.execute(() -> example.after());
    executorService.execute(() -> example.before());
}
before
after

java並發2--進階