1. 程式人生 > >多執行緒通訊(wait、notify、Lock、ThreadLocal)

多執行緒通訊(wait、notify、Lock、ThreadLocal)

多執行緒之間通訊

什麼是多執行緒通訊?

就是多個執行緒對同一個共享資源,進行不同的操作。

介紹兩個API中的方法,這兩個是Object裡面的方法:

wait();等待,執行緒從執行狀態變為休眠狀態

notify();喚醒,執行緒從休眠狀態變為執行狀態

現在解決一下這樣一個案例:

兩個執行緒,面向一個倉庫進行讀寫操作,倉庫裡面用一個使用者類表示,裡面包括姓名和性別這兩個屬性,A執行緒往裡面寫,然後B執行緒立馬讀出來,這樣交替執行,該怎麼設計?

分析一下這個題目:倉庫裡面是兩個屬性,兩個執行緒同時對倉庫進行操作,肯定要同步,不然會出現資料混亂問題,然後考慮的是讓兩個執行緒交替執行,A執行緒寫完後要等待B執行緒讀出以後在繼續寫,這時候要用到執行緒之間的通訊。wait和notify的使用必須與synchronized一起使用,wait包括釋放鎖,並進入阻塞佇列這兩個語義,這兩步需要指定一個監視器來完成;notify是喚醒該執行緒,要想喚醒,首先需要知道該物件在哪兒,需要獲取該物件的鎖,才能去該物件對應的等待佇列去喚醒一個執行緒,只有已經釋放該物件鎖的執行緒,才能被喚醒然後去競爭該物件鎖。

為了更好的看出效果,我讓寫執行緒奇數和偶數是寫入不同的姓名和性別,看是否列印會出現資料混亂。

程式碼如下:

class User {
    String name;
    String sex;
    boolean flag = true;
}
class Write extends Thread {
    User user;
    public Write(User user) {
        this.user = user;
    }
    @Override
    public void run() {
        int count = 2;
        
while (true) { synchronized (user) { if (!user.flag) { try { user.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
if (count % 2 == 0) { user.name = "周瑜"; user.sex = "男"; } else { user.name = "小喬"; user.sex = "女"; } count = (count + 1) % 2; user.notify(); user.flag = false; } } } } class Read extends Thread { User user; public Read(User user) { this.user = user; } @Override public void run() { while (true) { synchronized (user) { if (user.flag) { try { user.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(user.name + "," + user.sex); user.notify(); user.flag = true; } } } } public class OutInputDemo { public static void main(String[] args) { User user = new User(); Write write = new Write(user); Read read = new Read(user); write.start(); read.start(); } }
View Code

wait和sleep的區別:

wait位於同步中,需要釋放鎖的資源,需要被notify喚醒。

sleep不釋放鎖的資源,時間到自然醒。

 Lock鎖

jdk1.5以後,併發包中新增了Lock介面及其相應的實現類來實現鎖的功能,提供了和synchronized一樣的同步功能,但是也有區別。

Lock和synchronized的區別:

synchronized是從程式碼開始上鎖,程式碼結束釋放鎖,完全自動化,這種鎖的效率低、擴充套件性不高。

Lock鎖屬於手動的,手動上鎖,手動釋放鎖,靈活性高

在Lock中,不能使用wait和notify,取而代之的為:

Condition   它的功能類似於Object.wait()和Object.notify()的功能。

Condition condition = lock.newCondition();
condition.await();//相當於wait
condition.signal();//相當於notify

上面的案例用Lock鎖修改為:

class User2 {
    String name;
    String sex;
    boolean flag = true;
    Lock lock = new ReentrantLock();
}
class Write2 extends Thread {
    User2 user;
    Condition condition;
    public Write2(User2 user,Condition condition) {
        this.user = user;
        this.condition = condition;
    }
    @Override
    public void run() {
        int count = 2;
        while (true) {
            try {
                user.lock.lock();
                if (!user.flag) {
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if (count % 2 == 0) {
                    user.name = "周瑜";
                    user.sex = "男";
                } else {
                    user.name = "小喬";
                    user.sex = "女";
                }
                count = (count + 1) % 2;
                condition.signal();
                user.flag = false;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                user.lock.unlock();
            }
        }
    }
}
class Read2 extends Thread {
    User2 user;
    Condition condition;
    public Read2(User2 user,Condition condition) {
        this.user = user;
        this.condition = condition;
    }
    @Override
    public void run() {
        while (true) {
            try {
                user.lock.lock();
                if (user.flag) {
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(user.name + "," + user.sex);
                condition.signal();
                user.flag = true;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                user.lock.unlock();
            }
        }
    }
}
public class OutInputDemo2 {
    public static void main(String[] args) {
        User2 user = new User2();
        Condition condition = user.lock.newCondition();
        Write2 write = new Write2(user,condition);
        Read2 read = new Read2(user,condition);
        write.start();
        read.start();
    }
}
View Code

怎麼來停止執行緒???

stop()???

這個方法已經被棄用,不推薦使用,太暴力,不可恢復,就會導致不安全。

我麼使用interrupt來停止執行緒,API中還有Thread.currentThread().isInterrupted()來進行判斷是否中斷了執行緒,案例如下:

class StopThreadDemo2 extends Thread{
    @Override
    public synchronized void run() {
        while(!Thread.currentThread().isInterrupted()){
            for (int i = 0; i < 30; i++) {
                System.out.println(i);
            }
        }
        System.out.println("Thread is interrupt!");
    }
}
public class InterruptDemo {
    public static void main(String[] args) {
        StopThreadDemo2 stopThreadDemo = new StopThreadDemo2();
        stopThreadDemo.start();
        for (int i = 0; i < 10; i++) {
            if (i == 2) {
                stopThreadDemo.interrupt();
            }
            System.out.println("主執行緒"+i);
        }
    }
}

 ThreadLocal

本地執行緒,為每一個執行緒提供一個區域性變數。

定義的變數不會共享,是自己的本地區域性變數。

看下面這個案例:

class Number {
    int count = 0;
    public int getNumber() {
        count = count + 1;
        return count;
    }
}
class ThreadLocalThread extends Thread {
    Number number;
    public ThreadLocalThread(Number number) {
        this.number = number;
    }
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + number.getNumber());
        }
    }
}
public class ThreadLocalDemo {
    public static void main(String[] args) {
        Number number1 = new Number();
        Number number2 = new Number();
        Number number3 = new Number();
        ThreadLocalThread threadLocalThread1 = new ThreadLocalThread(number1);
        ThreadLocalThread threadLocalThread2 = new ThreadLocalThread(number2);
        ThreadLocalThread threadLocalThread3 = new ThreadLocalThread(number3);
        threadLocalThread1.start();
        threadLocalThread2.start();
        threadLocalThread3.start();
    }
}

 這個案例是三個執行緒分別用來生成自己的數字number,我們定義了三個Number物件,如果有100個執行緒,是不是需要定義100個number物件,該怎麼解決這個問題呢???

class Number {
    int count;
    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
        protected Integer initialValue() {//初始化threadLocal.get()的值
            return 0;
        };
    };
    public int getNumber() {
        count = threadLocal.get() + 1;
        threadLocal.set(count);//更新threadLocal裡面的值
        return count;
    }
}
class ThreadLocalThread extends Thread {
    Number number;
    public ThreadLocalThread(Number number) {
        this.number = number;
    }
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + number.getNumber());
        }
    }
}
public class ThreadLocalDemo {
    public static void main(String[] args) {
        Number number = new Number();
        ThreadLocalThread threadLocalThread1 = new ThreadLocalThread(number);
        ThreadLocalThread threadLocalThread2 = new ThreadLocalThread(number);
        ThreadLocalThread threadLocalThread3 = new ThreadLocalThread(number);
        threadLocalThread1.start();
        threadLocalThread2.start();
        threadLocalThread3.start();
    }
}

通過get()和set()進行對本地區域性變數的更新。

原理:Map集合儲存

get()原始碼解析:

 public T get() {
        Thread t = Thread.currentThread();//獲取當前執行緒
        ThreadLocalMap map = getMap(t);//獲取當前執行緒的ThreadLocalMap集合,
        if (map != null) {//判斷是否存在該執行緒的Map集合
            ThreadLocalMap.Entry e = map.getEntry(this);//然後就判斷該集合裡面是否有該物件的值,有的話,就返回存在的值,沒有就返回初始值
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

set()原始碼:

public void set(T value) {//獲取當前執行緒,看是否存在ThreadLocalMap,存在就直接放裡面放值,不存在就建立一個ThreadLocalMap
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }