【Java併發編程學習 4】多線程之間實現通訊及Lock鎖

1 什麼是多線程之間的通訊

在併發編程中,我們需要處理兩個關鍵問題:線程之間如何通信及線程之間如何同步。通信是指線程之間以如何來交換信息。一般線程之間的通信機制有兩種:共享內存和消息傳遞。

Java的併發採用的是共享內存模型,Java線程之間的通信總是隱式進行,整個通信過程對程序員完全透明。如果編寫多線程程序的Java程序員不理解隱式進行的線程之間通信的工作機制,很可能會遇到各種奇怪的內存可見性問題。

2 通訊業務需求及實現

需求是:第一個線程寫入用戶,另一個線程取讀取用戶,實現讀一個,寫一個操作。

2.1 代碼實現基本實現

  1. 定義User類
package com.lijie;

public class User {
    String name;
    String sex;
}
  1. 輸入線程資源
package com.lijie;

//輸入類
class InputThread extends Thread {
    private User user;

    public InputThread(User user) {
        this.user = user;
    }

    public void run() {
        int count = 0;
        while (true) {
            if (count % 2 == 0) {
                user.name = "張三";
                user.sex = "男";
            } else {
                user.name = "小紅";
                user.sex = "女";
            }
            count += 1;
        }
    }
}
  1. 輸出線程資源
package com.lijie;

//輸出類
class OutThread extends Thread {
    private User user;

    public OutThread(User user) {
        this.user = user;
    }

    public void run() {
        while (true) {
            System.out.println(user.name + "====" + user.sex);
        }
    }
}
  1. 創建測試
package com.lijie;

public class Test {
    public static void main(String[] args) {
        User user = new User();
        InputThread inputThread = new InputThread(user);
        OutThread outThread = new OutThread(user);
        inputThread.start();
        outThread.start();
    }
}

運行後你會發現,數據非常亂
在這裏插入圖片描述

2.2 使用synchronized解決線程安全問題

加上synchronized同步代碼塊
修改輸入和輸出線程資源倆個類:

package com.lijie;

//輸入類
class InputThread extends Thread {
    private User user;

    public InputThread(User user) {
        this.user = user;
    }

    public void run() {
        int count = 0;
        synchronized (this) {
            while (true) {
                if (count % 2 == 0) {
                    user.name = "張三";
                    user.sex = "男";
                } else {
                    user.name = "小紅";
                    user.sex = "女";
                }
                count += 1;
            }
        }
    }
}
package com.lijie;

//輸出類
class OutThread extends Thread {
    private User user;

    public OutThread(User user) {
        this.user = user;
    }

    public void run() {
        synchronized (this) {
            while (true) {
                System.out.println(user.name + "====" + user.sex);
            }
        }
    }
}

執行結果:發現數據沒有混亂了
在這裏插入圖片描述

2.3 改變需求

我覺得這樣輸出也很亂,我想要一個張三,一個小紅,在線程安全的情況下一個一個輸出如何實現:

2.4 wait、notify和方法

  1. 因爲涉及到對象鎖,他們必須都放在synchronized中來使用. wait、notify一定要在synchronized裏面進行使用。
  2. wait() :必須暫定當前正在執行的線程,並釋放資源鎖,讓其他線程可以有機會運行
  3. notify() :喚醒因鎖池中的線程,使之運行

2.6 修改代碼

  1. 修改User類,添加標識
package com.lijie;

public class User {
    String name;
    String sex;
    //線程通訊標識
    public boolean sign = false;
}
  1. 修改輸入類,添加wait和notify方法
package com.lijie;

//輸入類
class InputThread extends Thread {
    private User user;

    public InputThread(User user) {
        this.user = user;
    }

    public void run() {
        int count = 0;
        while (true) {
            synchronized (user) {
                if (user.sign == true) {
                    try {
                        // 當前線程變爲等待,但是可以釋放鎖
                        user.wait();
                    } catch (Exception e) {

                    }
                }
                if (count % 2 == 0) {
                    user.name = "張三";
                    user.sex = "男";
                } else {
                    user.name = "小紅";
                    user.sex = "女";
                }
                count += 1;
                user.sign = true;
                // 喚醒當前線程
                user.notify();
            }
        }
    }
}
  1. 修改輸出類,添加wait和notify方法
package com.lijie;

//輸出類
class OutThread extends Thread {
    private User user;

    public OutThread(User user) {
        this.user = user;
    }

    public void run() {
        while (true) {
            synchronized (user) {
                if (user.sign==false) {
                    try {
                        // 當前線程變爲等待,但是可以釋放鎖
                        user.wait();
                    } catch (Exception e) {

                    }
                }
                System.out.println(user.name + "====" + user.sex);
                user.sign = false;
                // 喚醒當前線程
                user.notify();
            }
        }
    }
}

執行結果:
在這裏插入圖片描述

2.7 wait與sleep區別

  1. sleep()方法,是屬於Thread類中的。
  2. wait()方法,則是屬於Object類中的。
  3. sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是沒有釋放鎖
  4. wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備

3 Lock鎖

在 jdk1.5 之後,併發包中新增了 Lock 接口(以及相關實現類)用來實現鎖功能,Lock 接口提供了與 synchronized 關鍵字類似的同步功能,但需要在使用時手動獲取鎖和釋放鎖。

3.1 Lock寫法

 	private Lock lock = new ReentrantLock();
    
    //使用完畢釋放後其他線程才能獲取鎖
    public void lockTest(Thread thread) {
        lock.lock();//獲取鎖
        try {

        } catch (Exception e) {

        }finally {
            lock.unlock(); //釋放鎖
        }

3.2Lock鎖示例代碼

package com.lijie;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    private Lock lock = new ReentrantLock();

    //使用完畢釋放後其他線程才能獲取鎖
    public void lockTest(Thread thread) {
        lock.lock();//獲取鎖
        try {
            System.out.println("線程" + thread.getName() + "獲取鎖");
        } catch (Exception e) {
            System.out.println("線程" + thread.getName() + "發生了異常");
        } finally {
            System.out.println("線程" + thread.getName() + "執行完畢釋放鎖");
            lock.unlock(); //釋放鎖
        }
    }

    public static void main(String[] args) {
        LockTest lockTest = new LockTest();
        Thread thread1 = new Thread(new Runnable() {

            public void run() {
                lockTest.lockTest(Thread.currentThread());
            }
        }, "線程一");

        Thread thread2 = new Thread(new Runnable() {

            public void run() {
                lockTest.lockTest(Thread.currentThread());
            }
        }, "線程二");
        // 啓動2個線程
        thread2.start();
        thread1.start();
    }
}

4 Condition

Condition與Lock的關係就類似於synchronized與Object.wait()/signal()

  1. Conditionaw.ait():必須暫定當前正在執行的線程,並釋放資源鎖,讓其他線程可以有機會運行。和Object.wait()方法很相似。
  2. Conditionaw.singal():喚醒因鎖池中的線程,使之運行。和Object.wait()方法很相似。

4.1 Condition和Lock代碼示例

package com.lijie;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class User {
    public String name;
    public String sex;
    //定義標識變量
    public boolean flag = false;
    //定義Lock鎖和ReentrantLock實現類
    Lock lock = new ReentrantLock();
}

class InputThread extends Thread {
    private User user;
    //定義Condition屬性,靠他來釋放鎖和喚醒鎖
    Condition newCondition;

    //構造參數賦值
    public InputThread(User user, Condition newCondition) {
        this.user = user;
        this.newCondition = newCondition;
    }

    //使用完畢釋放後其他線程才能獲取鎖
    public void run() {
        int count = 0;
        while (true) {
            try {
                //獲取鎖
                user.lock.lock();
                if (user.flag) {
                    try {
                        //使當前線程等待,同時釋放當前鎖
                        newCondition.await();
                    } catch (Exception e) {

                    }
                }
                if (count == 0) {
                    user.name = "張三";
                    user.sex = "男";
                } else {
                    user.name = "小紅";
                    user.sex = "女";
                }
                count = (count + 1) % 2;
                user.flag = true;
                //喚醒一個在等待中的線程
                newCondition.signal();
            } catch (Exception e) {

            } finally {
                //釋放鎖
                user.lock.unlock();
            }
        }
    }
}

class OutThread extends Thread {
    private User user;
    private Condition newCondition;

    public OutThread(User user, Condition newCondition) {
        this.user = user;
        this.newCondition = newCondition;
    }

    public void run() {
        while (true) {
            try {
                user.lock.lock();
                if (!user.flag) {
                    try {
                        newCondition.await();
                    } catch (Exception e) {

                    }
                }
                System.out.println(user.name + "===" + user.sex);
                user.flag = false;
                newCondition.signal();
            } catch (Exception e) {

            } finally {
                user.lock.unlock();
            }
        }

    }
}

public class ThreadDemo1 {
    public static void main(String[] args) {
        User user = new User();
        Condition newCondition = user.lock.newCondition();
        InputThread inputThread = new InputThread(user, newCondition);
        OutThread outThread = new OutThread(user, newCondition);
        inputThread.start();
        outThread.start();
    }
}

5 synchronized與Lock的區別

  1. 首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
  2. synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖;
  3. synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;
  4. 用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,自動放棄的方法tryLock(),具有更完善的錯誤恢復機制。
  5. Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章