Java併發編程,互斥同步和線程之間的協做

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

synchronized併發

  1. 同步一個代碼塊

public void func() {分佈式

synchronized (this) {
    // ...
}

}
它只做用於同一個對象,若是調用兩個對象上的同步代碼塊,就不會進行同步。ide

對於如下代碼,使用 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
對於如下代碼,兩個線程調用了不一樣對象的同步代碼塊,所以這兩個線程就不須要同步。從輸出結果能夠看出,兩個線程交叉執行。this

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 9code

  1. 同步一個方法

public synchronized void func () {

// ...

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

  1. 同步一個類

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

  1. 同步一個靜態方法

public synchronized static void fun() {

// ...

}
做用於整個類。

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. 鎖的實現

synchronized 是關鍵字,由 JVM 實現的,而 ReentrantLock 是類,由 JDK 實現的。

  1. 性能

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

  1. 等待可中斷

ReentrantLock 能夠獲取鎖的等待時間並能夠進行設置,這樣避免了死鎖。

  1. 公平鎖

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

synchronized 中的鎖是非公平的,ReentrantLock 默認狀況下也是非公平的,可是也能夠是公平的。

  1. 鎖綁定多個條件

一個 ReentrantLock 能夠同時綁定多個 Condition 對象,靈活地實現多路通知。

  1. 機制

synchronized 操做Mark World,ReentrantLock 調用Unsafe類的park()方法

使用選擇
除非須要使用 ReentrantLock 的高級功能,不然優先使用 synchronized。這是由於 synchronized 是 JVM 實現的一種鎖機制,JVM 原生地支持它,而 ReentrantLock 不是全部的 JDK 版本都支持。而且使用 synchronized 不用擔憂沒有釋放鎖而致使死鎖問題,由於 JVM 會確保鎖的釋放。

線程之間的協做
當多個線程能夠一塊兒工做去解決某個問題時,若是某些部分必須在其它部分以前完成,那麼就須要對線程進行協調。

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
wait() notify() notifyAll()
調用 wait() 使得線程等待某個條件知足,線程在等待時會被掛起,當其餘線程的運行使得這個條件知足時,其它線程會調用 notify() 或者 notifyAll() 來喚醒掛起的線程。

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

只能用在同步方法或者同步控制塊中使用,不然會在運行時拋出 IllegalMonitorStateException。

使用 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() 不會。
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高級資料須要本身領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G。
傳送門:https://mp.weixin.qq.com/s/Jz...

相關文章
相關標籤/搜索