一文詳解wait與notify

內(nèi)容簡介

本章需要重點掌握的技術(shù)點如下:
1)使用wait/notify實現(xiàn)線程間的通信毕箍。
2)生產(chǎn)者/消費者模式的實現(xiàn)。
3)方法join的使用枣耀。
4)ThreadLocal類的使用霉晕。

3.1 等待/通知機制

3.1.1 不使用等待/通知機制實現(xiàn)線程間的通信

public class MyList {
    private List list = new ArrayList();
    public void add(){
        list.add("");
    }
    public int size(){
        return list.size();
    }
}
public class ThreadA extends Thread {
    private MyList myList;
    public ThreadA(MyList myList) {
        this.myList = myList;
    }
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                myList.add();
                System.out.println("添加了" + (i + 1) + "個元素");
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ThreadB extends Thread {

    private volatile MyList myList;

    public ThreadB(MyList myList) {
        this.myList = myList;
    }

    @Override
    public void run() {
        try {
            while (true) {
                // System.out.print("");
                if (myList.size() == 5) {
                    System.out.println("==5,線程b退出來");
                    throw new InterruptedException();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class Run {
    public static void main(String[] args) {
        MyList myList = new MyList();
        ThreadA a = new ThreadA(myList);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(myList);
        b.setName("b");
        b.start();
    }
}
添加了1個元素
添加了2個元素
添加了3個元素
添加了4個元素
添加了5個元素
==5,線程b退出來
java.lang.InterruptedException
    at part6.ThreadB.run(ThreadB.java:18)
添加了6個元素
添加了7個元素
添加了8個元素
添加了9個元素
添加了10個元素

雖然兩個線程實現(xiàn)了通信,但是是通過while語句輪詢檢測實現(xiàn)的捞奕,輪詢時間過小浪費CPU資源牺堰,輪詢時間過大有可能會取不到想要的數(shù)據(jù),這時候就可以用到wait/notify機制颅围。

3.1.3 等待/通知機制的實現(xiàn)

wait()的作用是使當(dāng)前執(zhí)行代碼的線程進行等待伟葫。wait()是Object類的方法, 該方法用來將當(dāng)前線程置入“預(yù)執(zhí)行隊列”中院促,在調(diào)用wait()所在代碼行處停止執(zhí)行筏养,直到接到通知或被中斷 斧抱。在調(diào)用wait()之前,線程必須獲取該對象的對象級別鎖渐溶,只能在同步方法或同步代碼塊中調(diào)用wait()方法辉浦。執(zhí)行wait()之后,當(dāng)前線程釋放鎖茎辐。如果調(diào)用wait()時沒有持有適當(dāng)?shù)逆i宪郊,則會拋出IllegalMonitorStateException网杆。

方法notify()生棍,也要在同步方法或同步代碼塊中調(diào)用免胃,用來通知那些等待該對象鎖的其他線程恒水,如果多個線程等待鸟蟹,則由線程規(guī)劃器隨機挑選一個呈wait狀態(tài)的線程油狂,對其發(fā)起通知健霹。在執(zhí)行notify()方法后买置,當(dāng)前線程不會馬上釋放對象鎖速警,當(dāng)前線程釋放該對象鎖之后叹誉,呈wait狀態(tài)的線程才可以獲取鎖。如果調(diào)用notify()時沒有持有適當(dāng)?shù)逆i坏瞄,則會拋出IllegalMonitorStateException桂对。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        String lock = new String();
        System.out.println("sync上面");
        synchronized (lock){
            System.out.println("sync第一行");
            lock.wait();
            System.out.println("wait下面");
        }
        System.out.println("end");
    }
}
sync上面
sync第一行

根據(jù)上面的執(zhí)行結(jié)果來看,線程進入wait狀態(tài)鸠匀,不會繼續(xù)向下運行蕉斜,讓它繼續(xù)執(zhí)行,可以使用notify()notifyAll()缀棍。

public class Mythread1 extends Thread {
    private Object lock;
    public Mythread1(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        try {
            synchronized (lock) {
                System.out.println("start wait time =" + System.currentTimeMillis());
                lock.wait();
                System.out.println("end wait time   =" + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Mythread2 extends Thread {

    private Object lock;

    public Mythread2(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("start notify time =" + System.currentTimeMillis());
            lock.notify();
            System.out.println("end   notify time =" + System.currentTimeMillis());
        }
    }
}
public class Run {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Mythread1 mythread1 = new Mythread1(lock);
        mythread1.start();
        Thread.sleep(3000);
        Mythread2 mythread2 = new Mythread2(lock);
        mythread2.start();
    }
}
start wait time =1588821820237
start notify time =1588821823237
end   notify time =1588821823237
end wait time   =1588821823237

從上面打印信息來看宅此,Mythread1先執(zhí)行進入wait狀態(tài),3秒后被notify喚醒爬范。

public class MyList {
    private static List list = new ArrayList();
    public static void add() {
        list.add("");
    }
    public static int size() {
        return list.size();
    }
}
public class ThreadA extends Thread {
    private Object lock;
    public ThreadA(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        try {
            synchronized (lock) {
                if (MyList.size() != 5) {
                    System.out.println("wait begin " + System.currentTimeMillis());
                    lock.wait();
                    System.out.println("wait   end " + System.currentTimeMillis());
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadB extends Thread {
    private Object lock;
    public ThreadB(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        try {
            synchronized (lock) {
                for (int i = 0; i < 10; i++) {
                    MyList.add();
                    System.out.println("添加了" + (i + 1) + "個元素");
                    if (MyList.size() == 5) {
                        lock.notify();
                        System.out.println("notify end");
                    }
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        ThreadA a = new ThreadA(object);
        a.start();
        Thread.sleep(50);
        ThreadB b = new ThreadB(object);
        b.start();
    }
}
wait begin 1588829402319
添加了1個元素
添加了2個元素
添加了3個元素
添加了4個元素
添加了5個元素
notify end
添加了6個元素
添加了7個元素
添加了8個元素
添加了9個元素
添加了10個元素
wait end 1588829412382

日志wait end...在最后輸出父腕,也驗證了我們之前的結(jié)論:noitfy()方法執(zhí)行完之后不會立即釋放鎖。

synchronized可以將任何一個Object對象作為同步對象來對待青瀑,而Java為每個Object都實現(xiàn)了wait()notify()璧亮,他們必須用在被synchronized同步的Object的臨界區(qū)內(nèi)。
wait()可以使處于臨界區(qū)內(nèi)的線程進入等待狀態(tài)斥难,同時釋放被同步的對象的鎖枝嘶。
notify()可以喚醒一個因調(diào)用了wait()操作而處于阻塞狀態(tài)中的線程進入就緒狀態(tài)。被重新喚醒的線程會試圖重新獲取臨界區(qū)的控制權(quán)哑诊。

線程狀態(tài)切換示意圖

1)new Thread()后群扶,調(diào)用start(),系統(tǒng)會為此線程分配CPU資源,使其處于Runnable狀態(tài)竞阐,當(dāng)線程搶占到CPU資源缴饭,此線程就處于Running狀態(tài)。
2)Runnable狀態(tài)和Runnable可相互切換骆莹,線程在Running狀態(tài)時颗搂,調(diào)用yield()使當(dāng)前線程重新回到Runnable狀態(tài)。
3)Blocked是線程因為某種原因放棄CPU使用權(quán)汪疮,暫時停止運行峭火。直到線程進入就緒狀態(tài),才有機會轉(zhuǎn)到運行狀態(tài)智嚷。

線程進入Runnable狀態(tài)大致有以下5中情況:
1.調(diào)用sleep()方法后經(jīng)過的時間查過了指定的休眠時間。
2.線程調(diào)用的阻塞IO已經(jīng)返回纺且,阻塞方法執(zhí)行完畢盏道。
3.線程成功獲得視圖同步的監(jiān)視器。
4.線程正在等待某個通知载碌,其他線程發(fā)出了通知猜嘱。
5.處于掛起狀態(tài)的線程調(diào)用了resume()。
線程進入Blocked狀態(tài)大致有以下5中情況:
1.線程調(diào)用sleep()嫁艇,主動放棄占用的處理器資源朗伶。
2.線程調(diào)用的阻塞IO方法,在該方法返回前步咪,該線程被阻塞论皆。
3.線程視圖獲得一個同步監(jiān)視器,但同步監(jiān)視器正被其他線程持有猾漫。
4.線程等待某個通知点晴。
5.調(diào)用suspend()將該線程掛起。

3.1.4 方法wait()鎖釋放與notify()鎖不釋放

下面我們進行試驗wait鎖釋放悯周,代碼如下:

public class Service {
    public void testMethod(Object lock){
        try {
            synchronized (lock){
                System.out.println("begin wait()");
                lock.wait();
                System.out.println("  end wait()");
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private Object lock;
    public ThreadA(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class ThreadB extends Thread {
    private Object lock;
    public ThreadB(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class Test {
    public static void main(String[] args) {
        Object lock = new Object();
        ThreadA a = new ThreadA(lock);
        a.start();
        ThreadB b = new ThreadB(lock);
        b.start();
    }
}

執(zhí)行結(jié)果如下:

begin wait()
begin wait()
結(jié)論:當(dāng)wait()被執(zhí)行后粒督,鎖被自動釋放。

下面來驗證:notify()被執(zhí)行后不釋放鎖禽翼。

public class Service {

    public void testMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin wait() ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
                lock.wait();
                System.out.println("  end wait() ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void synNotifyMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin notify() ThreadName=" + Thread.currentThread().getName() + "  time=" + System.currentTimeMillis() + " time=" + System.currentTimeMillis());
                lock.notify();
                Thread.sleep(5000);
                System.out.println("  end notify() ThreadName=" + Thread.currentThread().getName() + "  time=" + System.currentTimeMillis() + " time=" + System.currentTimeMillis());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private Object lock;
    public ThreadA(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class NotifyThread extends Thread {
    private Object lock;
    public NotifyThread(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }
}
public class SynNotifyMethodThread extends Thread {
    private Object lock;
    public SynNotifyMethodThread(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }
}
public class Test {

    public static void main(String[] args) {
        Object lock = new Object();
        ThreadA a = new ThreadA(lock);
        a.start();
        NotifyThread notifyThread = new NotifyThread(lock);
        notifyThread.start();
        SynNotifyMethodThread syn = new SynNotifyMethodThread(lock);
        syn.start();
    }
}
begin wait() ThreadName=Thread-0 time=1590474000435
begin notify() ThreadName=Thread-2  time=1590474000436 time=1590474000436
  end notify() ThreadName=Thread-2  time=1590474005436 time=1590474005436
  end wait() ThreadName=Thread-0 time=1590474005436
begin notify() ThreadName=Thread-1  time=1590474005436 time=1590474005436
  end notify() ThreadName=Thread-1  time=1590474010436 time=1590474010436
結(jié)論:必須執(zhí)行完notify方法所在的同步synchronized代碼塊之后才釋放鎖屠橄。

3.1.5 當(dāng)interrupt方法遇到wait方法

當(dāng)線程呈wait狀態(tài)時,調(diào)用線程對象的interrupt方法時闰挡,會出現(xiàn)InterruptException異常锐墙。
public class Service {

    public void testMethod(Object lock){
        try {
            synchronized (lock){
                System.out.println("begin wait");
                lock.wait();
                System.out.println("  end wait");
            }
        }catch (Exception e){

        }
    }
}
public class ThreadA extends Thread {
    private Object lock;
    public ThreadA(Object lock){
        this.lock = lock;
    }
    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class Test {
    public static void main(String[] args) {
        try {
            Object lock = new Object();
            ThreadA a = new ThreadA(lock);
            a.start();
            Thread.sleep(5000);
            a.interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
begin wait
java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)

結(jié)論:
執(zhí)行同步代碼塊的過程中,執(zhí)行鎖所屬對象的wait()方法后解总,這個線程會釋放對象鎖贮匕,而此線程對象會進入線程等待池中,等待被喚醒花枫,如果在這個過程中斷該線程時刻盐,會出現(xiàn)InterruptException異常掏膏。

3.1.6 只通知一個線程與通知所有線程

下面來驗證:調(diào)用notify()時,一次只隨機通知一個線程進行喚醒敦锌。

public class Service extends Thread {

    public void testMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin wait ThreadName=" + Thread.currentThread().getName());
                lock.wait();
                System.out.println("  end wait ThreadName=" + Thread.currentThread().getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private Object lock;
    public ThreadA(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class ThreadB extends Thread {
    private Object lock;
    public ThreadB(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class ThreadC extends Thread {
    private Object lock;
    public ThreadC(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class NotifyThread extends Thread {
    private Object lock;
    public NotifyThread(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        synchronized (lock) {
            lock.notify();
        }
    }
}
public class Test {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        ThreadA a = new ThreadA(lock);
        a.start();
        ThreadB b = new ThreadB(lock);
        b.start();
        ThreadC c = new ThreadC(lock);
        c.start();
        Thread.sleep(1000);
        NotifyThread notifyThread = new NotifyThread(lock);
        notifyThread.start();
    }
}
begin wait ThreadName=Thread-0
begin wait ThreadName=Thread-3
begin wait ThreadName=Thread-1
  end wait ThreadName=Thread-0
結(jié)論:調(diào)用notify方法后馒疹,隨機通知一個等待該對象的對象鎖的其他線程。

那么如何一次性喚醒所有等待該對象的對象鎖的其他線程呢乙墙?修改NotifyThread.java代碼如下:

public class NotifyThread extends Thread {
    private Object lock;
    public NotifyThread(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        synchronized (lock) {
            lock.notifyAll();
        }
    }
}
begin wait ThreadName=Thread-0
begin wait ThreadName=Thread-1
begin wait ThreadName=Thread-2
  end wait ThreadName=Thread-2
  end wait ThreadName=Thread-1
  end wait ThreadName=Thread-0

3.1.8 方法wait(long)的使用

wait(long)方法的功能是等待某一時間內(nèi)是否有線程對鎖進行喚醒颖变,如果超過這個時間則自動喚醒。
public class MyRunnable {
    static private Object lock = new Object();
    static private Runnable runnable = new Runnable() {
        public void run() {
            try {
                synchronized (lock) {
                    System.out.println("wait begin timer=" + System.currentTimeMillis());
                    lock.wait(5000);
                    System.out.println("wait   end timer=" + System.currentTimeMillis());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    static private Runnable runnable2 = new Runnable() {
        public void run() {
            synchronized (lock){
                System.out.println("notify begin timer=" + System.currentTimeMillis());
                lock.notify();
                System.out.println("notify   end timer=" + System.currentTimeMillis());
            }
        }
    };
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(runnable);
        t.start();
    }
}
wait begin timer=1590475975460
wait   end timer=1590475980460

3.1.9 等待wait的條件發(fā)生變化听想。

在使用wait/notify模式時腥刹,還需要注意另一正情況,也就是wait的等待條件發(fā)生變化汉买,容易造成程序邏輯的混亂衔峰。

public class ValueObject {
    public static List<String> list = new ArrayList<String>();
}
public class Add {
    private String lock;
    public Add(String lock) {
        this.lock = lock;
    }
    public void add() {
        synchronized (lock) {
            ValueObject.list.add("lalala");
            lock.notifyAll();
        }
    }
}
public class AddThread extends Thread {
    public Add add;

    public AddThread(Add add) {
        this.add = add;
    }
    @Override
    public void run() {
        add.add();
    }
}
public class Subtract {
    private String lock;

    public Subtract(String lock) {
        this.lock = lock;
    }
    public void subtract() {
        try {
            synchronized (lock) {
                if (ValueObject.list.size() == 0) {
                    System.out.println("wait begin ThreadName=" + Thread.currentThread().getName());
                    lock.wait();
                    System.out.println("wait   end ThreadName=" + Thread.currentThread().getName());
                }
                System.out.println("do remove threadName=" + Thread.currentThread().getName());
                ValueObject.list.remove(0);
                System.out.println("do remove end threadName=" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class SubtractThread extends Thread {
    public Subtract subtract;
    public SubtractThread(Subtract subtract) {
        this.subtract = subtract;
    }
    @Override
    public void run() {
        subtract.subtract();
    }
}
public class Run {

    public static void main(String[] args) throws InterruptedException {
        String lock = "";
        Add add = new Add(lock);
        Subtract subtract = new Subtract(lock);
        SubtractThread subtractThread = new SubtractThread(subtract);
        subtractThread.setName("sub1");
        subtractThread.start();

        SubtractThread subtractThread2 = new SubtractThread(subtract);
        subtractThread2.setName("sub2");
        subtractThread2.start();
        Thread.sleep(1000);
        AddThread addThread = new AddThread(add);
        addThread.setName("add");
        addThread.start();
    }
}
wait begin ThreadName=sub2
wait begin ThreadName=sub1
wait   end ThreadName=sub1
do remove threadName=sub1
do remove end threadName=sub1
wait   end ThreadName=sub2
do remove threadName=sub2
Exception in thread "sub2" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:657)
    at java.util.ArrayList.remove(ArrayList.java:496)

出現(xiàn)上述問題的原因是Thread.sleep()之前,sub1蛙粘,sub2線程都都執(zhí)行了wait方法垫卤,進入WAITING狀態(tài),在add線程執(zhí)行完成之后通過notifyAdd()喚醒兩個線程出牧,在一個線程執(zhí)行完remove之后穴肘,list的size為零,這時候舔痕,另一個線程再進行remove操作的時候就拋出了IndexOutOfBoundsException评抚。
那么下面我們修改Subtract.java代碼如下:

public class Subtract {
    private String lock;

    public Subtract(String lock) {
        this.lock = lock;
    }
    public void subtract() {
        try {
            synchronized (lock) {
                while (ValueObject.list.size() == 0) {
                    System.out.println("wait begin ThreadName=" + Thread.currentThread().getName());
                    lock.wait();
                    System.out.println("wait   end ThreadName=" + Thread.currentThread().getName());
                }
                System.out.println("do remove threadName=" + Thread.currentThread().getName());
                ValueObject.list.remove(0);
                System.out.println("do remove end threadName=" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
wait begin ThreadName=sub1
wait begin ThreadName=sub2
wait   end ThreadName=sub2
do remove threadName=sub2
do remove end threadName=sub2
wait   end ThreadName=sub1
wait begin ThreadName=sub1

通過上面打印信息可以看出,當(dāng)兩個線程同時被喚醒之后赵讯,兩個SubtractThread線程其中一個執(zhí)行完remove之后盈咳,另一個線程執(zhí)行完wait()方法之后的代碼,因滿足ValueObject.list.size() == 0條件边翼,重新進入WAITING狀態(tài)鱼响。

3.1.11 生產(chǎn)者/消費者模式的實現(xiàn)。

等待/通知模式的經(jīng)典案例就是生產(chǎn)者/消費者模式组底。

3.1.11.1 一生產(chǎn)與一消費:操作值

public class ValueObject {
    public static String value = "";
}
public class Producer {
    private String lock;
    public Producer(String lock) {
        this.lock = lock;
    }
    public void setValue() {
        try {
            synchronized (lock){
                if (!"".equals(ValueObject.value)) {
                    lock.wait();
                }
                String value = System.currentTimeMillis() + "_" + System.nanoTime();
                System.out.println("put value = " + value);
                ValueObject.value = value;
                lock.notify();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ProducerThread extends Thread {
    private Producer producer;
    public ProducerThread(Producer producer) {
        this.producer = producer;
    }
    @Override
    public void run() {
        while (true) {
            producer.setValue();
        }
    }
}
public class Consumer {
    private String lock;
    public Consumer(String lock) {
        this.lock = lock;
    }
    public void getValue() {
        synchronized (lock) {
            if ("".equals(ValueObject.value)) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("get Value = " + ValueObject.value);
            ValueObject.value = "";
            lock.notify();
        }
    }
}

public class ConsumerThread extends Thread {
    private Consumer consumer;
    public ConsumerThread(Consumer consumer) {
        this.consumer = consumer;
    }
    @Override
    public void run() {
        while (true) {
            consumer.getValue();
        }
    }
}
public class Run {
    public static void main(String[] args) {
        String lock = "";
        Producer producer = new Producer(lock);
        Consumer consumer = new Consumer(lock);
        ProducerThread producerThread = new ProducerThread(producer);
        ConsumerThread consumerThread = new ConsumerThread(consumer);
        producerThread.start();
        consumerThread.start();
    }
}
put value = 1590487918891_263135745700279
get Value = 1590487918891_263135745700279

put value = 1590487918891_263135745708299
get Value = 1590487918891_263135745708299

put value = 1590487918891_263135745716318
get Value = 1590487918891_263135745716318

put value = 1590487918891_263135745729149
get Value = 1590487918891_263135745729149

put value = 1590487918891_263135745736848
get Value = 1590487918891_263135745736848

put value = 1590487918891_263135745744547
get Value = 1590487918891_263135745744547

···

當(dāng)前只是一個生產(chǎn)者與一個消費者進行的數(shù)據(jù)交互丈积。但是如果在此實驗的基礎(chǔ)上,出現(xiàn)多個生產(chǎn)與消費者债鸡,那么在運行的時候極有可能出現(xiàn)“假死”的情況江滨,也就是所有的線程都呈WAITING狀態(tài)。

3.1.11.2 多生產(chǎn)與多消費:假死

public class Producer {
    private Object lock;
    private List<String> list;
    public Producer(Object lock, List<String> list) {
        this.lock = lock;
        this.list = list;
    }
    public void setValue() {
        try {
            synchronized (lock) {
                if (list.size() != 0) {
                    System.out.println("生產(chǎn)者 :" + Thread.currentThread().getName() + " 處于WAITING");
                    lock.wait();
                }
                String value = System.currentTimeMillis() + "_" + System.nanoTime();
                System.out.println("生產(chǎn)者 :" + Thread.currentThread().getName() + " 處于RUNNABLE");
                list.add(value);
                lock.notify();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ProducerThread extends Thread {
    private Producer producer;
    public ProducerThread(Producer producer) {
        this.producer = producer;
    }
    @Override
    public void run() {
        while (true) {
            producer.setValue();
        }
    }
}
public class Consumer {
    private Object lock;
    private List<String> list;
    public Consumer(Object lock, List<String> list) {
        this.lock = lock;
        this.list = list;
    }
    public void getValue() {
        try {
            synchronized (lock) {
                if (list.size() == 0) {
                    System.out.println("消費者 :" + Thread.currentThread().getName() + " 處于WAITING");
                    lock.wait();
                }
                System.out.println("消費者 :" + Thread.currentThread().getName() + " 處于RUNNABLE");
                list.remove(0);
                lock.notify();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ConsumerThread extends Thread {
    private Consumer consumer;
    public ConsumerThread(Consumer consumer) {
        this.consumer = consumer;
    }
    @Override
    public void run() {
        while (true) {
            consumer.getValue();
        }
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        List<String> list = new ArrayList<String>();
        Consumer consumer = new Consumer(lock, list);
        Producer producer = new Producer(lock, list);
        ConsumerThread[] consumerThread = new ConsumerThread[2];
        ProducerThread[] producerThread = new ProducerThread[2];
        for (int i = 0; i < 2; i++) {
            consumerThread[i] = new ConsumerThread(consumer);
            consumerThread[i].setName("消費者" + (i + 1));
            producerThread[i] = new ProducerThread(producer);
            producerThread[i].setName("生產(chǎn)者" + (i + 1));
            consumerThread[i].start();
            producerThread[i].start();
        }
        Thread.sleep(5000);
        // Thread.currentThread().getThreadGroup().activeCount() 活動線程數(shù)
        Thread[] threadArray = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
        // 每個活動線程的線程組及其子組復(fù)制到指定的數(shù)組中
        Thread.currentThread().getThreadGroup().enumerate(threadArray);
        for (Thread thread : threadArray) {
            System.out.println(thread.getName() + " " + thread.getState());
        }
    }
}
···
生產(chǎn)者 :生產(chǎn)者2 處于RUNNABLE
生產(chǎn)者 :生產(chǎn)者2 處于WAITING
生產(chǎn)者 :生產(chǎn)者1 處于WAITING
消費者 :消費者1 處于RUNNABLE
消費者 :消費者1 處于WAITING
消費者 :消費者2 處于RUNNABLE
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:657)
    at java.util.ArrayList.remove(ArrayList.java:496)
    at part17.Consumer.getValue(Consumer.java:20)
    at part17.ConsumerThread.run(ConsumerThread.java:11)
消費者 :消費者2 處于WAITING
main RUNNABLE
Monitor Ctrl-Break RUNNABLE
消費者1 WAITING
生產(chǎn)者1 WAITING
消費者2 WAITING
生產(chǎn)者2 WAITING

出現(xiàn)都在等待狀態(tài)的原因是:代碼中確實已經(jīng)通過notify/wait進行通信了厌均,但不保證notify喚醒的也許是同類(生產(chǎn)者喚醒生產(chǎn)者唬滑、消費者喚醒消費者),如果是按照這種情況運行,那么就會造成該情況的出現(xiàn)晶密。解決方案就是使用notifyAll方法即可擒悬。

3.1.11.3 一生產(chǎn)與一消費:操作棧

生產(chǎn)者向堆棧List對象中放入數(shù)據(jù),使消費者從List堆棧中取出數(shù)據(jù)稻艰。

public class MyStack {
    private List<String> list = new ArrayList();

    public synchronized void push() {
        System.out.println("do push threadName=" + Thread.currentThread().getName());
        try {
            if (list.size() == 1) {
                wait();
            }
            list.add("anyString=" + Math.random());
            notify();
            System.out.println("push size = " + list.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized String pop() {
        String returnValue = "";
        System.out.println("do pop threadName=" + Thread.currentThread().getName());
        try {
            if (list.size() == 0) {
                System.out.println("pop操作中的:" + Thread.currentThread().getName() + "線程呈WAITING");
                wait();
            }
            returnValue = "" + list.get(0);
            list.remove(0);
            notify();
            System.out.println("pop=" + list.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return returnValue;
    }
}
public class Producer {
    private MyStack myStack;
    public Producer(MyStack myStack) {
        this.myStack = myStack;
    }
    public void pushService() {
        myStack.push();
    }
}
public class ProducerThread extends Thread {
    private Producer producer;
    public ProducerThread(Producer producer) {
        this.producer = producer;
    }
    @Override
    public void run() {
        while (true) {
            producer.pushService();
        }
    }
}
public class Consumer {
    private MyStack myStack;
    public Consumer(MyStack myStack) {
        this.myStack = myStack;
    }
    public void popService() {
        myStack.pop();
    }
}
public class ConsumerThread extends Thread {
    private Consumer consumer;
    public ConsumerThread(Consumer consumer) {
        this.consumer = consumer;
    }
    @Override
    public void run() {
        while (true){
            consumer.popService();
        }
    }
}
public class Run {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        Producer producer = new Producer(myStack);
        Consumer consumer = new Consumer(myStack);
        ProducerThread producerThread = new ProducerThread(producer);
        ConsumerThread consumerThread = new ConsumerThread(consumer);
        producerThread.start();
        consumerThread.start();
    }
}
···
do push threadName=Thread-0
push size = 1
do push threadName=Thread-0
do pop threadName=Thread-1
pop=0
do pop threadName=Thread-1
pop操作中的:Thread-1線程呈WAITING
push size = 1
do push threadName=Thread-0
pop=0
do pop threadName=Thread-1
pop操作中的:Thread-1線程呈WAITING
push size = 1
do push threadName=Thread-0
pop=0
do pop threadName=Thread-1
···

3.1.11.4 一生產(chǎn)與多消費-操作棧:解決wait條件改變與假死

使用上一節(jié)的代碼懂牧,修改Run方法如下:

public class Run {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        Producer producer = new Producer(myStack);
        Consumer consumer1 = new Consumer(myStack);
        Consumer consumer2 = new Consumer(myStack);
        Consumer consumer3 = new Consumer(myStack);
        Consumer consumer4 = new Consumer(myStack);
        Consumer consumer5 = new Consumer(myStack);
        ProducerThread producerThread = new ProducerThread(producer);
        producerThread.start();
        ConsumerThread consumerThread1 = new ConsumerThread(consumer1);
        ConsumerThread consumerThread2 = new ConsumerThread(consumer2);
        ConsumerThread consumerThread3 = new ConsumerThread(consumer3);
        ConsumerThread consumerThread4 = new ConsumerThread(consumer4);
        ConsumerThread consumerThread5 = new ConsumerThread(consumer5);
        consumerThread1.start();
        consumerThread2.start();
        consumerThread3.start();
        consumerThread4.start();
        consumerThread5.start();
    }
}
do push threadName=Thread-0
push size = 1
do push threadName=Thread-0
do pop threadName=Thread-5
pop=0
do pop threadName=Thread-5
pop操作中的:Thread-5線程呈WAITING
do pop threadName=Thread-4
pop操作中的:Thread-4線程呈WAITING
push size = 1
do push threadName=Thread-0
pop=0
do pop threadName=Thread-5
pop操作中的:Thread-5線程呈WAITING
Exception in thread "Thread-4" do pop threadName=Thread-3
pop操作中的:Thread-3線程呈WAITING
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:657)
    at java.util.ArrayList.get(ArrayList.java:433)
    at com.ykc.m.MyStack.pop(MyStack.java:31)
    at com.ykc.m.Consumer.popService(Consumer.java:9)
    at com.ykc.m.ConsumerThread.run(ConsumerThread.java:11)
do pop threadName=Thread-2
pop操作中的:Thread-2線程呈WAITING
do pop threadName=Thread-1
pop操作中的:Thread-1線程呈WAITING

由于條件發(fā)生改變沒有及時得到響應(yīng),所以多個呈wait狀態(tài)的線程被喚醒尊勿,執(zhí)行remove方法時出現(xiàn)異常僧凤,我們可以吧if改成while即可,代碼如下:

public class MyStack {
    private List<String> list = new ArrayList();

    public synchronized void push() {
        System.out.println("do push threadName=" + Thread.currentThread().getName());
        try {
            while (list.size() == 1) {
                wait();
            }
            list.add("anyString=" + Math.random());
            notify();
            System.out.println("push size = " + list.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized String pop() {
        String returnValue = "";
        System.out.println("do pop threadName=" + Thread.currentThread().getName());
        try {
            while (list.size() == 0) {
                System.out.println("pop操作中的:" + Thread.currentThread().getName() + "線程呈WAITING");
                wait();
            }
            returnValue = "" + list.get(0);
            list.remove(0);
            notify();
            System.out.println("pop=" + list.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return returnValue;
    }
}
···
do pop threadName=Thread-5
pop操作中的:Thread-5線程呈WAITING
do pop threadName=Thread-1
pop操作中的:Thread-1線程呈WAITING
do pop threadName=Thread-2
pop操作中的:Thread-2線程呈WAITING
do pop threadName=Thread-4
pop操作中的:Thread-4線程呈WAITING
push size = 1
do push threadName=Thread-0
pop=0
do pop threadName=Thread-3
pop操作中的:Thread-3線程呈WAITING
pop操作中的:Thread-1線程呈WAITING
pop操作中的:Thread-5線程呈WAITING

異常的問題是解決了元扔,卻又出現(xiàn)了“假死”躯保,之前我們有驗證過,把notify方法摇展,改為notifyAll即可吻氧,有疑問的可以重新看3.1.11.2小節(jié)。

3.1.11.4 多生產(chǎn)與多消費-操作棧:解決wait條件改變與假死

仍然使用上一章節(jié)代碼咏连,修改我們的Run.java的代碼如下:

public class Run {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        Producer producer1 = new Producer(myStack);
        Producer producer2 = new Producer(myStack);
        Producer producer3 = new Producer(myStack);
        Producer producer4 = new Producer(myStack);
        Producer producer5 = new Producer(myStack);
        Consumer consumer1 = new Consumer(myStack);
        Consumer consumer2 = new Consumer(myStack);
        Consumer consumer3 = new Consumer(myStack);
        Consumer consumer4 = new Consumer(myStack);
        Consumer consumer5 = new Consumer(myStack);
        ProducerThread producerThread1 = new ProducerThread(producer1);
        ProducerThread producerThread2 = new ProducerThread(producer2);
        ProducerThread producerThread3 = new ProducerThread(producer3);
        ProducerThread producerThread4 = new ProducerThread(producer4);
        ProducerThread producerThread5 = new ProducerThread(producer5);
        producerThread1.start();
        producerThread2.start();
        producerThread3.start();
        producerThread4.start();
        producerThread5.start();
        ConsumerThread consumerThread1 = new ConsumerThread(consumer1);
        ConsumerThread consumerThread2 = new ConsumerThread(consumer2);
        ConsumerThread consumerThread3 = new ConsumerThread(consumer3);
        ConsumerThread consumerThread4 = new ConsumerThread(consumer4);
        ConsumerThread consumerThread5 = new ConsumerThread(consumer5);
        consumerThread1.start();
        consumerThread2.start();
        consumerThread3.start();
        consumerThread4.start();
        consumerThread5.start();
    }
}
do push threadName=Thread-0
push size = 1
do push threadName=Thread-0
do push threadName=Thread-2
do push threadName=Thread-4
do pop threadName=Thread-8
pop=0
do pop threadName=Thread-8
pop操作中的:Thread-8線程呈WAITING
push size = 1
do push threadName=Thread-4
do pop threadName=Thread-5
pop=0
do pop threadName=Thread-5
pop操作中的:Thread-5線程呈WAITING
push size = 1
do push threadName=Thread-2
pop=0
do pop threadName=Thread-5
pop操作中的:Thread-5線程呈WAITING
push size = 1
do pop threadName=Thread-9
pop=0
···

3.1.12 通過管道進行線程間通信:字節(jié)流

PipeStream是一種特殊的流,用于在不同線程間直接傳送數(shù)據(jù)鲁森。一個線程發(fā)送數(shù)據(jù)輸出管道祟滴,另一個線程從輸入管道中讀取數(shù)據(jù)。
1)PipedInputStreamPipedOutputStream
2)PipedReaderPipedWriter

class WriteData {
    void writeMethod(PipedOutputStream out) throws IOException {
        try {
            System.out.println("write");
            for (int i = 0; i < 10; i++) {
                String outData = "" + (i + 1);
                out.write(outData.getBytes());
                System.out.print(outData);
            }
            System.out.println();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            out.close();
        }
    }
}
public class WriteThread extends Thread {
    private WriteData write;
    private PipedOutputStream out;
    public WriteThread(WriteData write, PipedOutputStream out) {
        this.write = write;
        this.out = out;
    }
    @Override
    public void run() {
        try {
            write.writeMethod(out);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class ReadData {
    public void readMethod(PipedInputStream input) throws IOException {
        try {
            System.out.println("read");
            byte[] byteArray = new byte[20];
            int readLength = input.read(byteArray);
            while (readLength != -1) {
                String newData = new String(byteArray, 0, readLength);
                System.out.print(newData);
                readLength = input.read(byteArray);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            input.close();
        }
    }
}
public class ReadThread extends Thread {
    private ReadData read;
    private PipedInputStream in;
    public ReadThread(ReadData read, PipedInputStream in) {
        this.read = read;
        this.in = in;
    }
    @Override
    public void run() {
        try {
            read.readMethod(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class Run {
    public static void main(String[] args) throws IOException, InterruptedException {
        WriteData writeData = new WriteData();
        ReadData readData = new ReadData();
        PipedInputStream inputStream = new PipedInputStream();
        PipedOutputStream outputStream = new PipedOutputStream();
        outputStream.connect(inputStream);
        WriteThread writeThread = new WriteThread(writeData, outputStream);
        writeThread.start();
        Thread.sleep(1000);
        ReadThread readThread = new ReadThread(readData, inputStream);
        readThread.start();
    }
}
write
12345678910
read
12345678910

3.1.13通過管道進行線程間通信:字符流

public class WriteData {
    public void writeMethod(PipedWriter out) {
        try {
            System.out.print("write :");
            for (int i = 0; i < 11; i++) {
                String outData = "" + (i + 1);
                System.out.print(outData);
                out.write(outData);
            }
            System.out.println();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class WriteThread extends Thread {
    private WriteData write;
    private PipedWriter out;
    public WriteThread(WriteData write, PipedWriter out) {
        this.write = write;
        this.out = out;
    }
    @Override
    public void run() {
        write.writeMethod(out);
    }
}
public class ReadData {
    public void readMethod(PipedReader input) {
        try {
            System.out.print(" read :");
            char[] byteArray = new char[20];
            int readLength = input.read(byteArray);
            while (readLength != -1) {
                String newData = new String(byteArray, 0, readLength);
                System.out.print(newData);
                readLength = input.read(byteArray);
            }
            input.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ReadThread extends Thread {
    private ReadData read;
    private PipedReader in;
    public ReadThread(ReadData read, PipedReader in) {
        this.read = read;
        this.in = in;
    }
    @Override
    public void run() {
        read.readMethod(in);
    }
}
public class Run {
    public static void main(String[] args) {
        try {
            WriteData writeData = new WriteData();
            ReadData readData = new ReadData();
            PipedReader inputStream = new PipedReader();
            PipedWriter outputStream = new PipedWriter();
            outputStream.connect(inputStream);
            WriteThread writeThread = new WriteThread(writeData, outputStream);
            writeThread.start();
            Thread.sleep(100);
            ReadThread readThread = new ReadThread(readData, inputStream);
            readThread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
write :1234567891011
 read :1234567891011

3.2 join方法的使用

主線程創(chuàng)建并啟動子線程歌溉,如果子線程需要進行大量的耗時運算垄懂,主線程往往將早于子線程結(jié)束之前結(jié)束,這時痛垛,如果主線程想要等待子線程執(zhí)行完成之后再結(jié)束草慧,就要用到join()了。join()的作用是等待線程對象銷毀匙头。

public class MyThread extends Thread {
    @Override
    public void run() {
        int secondValue = (int) (Math.random() * 10000);
        System.out.println(secondValue);
    }
}
public class Test {
    public static void main(String[] args) {
        MyThread threadTest = new MyThread();
        threadTest.start();
        System.out.println("想在線程運行結(jié)束后打印···");
    }
}
想在線程運行結(jié)束后打印···
9607

修改Test.java代碼如下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread threadTest = new MyThread();
        threadTest.start();
        threadTest.join();
        System.out.println("想在線程運行結(jié)束后打印···");
    }
}
5277
想在線程運行結(jié)束后打印···

join()的作用是使所屬線程對象x正常執(zhí)行run()方法中的任務(wù)漫谷,而使當(dāng)前線程z進行無限期的阻塞,等待線程x銷毀后蹂析,再繼續(xù)執(zhí)行線程z后面的代碼舔示。在join過程中,如果當(dāng)前線程對象被中斷电抚,則當(dāng)前會出現(xiàn)InterruptedException惕稻。

join()具有使線程排隊運行的作用,有些類似同步的運行效果蝙叛。join與synchronized的區(qū)別是:join()在內(nèi)部使用wait()方法進行等待俺祠,而synchronized使用的是對象監(jiān)視器來實現(xiàn)同步。

3.2.1 join(long)與sleep(long)的區(qū)別

join(long)的功能在內(nèi)部是使用wait(long)來實現(xiàn)的,所以join(long)方法具有釋放鎖的特點蜘渣。join(long)源碼如下:

    public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

3.2.2 方法join()后面的代碼提前運行

public class ThreadB extends Thread {
    @Override
    synchronized public void run() {
        try {
            System.out.println("begin B threadName=" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("  end B threadName=" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private ThreadB threadB;
    public ThreadA(ThreadB threadB) {
        this.threadB = threadB;
    }
    @Override
    public void run() {
        synchronized (threadB) {
            try {
                System.out.println("begin A threadName = " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                Thread.sleep(5000);
                System.out.println("  end A threadName = " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Run {
    public static void main(String[] args) {
        try {
            ThreadB threadB = new ThreadB();
            ThreadA threadA = new ThreadA(threadB);
            threadA.start();
            threadB.start();
            threadB.join(2000);
            System.out.println("main end " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
begin A threadName = Thread-1 1590631573130
  end A threadName = Thread-1 1590631578130
main end 1590631578130
begin B threadName=Thread-0 1590631578130
  end B threadName=Thread-0 1590631583130

那么下面我們來分析一下問題出現(xiàn)的原因淌铐,修改Run.java代碼如下:

public class Run {
    public static void main(String[] args) {
        ThreadB threadB = new ThreadB();
        ThreadA threadA = new ThreadA(threadB);
        threadA.start();
        threadB.start();
        System.out.println("main end " + System.currentTimeMillis());
    }
}
main end 1590631921788
begin A threadName = Thread-1 1590631921788
  end A threadName = Thread-1 1590631926790
begin B threadName=Thread-0 1590631926790
  end B threadName=Thread-0 1590631931790

從執(zhí)行結(jié)果來看,“main end ···”每次都是第一個打印的宋梧。那么針對上次的執(zhí)行結(jié)果來看匣沼,join(2000)幾乎是每次都是先運行的,也就是最先搶到ThreadB的鎖捂龄,然后釋放释涛。基本的過程如下:
1)threadB.join(2000)先搶到ThreadB的鎖倦沧,然后釋放唇撬。
2)ThreadA搶到ThreadB的鎖,執(zhí)行synchronized代碼塊內(nèi)的代碼展融,進行sleep(5000)窖认,5s結(jié)束后打印end。
3)join(2000)和ThreadB再次爭搶鎖告希,時間已過2000ms扑浸,釋放鎖,然后打印“main end”燕偶。
4)ThreadB獲得鎖之后執(zhí)行run方法喝噪。

3.3 ThreadLocal的使用

ThreadLocal是除了加鎖這種同步方式之外,保證多線程訪問不會出現(xiàn)非線程安全問題指么,當(dāng)我們在創(chuàng)建一個變量后酝惧,每個線程對其進行訪問的時,訪問的都是線程自己的變量這樣就不會存在線程不安全問題伯诬。
Map里面存儲線程本地對象(key)和線程的變量副本(value)
但是晚唇,Thread內(nèi)部的Map是由ThreadLocal維護的,由ThreadLocal負責(zé)向map獲取和設(shè)置線程的變量值盗似。
下面我看幾個常用方法如下:

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        // 獲取當(dāng)前線程對象
        Thread t = Thread.currentThread();
        // 獲取當(dāng)前線程對象的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        // 判斷map是否存在
        if (map != null) {
            // 當(dāng)前ThreadLocal實例對象為key哩陕,獲取之前存儲的實體
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 判斷當(dāng)前實體是否存在
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 若map不存在則進行初始化值
        return setInitialValue();
    }
    
   /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        // 返回當(dāng)前線程的ThreadLocalMap 
        return t.threadLocals;
    }

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        // null
        T value = initialValue();
        // 獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        // 獲取當(dāng)前線程對象的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // map不為空時,初始化存儲對象實體value為null
            map.set(this, value);
        else
            // map為空時桥言,以當(dāng)前線程的threadLocals為key
            createMap(t, value);
        return value;
    }

    protected T initialValue() {
        return null;
    }

   /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

注意點:在線程run() 方法中不顯示的調(diào)用remove() 清理與線程相關(guān)的ThreadLocal 信息萌踱,線程復(fù)用會產(chǎn)生臟數(shù)據(jù)(線程池會重用Thread對象,如果先一個線程不調(diào)用set() 設(shè)置初始值号阿,那么與Thread綁定的類靜態(tài)屬性也會被重用)并鸵。

3.3.1 驗證線程變量的隔離性

public class ThreadLocalTools {
    public static ThreadLocal threadLocal = new ThreadLocal();
}
public class ThreadA extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                ThreadLocalTools.threadLocal.set("ThreadA " + i);
                System.out.println("ThreadA get Value = " + ThreadLocalTools.threadLocal.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadB extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                ThreadLocalTools.threadLocal.set("ThreadB " + i);
                System.out.println("ThreadB get Value = " + ThreadLocalTools.threadLocal.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        ThreadA a = new ThreadA();
        ThreadB b = new ThreadB();
        a.start();
        b.start();
        for (int i = 0; i < 100; i++) {
            ThreadLocalTools.threadLocal.set("main thread " + i);
            System.out.println("main thread getValue " + ThreadLocalTools.threadLocal.get());
            Thread.sleep(200);
        }
    }
}
···
main thread getValue main thread 97
ThreadA get Value = ThreadA 97
ThreadB get Value = ThreadB 97
main thread getValue main thread 98
ThreadA get Value = ThreadA 98
ThreadB get Value = ThreadB 98
main thread getValue main thread 99
ThreadA get Value = ThreadA 99
ThreadB get Value = ThreadB 99

3.4 InheritableThreadLocal的使用

使用InheritableThreadLocal可以再子線程中取得父線程繼承下來的值。

public class InheritableThreadLocalExt extends InheritableThreadLocal {
    @Override
    protected Object initialValue() {
        return System.currentTimeMillis();
    }
}
public class Tools {
    public static InheritableThreadLocalExt t1 = new InheritableThreadLocalExt();
}
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread獲取的值 = " + Tools.t1.get());
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(" main線程取值    = " + Tools.t1.get());
        Thread.sleep(1000);
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
 main線程取值    = 1590647232214
MyThread獲取的值 = 1590647232214

參考文獻

《Java多線程編程核心技術(shù)》高紅巖

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扔涧,一起剝皮案震驚了整個濱河市园担,隨后出現(xiàn)的幾起案子届谈,更是在濱河造成了極大的恐慌,老刑警劉巖弯汰,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艰山,死亡現(xiàn)場離奇詭異,居然都是意外死亡咏闪,警方通過查閱死者的電腦和手機曙搬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸽嫂,“玉大人纵装,你說我怎么就攤上這事【菽常” “怎么了橡娄?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長癣籽。 經(jīng)常有香客問我挽唉,道長,這世上最難降的妖魔是什么筷狼? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任瓶籽,我火速辦了婚禮,結(jié)果婚禮上埂材,老公的妹妹穿的比我還像新娘棘劣。我一直安慰自己,他們只是感情好楞遏,可當(dāng)我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著首昔,像睡著了一般寡喝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勒奇,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天预鬓,我揣著相機與錄音,去河邊找鬼赊颠。 笑死格二,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的竣蹦。 我是一名探鬼主播顶猜,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼痘括!你這毒婦竟也來了长窄?” 一聲冷哼從身側(cè)響起滔吠,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挠日,沒想到半個月后疮绷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡嚣潜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年冬骚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懂算。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡只冻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出犯犁,到底是詐尸還是另有隱情属愤,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布酸役,位于F島的核電站住诸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涣澡。R本人自食惡果不足惜贱呐,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望入桂。 院中可真熱鬧奄薇,春花似錦、人聲如沸抗愁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜘腌。三九已至沫屡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間撮珠,已是汗流浹背沮脖。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芯急,地道東北人勺届。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像娶耍,于是被迫代替她去往敵國和親免姿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,969評論 2 355