Java多線程編程(三)線程間通信


  線程是操作系統中獨立的個體,但這些個體如果不經過特殊的處理就不能成為一個整體。線程間的通信就是成為整體的必用方案之一,可以說,使線程間進行通信后,系統之間的交互性會更強大,在大大提高CPU利用率的同時還會使程序員對各線程任務在處理的過程中進行有效地把控與監督。  

  一、等待/通知機制

  1.不使用等待/通知機制實現線程間通信

  示例:線程A向數組中增加元素,線程B不斷查詢數組中元素個數,在元素個數等於1時發生異常並停止。雖然兩個線程實現了通信,但有一個弊端就是,線程B不停地通過while語句輪詢機制來檢測某一個條件,這樣會浪費CPU資源。如果輪詢的時間間隔很小,更浪費CPU資源;如果輪詢的時間間隔很大,有可能會取不到想要得到的數據。所以就需要有一種機制來減少CPU資源的浪費,而且還可以實現在多個線程間通信,它就是“wait/notify”機制。

package mylist;

import java.util.ArrayList;
import java.util.List;

public class MyList {

    private List<Integer> list = new ArrayList<Integer>();

    public void add() {
        list.add(1);
    }

    public int size() {
        return list.size();
    }

}
package extthread;

import mylist.MyList;

public class ThreadA extends Thread {

    private MyList list;

    public ThreadA(MyList list) {
        super();
        this.list = list;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                list.add();
                System.out.println("添加了" + (i + 1) + "個元素!");
                System.out.println(list.size());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package extthread;

import mylist.MyList;

public class ThreadB extends Thread {

    private MyList list;

    public ThreadB(MyList list) {
        super();
        this.list = list;
    }

    @Override
    public void run() {
        System.out.println("B");
        try {
            while (true) {
                if (list.size() == 1) {
                    System.out.println("==1了,線程b要退出了!");
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package test;

import mylist.MyList;
import extthread.ThreadA;
import extthread.ThreadB;

public class Test {

    public static void main(String[] args) {
        MyList service = new MyList();

        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();

    }

}
添加了1個元素!
1
B
==1了,線程b要退出了!
java.lang.InterruptedException
	at extthread.ThreadB.run(ThreadB.java:21)
添加了2個元素!
2
添加了3個元素!
3
...

  2.什么是等待/通知機制

  等待/通知機制用廚師和服務員的交互來解釋:

  (1)廚師做完一道菜的時間不確定,所以廚師將菜品放到“傳菜台”上的時間也不確定。

  (2)服務員取到菜的時間取決於廚師,所以服務員就有“等待”的狀態。

  (3)廚師將菜放在“傳菜苔”上,服務員才能取到菜,這就相當於一種“通知”,這時服務員才可以拿到菜並交給就餐者。

  前面介紹的多個線程之間的通信,原因是多個線程共同訪問同一個變量,但那種通信機制不是等待/通知機制,兩個線程完全是主動式地讀取一個共享變量,在花費讀取時間的基礎上,讀到的值是不是想要的,並不能完全確定。等待/通知機制可以解決這些問題。

  3.等待/通知機制的實現

  方法wait()的作用:使當前執行代碼的線程進行等待,wait()方法是Object類的方法,該方法用來將當前線程置入“預執行隊列”中,並且在wait()所在的代碼行處停止執行,直到接到通知或被中斷為止。在調用wait()之前,線程必須獲得該對象的對象級別鎖,即只能在同步方法或同步塊中調用wait()方法。在執行wait()方法后,當前線程釋放鎖。在從wait()返回前,線程與其他線程競爭重新獲得鎖。如果調用wait()方法時沒有持有適當的鎖,則拋出IllegalMonitorStateException異常,它是RuntimeException的一個子類,因此,不需要try-catch語句進行捕捉異常。

  方法notify()的作用:也要在同步方法或同步塊中調用,即在調用前,線程也必須獲得該對象的對象級別鎖。如調用notify()時沒有持有適當的鎖,也會拋出IllegalMonitorStateException。該方法用來通知那些可能等待該對象的對象鎖的其他線程,如果有多個線程等待,則由線程規划器隨機挑選出其中一個呈wait狀態的線程,對其發出通知notify,並使它等待獲取該對象的對象鎖。需要說明的是,在執行notify()方法后,當前線程不會馬上釋放該對象鎖,呈wait狀態的線程也並不能馬上獲取該對象鎖,到等到執行notify()方法的線程將程序執行完,也就是退出synchronized代碼塊后,當前線程才會釋放鎖,而呈wait狀態所在的線程才可以獲取該對象鎖。當第一個獲得了該對象鎖的wait線程運行完畢以后,它會釋放掉該對象鎖,此時如果該對象沒有再次使用notify語句,則即便該對象已經空閑,其他wait狀態等待的線程由於沒有得到該對象的通知,還會繼續阻塞在wait狀態,直到這個對象發出一個notify或notifyAll。

  總結:wait使線程停止運行,而notify使停止的線程繼續運行。

  示例1:沒有對newString加對象級別鎖,沒有“對象監視器”,也就是沒有同步加鎖,所以出現異常。

package test;

public class Test1 {
    public static void main(String[] args) {
        try {
            String newString = new String("");
            newString.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Unknown Source)
	at test.Test1.main(Test1.java:7)

   示例2:獲得對象鎖后,不再拋出異常,從輸出結果可以看出,wait()方法后面的代碼都不執行了,使用notify()方法可以使等待wait狀態的線程繼續運行。

package test;

public class Test2 {

    public static void main(String[] args) {
        try {
            String lock = new String();
            System.out.println("syn上一行");
            synchronized (lock) {
                System.out.println("syn第一行");
                lock.wait();
                System.out.println("wait下面一行");
            }
            System.out.println("syn代碼塊下面的一行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
syn上一行
syn第一行

   示例3:線程A運行時呈wait狀態,等待3秒后,線程B通過調用notify()方法將線程A喚醒。

package extthread;

public class MyThread1 extends Thread {
    private Object lock;

    public MyThread1(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                System.out.println("開始     wait time=" + System.currentTimeMillis());
                lock.wait();
                System.out.println("結束      wait time=" + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package extthread;

public class MyThread2 extends Thread {
    private Object lock;

    public MyThread2(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("開始notify time=" + System.currentTimeMillis());
            lock.notify();
            System.out.println("結束notify time=" + System.currentTimeMillis());
        }
    }
}
package test;

import extthread.MyThread1;
import extthread.MyThread2;

public class Test {
	public static void main(String[] args) {
		try {
			Object lock = new Object();

			MyThread1 t1 = new MyThread1(lock);
			t1.start();

			Thread.sleep(3000);

			MyThread2 t2 = new MyThread2(lock);
			t2.start();

		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

   示例4:從輸出可以看出,wait end在最后輸出,這說明notify()方法執行后並不立即釋放鎖。

package extlist;

import java.util.ArrayList;
import java.util.List;

public class MyList {

    private static List<String> list = new ArrayList<String>();

    public static void add() {
        list.add("anyString");
    }

    public static int size() {
        return list.size();
    }

}
package extthread;

import extlist.MyList;

public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        super();
        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();
        }
    }

}
package extthread;

import extlist.MyList;

public class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                for (int i = 0; i < 10; i++) {
                    MyList.add();
                    if (MyList.size() == 5) {
                        lock.notify();
                        System.out.println("已發出通知!");
                    }
                    System.out.println("添加了" + (i + 1) + "個元素!");
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package test;

import extthread.ThreadA;
import extthread.ThreadB;

public class Run {

    public static void main(String[] args) {

        try {
            Object lock = new Object();

            ThreadA a = new ThreadA(lock);
            a.start();

            Thread.sleep(50);

            ThreadB b = new ThreadB(lock);
            b.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}
wait begin 1525504132043
添加了1個元素!
添加了2個元素!
添加了3個元素!
添加了4個元素!
已發出通知!
添加了5個元素!
添加了6個元素!
添加了7個元素!
添加了8個元素!
添加了9個元素!
添加了10個元素!
wait end  1525504142153

   總結:

  關鍵字synchronized可以將任何一個Object對象作為同步對象來看待,而Java為每個Object都實現了wait()和notify()方法,它們必須用在被synchronized同步的Object的臨界區內。通過調用wait()方法可以使處於臨界區內的線程進入等待狀態,同時釋放被同步對象的鎖。而notify操作可以喚醒一個因調用了wait操作而處於阻塞狀態中的線程,使其進去就緒狀態。被重新喚醒的線程會視圖重新獲得臨界區的控制權,也就是鎖,並繼續執行臨界區內的代碼。如果發出notify操作時沒有處於阻塞狀態中的線程,那么該命令會被忽略。

  wait()方法可以使調用該方法的線程釋放共享資源的鎖,然后從運行狀態退出,進入等待隊列,直到被再次喚醒。

  notify()方法可以隨機喚醒等待隊列中等待同一共享資源的“一個”線程,並使該線程退出等待隊列,進入可運行狀態,也就是notify()方法僅通知“一個”線程。

  notifyAll()方法可以使所有正在等待隊列中等待統一共享資源的“全部”線程從等待狀態退出,進入可運行狀態。此時,優先級最高的那個線程最先執行,但也有可能是隨機執行,因為這要取決於JVM虛擬機的實現。

  4.方法wait()鎖釋放與notify()鎖不釋放

  當wait()方法被執行后,鎖被自動釋放,但執行完notify()方法,鎖卻不自動釋放。

  示例1:兩個線程都對同一共享資源對象鎖,其中一個線程執行完wait()后立刻釋放鎖,然后另一個線程得以執行。

package service;

public class Service {

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

}
package extthread;

import service.Service;

public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }

}
package extthread;

import service.Service;

public class ThreadB extends Thread {
    
    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }

}
package test;

import extthread.ThreadA;
import extthread.ThreadB;

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();

    }

}
begin wait()
begin wait()

   示例2:將wait()方法修改成sleep方法后,就成了同步的效果了。

package service;

public class Service {

    public void testMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin wait()");
                Thread.sleep(40000);
                System.out.println("  end wait()");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
begin wait()
  end wait()
begin wait()
  end wait()

   示例3:從輸出結果可以看出,notify都是成對打印的,這說明了必須執行完notify()方法所在的同步synchronized代碼塊后才釋放鎖。

package service;

public class Service {

    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 (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void synNotifyMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin notify() ThreadName="+ Thread.currentThread().getName() 
                                 + " time="+ System.currentTimeMillis());
                lock.notify();
                Thread.sleep(5000);
                System.out.println("  end notify() ThreadName="+ Thread.currentThread().getName() 
                        + " time="+ System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package extthread;

import service.Service;

public class ThreadA extends Thread {
    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }

}
package extthread;

import service.Service;

public class NotifyThread extends Thread {
    private Object lock;

    public NotifyThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }

}
package extthread;

import service.Service;

public class synNotifyMethodThread extends Thread {
    private Object lock;

    public synNotifyMethodThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }

}
package test;

import extthread.NotifyThread;
import extthread.ThreadA;
import extthread.synNotifyMethodThread;

public class Test {

    public static void main(String[] args) throws InterruptedException {

        Object lock = new Object();

        ThreadA a = new ThreadA(lock);
        a.start();

        NotifyThread notifyThread = new NotifyThread(lock);
        notifyThread.start();

        synNotifyMethodThread c = new synNotifyMethodThread(lock);
        c.start();

    }

}
begin wait()   ThreadName=Thread-0 time=1525508164635
begin notify() ThreadName=Thread-2 time=1525508164635
  end notify() ThreadName=Thread-2 time=1525508169635
begin notify() ThreadName=Thread-1 time=1525508169635
  end notify() ThreadName=Thread-1 time=1525508174635
  end wait()   ThreadName=Thread-0 time=1525508174635

  5.當interrupt方法遇到wait方法

  示例:當線程呈wait狀態時,調用線程對象的interrupt()方法會出現InterruptdException異常。

package service;

public class Service {

    public void testMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin wait()");
                lock.wait();
                System.out.println("  end wait()");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("出現異常了,因為呈wait狀態的線程被interrupt了!");
        }
    }

}
package extthread;

import service.Service;

public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }

}
package test;

import extthread.ThreadA;

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 (InterruptedException e) {
            e.printStackTrace();
        }

    }

}
begin wait()
java.lang.InterruptedException
出現異常了,因為呈wait狀態的線程被interrupt了!
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Unknown Source)
	at service.Service.testMethod(Service.java:9)
	at extthread.ThreadA.run(ThreadA.java:17)

   總結:

  (1)wait()方法執行完同步代碼塊就會釋放對象的鎖。

  (2)在執行同步代碼塊的過程中,遇到異常導致呈wait狀態的線程終止,鎖也會被釋放。

  (3)在執行同步代碼塊的過程中,執行了鎖所屬對象的wait(0方法,這個線程會釋放對象鎖,而此線程對象會進入線程等待池中,等待被喚醒。

  6.只通知一個線程

  示例1:調用notify()方法一次只隨機通知一個線程進行喚醒。線程A、B、C都執行wait()方法,通知線程只執行一次notify()方法。從輸出結果也可以看出,隨機喚醒三個線程的任意一個。

package extthread;

import service.Service;

public class NotifyThread extends Thread {
    private Object lock;

    public NotifyThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            lock.notify();
        }
    }

package extthread;

import service.Service;

public class ThreadA extends Thread {
    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }

}
package service;

public class Service {

    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 (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package test;

import extthread.NotifyThread;
import extthread.ThreadA;
import extthread.ThreadB;
import extthread.ThreadC;

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-1
begin wait() ThreadName=Thread-2
begin wait() ThreadName=Thread-0
  end wait() ThreadName=Thread-1

   示例2:修改示例1中的通知線程,多次調用notify()方法,會隨機將等待wait狀態的線程進行喚醒。

package extthread;

import service.Service;

public class NotifyThread extends Thread {
    private Object lock;

    public NotifyThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
        }
    }

}
begin wait() ThreadName=Thread-0
begin wait() ThreadName=Thread-2
begin wait() ThreadName=Thread-1
  end wait() ThreadName=Thread-0
  end wait() ThreadName=Thread-1
  end wait() ThreadName=Thread-2

  7.喚醒所有線程

  示例:修改6中的實例,將notify()方法修改成notifyAll()方法並只執行一次,就可以喚醒全部處於wait狀態的線程。

package extthread;

import service.Service;

public class NotifyThread extends Thread {
    private Object lock;

    public NotifyThread(Object lock) {
        super();
        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

  8.方法wait(long)的使用

  帶一個參數的wait(long)方法的功能是等待某一時間內是否有線程對鎖進行喚醒,如果超過這個時間則自動喚醒。

  示例1:線程等待了5秒后,自動被喚醒,即退出wait狀態。

package myrunnable;

public class MyRunnable {
    static private Object lock = new Object();
    
    static private Runnable runnable1 = new Runnable() {
        @Override
        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 (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    static private Runnable runnable2 = new Runnable() {
        @Override
        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 t1 = new Thread(runnable1);
        t1.start();
        //Thread.sleep(3000);
        //Thread t2 = new Thread(runnable2);
        //t2.start();
    }

}
wait   begin timer=1525511220380
wait     end timer=1525511225381

   示例2:也可以在未喚醒之前由其他線程喚醒。從結果可以看出,在3秒后由其他線程喚醒。

package myrunnable;

public class MyRunnable {
    static private Object lock = new Object();
    
    static private Runnable runnable1 = new Runnable() {
        @Override
        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 (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    static private Runnable runnable2 = new Runnable() {
        @Override
        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 t1 = new Thread(runnable1);
        t1.start();
        Thread.sleep(3000);
        Thread t2 = new Thread(runnable2);
        t2.start();
    }

}
wait   begin timer=1525511382723
notify begin timer=1525511385724
notify   end timer=1525511385724
wait     end timer=1525511385724

  9.通知過早

  如果通知過早,就會打亂程序正常的運行邏輯。

  示例1:正常的執行順序是先wait,然后等待100秒,然后通知,這樣是正確的結果。

package test;

public class MyRun {

    private String lock = new String("");
    private boolean isFirstRunB = false;

    private Runnable runnableA = new Runnable() {
        @Override
        public void run() {
            try {
                synchronized (lock) {
                    while (isFirstRunB == false) {
                        System.out.println("begin wait");
                        lock.wait();
                        System.out.println("end wait");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    private Runnable runnableB = new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("begin notify");
                lock.notify();
                System.out.println("end notify");
                isFirstRunB = true;

            }
        }
    };

    public static void main(String[] args) throws InterruptedException {

        MyRun run = new MyRun();

        Thread a = new Thread(run.runnableA);
        a.start();

        Thread.sleep(100);

        Thread b = new Thread(run.runnableB);
        b.start();

    }

}
begin wait
begin notify
end notify
end wait

   示例2:將線程B先執行,然后sleep100毫秒,然后執行線程A,會導致先通知的情況下,wait方法就沒有必要執行了。

begin notify
end notify

  示例3:修改程序,將邏輯去掉,先A后B,可以得到正確的結果。

package test;

public class MyRun {

    private String lock = new String("");
    private boolean isFirstRunB = false;

    private Runnable runnableA = new Runnable() {
        @Override
        public void run() {
            try {
                synchronized (lock) {
                        System.out.println("begin wait");
                        lock.wait();
                        System.out.println("end wait");
                    }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    private Runnable runnableB = new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("begin notify");
                lock.notify();
                System.out.println("end notify");
                isFirstRunB = true;

            }
        }
    };

    public static void main(String[] args) throws InterruptedException {

        MyRun run = new MyRun();

        Thread a = new Thread(run.runnableA);
        a.start();

        Thread.sleep(100);

        Thread b = new Thread(run.runnableB);
        b.start();

    }

}
begin wait
begin notify
end notify
end wait

   示例4:先B后A,方法wait永遠不會被通知。

begin notify
end notify
begin wait

  10.等待wait的條件發生變化

  wait等待的條件發生了變化,也容易造成程序邏輯的混亂。

  示例1:出現異常的原因是,有兩個實現刪除remove()操作的線程,即兩個減法線程,在main方法中,sleep(1000)之前,兩個線程都執行了wait()方法,呈等待狀態。當加法線程在1秒后被運行時,通知了所有呈wait狀態的兩個減法線程,然后兩個減法線程就去爭搶執行減法操作,那么第一個減法線程可以正確地刪除list中索引為0的數據,但第二個減法線程則出現索引溢出的異常,因為list中僅僅添加了一個數據,也只能刪除一個數據,所以沒有第二個數據可供刪除。

package entity;

import java.util.ArrayList;
import java.util.List;

public class ValueObject {

    public static List<String> list = new ArrayList<String>();

}
package entity;

//加法
public class Add {

    private String lock;

    public Add(String lock) {
        super();
        this.lock = lock;
    }

    public void add() {
        synchronized (lock) {
            ValueObject.list.add("anyString");
            lock.notifyAll();
        }
    }

}
package entity;

//減法
public class Subtract {

    private String lock;

    public Subtract(String lock) {
        super();
        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());
                }
                ValueObject.list.remove(0);
                System.out.println("list size=" + ValueObject.list.size());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package extthread;

import entity.Add;

public class ThreadAdd extends Thread {

    private Add p;

    public ThreadAdd(Add p) {
        super();
        this.p = p;
    }

    @Override
    public void run() {
        p.add();
    }

}
package extthread;

import entity.Subtract;

public class ThreadSubtract extends Thread {

    private Subtract r;

    public ThreadSubtract(Subtract r) {
        super();
        this.r = r;
    }

    @Override
    public void run() {
        r.subtract();
    }

}
package test;

import entity.Add;
import entity.Subtract;
import extthread.ThreadAdd;
import extthread.ThreadSubtract;

public class Run {

    public static void main(String[] args) throws InterruptedException {

        String lock = new String("");

        Add add = new Add(lock);
        Subtract subtract = new Subtract(lock);

        ThreadSubtract subtract1Thread = new ThreadSubtract(subtract);
        subtract1Thread.setName("subtract1Thread");
        subtract1Thread.start();

        ThreadSubtract subtract2Thread = new ThreadSubtract(subtract);
        subtract2Thread.setName("subtract2Thread");
        subtract2Thread.start();

        Thread.sleep(1000);

        ThreadAdd addThread = new ThreadAdd(add);
        addThread.setName("addThread");
        addThread.start();

    }

}
wait begin ThreadName=subtract1Thread
wait begin ThreadName=subtract2Thread
wait   end ThreadName=subtract2Thread
list size=0
wait   end ThreadName=subtract1Thread
Exception in thread "subtract1Thread" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(Unknown Source)
	at java.util.ArrayList.remove(Unknown Source)
	at entity.Subtract.subtract(Subtract.java:21)
	at extthread.ThreadSubtract.run(ThreadSubtract.java:16)

  示例2:將減法線程中的if語句改成while語句,這樣就可以解決異常的情況。

package entity;

//減法
public class Subtract {

    private String lock;

    public Subtract(String lock) {
        super();
        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());
                }
                ValueObject.list.remove(0);
                System.out.println("list size=" + ValueObject.list.size());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
wait begin ThreadName=subtract2Thread
wait begin ThreadName=subtract1Thread
wait   end ThreadName=subtract1Thread
list size=0
wait   end ThreadName=subtract2Thread
wait begin ThreadName=subtract2Thread

  11.生產者/消費者模式實現

  (1)一生產與一消費:操作值

  示例:set和get交替運行,兩個線程中的if語句決定了第一個執行的一定是生產者的set過程,然后然后兩個線程互相交替set和get。

package entity;

public class ValueObject {

    public static String value = "";

}
package entity;

//生產者
public class P {

    private String lock;

    public P(String lock) {
        super();
        this.lock = lock;
    }

    public void setValue() {
        try {
            synchronized (lock) {
                if (!ValueObject.value.equals("")) {
                    lock.wait();
                }
                String value = System.currentTimeMillis() + "_"+ System.nanoTime();
                System.out.println("set的值是" + value);
                ValueObject.value = value;
                lock.notify();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package entity;

//消費者
public class C {

    private String lock;

    public C(String lock) {
        super();
        this.lock = lock;
    }

    public void getValue() {
        try {
            synchronized (lock) {
                if (ValueObject.value.equals("")) {
                    lock.wait();
                }
                System.out.println("get的值是" + ValueObject.value);
                ValueObject.value = "";
                lock.notify();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package extthread;

import entity.C;

public class ThreadC extends Thread {

    private C r;

    public ThreadC(C r) {
        super();
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            r.getValue();
        }
    }

}
package extthread;

import entity.P;

public class ThreadP extends Thread {

    private P p;

    public ThreadP(P p) {
        super();
        this.p = p;
    }

    @Override
    public void run() {
        while (true) {
            p.setValue();
        }
    }

}
package test;

import entity.P;
import entity.C;
import extthread.ThreadP;
import extthread.ThreadC;

public class Run {

    public static void main(String[] args) {

        String lock = new String("");
        P p = new P(lock);
        C r = new C(lock);

        ThreadP pThread = new ThreadP(p);
        ThreadC rThread = new ThreadC(r);

        pThread.start();
        rThread.start();
    }

}
set的值是1525522701519_86515436524665
get的值是1525522701519_86515436524665
set的值是1525522701519_86515436548871
get的值是1525522701519_86515436548871
set的值是1525522701519_86515436566512
get的值是1525522701519_86515436566512
set的值是1525522701519_86515436584153
get的值是1525522701519_86515436584153
set的值是1525522701519_86515436601383
get的值是1525522701519_86515436601383
set的值是1525522701519_86515436619435
get的值是1525522701519_86515436619435
set的值是1525522701519_86515436643640
get的值是1525522701519_86515436643640
set的值是1525522701519_86515436661691
get的值是1525522701519_86515436661691
set的值是1525522701519_86515436679742
get的值是1525522701519_86515436679742
...

  (2)多生產與多消費:操作值-假死

  “假死”的現象其實就是線程進入wait等待狀態。如果全部線程都進入wait狀態,那程序就不再執行任何業務功能了,整個項目呈停止狀態。

  示例:兩個生產者線程和兩個消費者線程,下面按每一行詳細分析執行過程。假死的主要原因就是有可能連續喚醒同類。

package entity;

public class ValueObject {

    public static String value = "";

}
package entity;

//生產者
public class P {

    private String lock;

    public P(String lock) {
        super();
        this.lock = lock;
    }

    public void setValue() {
        try {
            synchronized (lock) {
                while (!ValueObject.value.equals("")) {
                    System.out.println("生產者 "+ Thread.currentThread().getName() + " WAITING了★");
                    lock.wait();
                }
                System.out.println("生產者 " + Thread.currentThread().getName()+ " RUNNABLE了");
                String value = System.currentTimeMillis() + "_"+ System.nanoTime();
                ValueObject.value = value;
                lock.notify();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package entity;

//消費者
public class C {

    private String lock;

    public C(String lock) {
        super();
        this.lock = lock;
    }

    public void getValue() {
        try {
            synchronized (lock) {
                while (ValueObject.value.equals("")) {
                    System.out.println("消費者 "+ Thread.currentThread().getName() + " WAITING了☆");
                    lock.wait();
                }
                System.out.println("消費者 " + Thread.currentThread().getName()+ " RUNNABLE了");
                ValueObject.value = "";
                lock.notify();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package extthread;

import entity.P;

public class ThreadP extends Thread {

    private P p;

    public ThreadP(P p) {
        super();
        this.p = p;
    }

    @Override
    public void run() {
        while (true) {
            p.setValue();
        }
    }

}
package extthread;

import entity.C;

public class ThreadC extends Thread {

    private C r;

    public ThreadC(C r) {
        super();
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            r.getValue();
        }
    }

}
package test;

import entity.P;
import entity.C;
import extthread.ThreadP;
import extthread.ThreadC;

public class Run {

    public static void main(String[] args) throws InterruptedException {

        String lock = new String("");
        P p = new P(lock);
        C r = new C(lock);

        ThreadP[] pThread = new ThreadP[2];
        ThreadC[] rThread = new ThreadC[2];

        for (int i = 0; i < 2; i++) {
            pThread[i] = new ThreadP(p);
            pThread[i].setName("生產者" + (i + 1));

            rThread[i] = new ThreadC(r);
            rThread[i].setName("消費者" + (i + 1));

            pThread[i].start();
            rThread[i].start();
        }

        Thread.sleep(5000);
        Thread[] threadArray = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
        Thread.currentThread().getThreadGroup().enumerate(threadArray);

        for (int i = 0; i < threadArray.length; i++) {
            System.out.println(threadArray[i].getName() + " "+ threadArray[i].getState());
        }
    }

}
生產者 生產者1 RUNNABLE了
生產者 生產者1 WAITING了★
生產者 生產者2 WAITING了★
消費者 消費者2 RUNNABLE了
消費者 消費者2 WAITING了☆
消費者 消費者1 WAITING了☆
生產者 生產者1 RUNNABLE了
生產者 生產者1 WAITING了★
生產者 生產者2 WAITING了★
main RUNNABLE
生產者1 WAITING
消費者1 WAITING
生產者2 WAITING
消費者2 WAITING

  輸出結果分析(結果不唯一,有可能出現其他情況,但最后的結果都是四個線程都進入wait狀態。):

  ①生產者1進行生產,while判斷語句不通過,執行生產語句,執行賦值操作后,發出通知,並釋放鎖,准備進入下一次的while循環。

  ②生產者1進行了下一次whlie循環,whlie判斷語句通過,進入wait等待狀態。

  ③生產者2被啟動,生產者2whlie判斷語句通過,也進入wait等待狀態。

  ④消費者2被啟動,判斷語句不通過,進入消費狀態,並發出通知喚醒第七行中的生產者1,運行結束后釋放鎖,等待消費者2進行下一次循環。

  ⑤消費者2進行下一次while循環,判斷語句通過,進入等待狀態。

  ⑥消費者1被啟動,判斷語句通過,也進入等待狀態。

  ⑦生產者1被④中的通知喚醒,判斷語句不通過,執行生產語句,然后發出通知,准備進入下一次的whlie循環。

  ⑧生產者1進入下一次whlie循環,判斷語句通過,進入wait等待狀態。

  ⑨由於⑦發出了通知,喚醒了生產者2,生產者2判斷語句通過,也進入wait等待狀態。

  (3)多生產與多消費:操作值

  示例:(2)中的代碼,將生產者與消費者執行方法中的notify()修改成notifyAll()方法, 這樣就可以解決問題了,程序就可以一直運行下去。原理是,不只是通知同類線程,也包括異類線程,這樣就不會出現假死的狀態了,程序就會一直運行下去。

package entity;

//生產者
public class P {

    private String lock;

    public P(String lock) {
        super();
        this.lock = lock;
    }

    public void setValue() {
        try {
            synchronized (lock) {
                while (!ValueObject.value.equals("")) {
                    System.out.println("生產者 "+ Thread.currentThread().getName() + " WAITING了★");
                    lock.wait();
                }
                System.out.println("生產者 " + Thread.currentThread().getName()+ " RUNNABLE了");
                String value = System.currentTimeMillis() + "_"+ System.nanoTime();
                ValueObject.value = value;
                lock.notifyAll();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package entity;

//消費者
public class C {

    private String lock;

    public C(String lock) {
        super();
        this.lock = lock;
    }

    public void getValue() {
        try {
            synchronized (lock) {
                while (ValueObject.value.equals("")) {
                    System.out.println("消費者 "+ Thread.currentThread().getName() + " WAITING了☆");
                    lock.wait();
                }
                System.out.println("消費者 " + Thread.currentThread().getName()+ " RUNNABLE了");
                ValueObject.value = "";
                lock.notifyAll();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

  (4)一生產與一消費:操作棧

  示例:生產者向堆棧List對象中放入數據,消費者從List堆棧中取出數據。List最大容量是1,且只有一個生產者和一個消費者。程序運行結果是size()不會大於1,值在0和1之間進行交替,也就是生產和消費這兩個過程在交替執行。

package entity;

import java.util.ArrayList;
import java.util.List;

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

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

    synchronized public String pop() {
        String returnValue = "";
        try {
            if (list.size() == 0) {
                System.out.println("pop操作中的:"+ Thread.currentThread().getName() + " 線程呈wait狀態");
                this.wait();
            }
            returnValue = "" + list.get(0);
            list.remove(0);
            this.notify();
            System.out.println("pop=" + list.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return returnValue;
    }
}
package service;

import entity.MyStack;

public class C {

    private MyStack myStack;

    public C(MyStack myStack) {
        super();
        this.myStack = myStack;
    }

    public void popService() {
        System.out.println("pop=" + myStack.pop());
    }
}
package service;

import entity.MyStack;

public class P {

    private MyStack myStack;

    public P(MyStack myStack) {
        super();
        this.myStack = myStack;
    }

    public void pushService() {
        myStack.push();
    }
}
package extthread;

import service.C;

public class C_Thread extends Thread {

    private C r;

    public C_Thread(C r) {
        super();
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            r.popService();
        }
    }

}
package extthread;

import service.P;

public class P_Thread extends Thread {

    private P p;

    public P_Thread(P p) {
        super();
        this.p = p;
    }

    @Override
    public void run() {
        while (true) {
            p.pushService();
        }
    }

}
package test.run;

import service.P;
import service.C;
import entity.MyStack;
import extthread.P_Thread;
import extthread.C_Thread;

public class Run {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();

        P p = new P(myStack);
        C r = new C(myStack);

        P_Thread pThread = new P_Thread(p);
        C_Thread rThread = new C_Thread(r);
        pThread.start();
        rThread.start();
    }

}
push=1
pop=0
pop=anyString=0.3254503931098508
push=1
pop=0
pop=anyString=0.6957780954007395
push=1
pop=0
pop=anyString=0.5752314740754261
push=1
pop=0
pop=anyString=0.6358907873485814
push=1
pop=0
pop=anyString=0.5491334774745474
push=1
pop=0
pop=anyString=0.7318824495167014
push=1
pop=0
pop=anyString=0.5984936669842421
push=1
pop=0
pop=anyString=0.47994410023033296
push=1
pop=0
pop=anyString=0.15752443606208588
push=1
pop=0
pop=anyString=0.02593849380725266
push=1
pop=0
pop=anyString=0.6580876878111466
push=1
pop=0
pop=anyString=0.04516506982998558
push=1
pop=0
pop=anyString=0.718448881542528
push=1
pop=0
pop=anyString=0.37108334401362963
push=1
pop=0
pop=anyString=0.6501388479925284
push=1
pop=0
pop=anyString=0.47586404781477243
push=1
pop=0
pop=anyString=0.9295344765233564
push=1
pop=0
pop=anyString=0.5197464329430267
push=1
pop=0
pop=anyString=0.8813580448635558
push=1
pop=0
pop=anyString=0.2578693811523306
push=1
pop=0
pop=anyString=0.37409514156698975
push=1
pop=0
pop=anyString=0.3031788471366039
push=1
pop=0
pop=anyString=0.8020652463477314
push=1
pop=0
...

  (5)一生產與多消費:操作棧:解決wait條件改變與假死

  示例1:一個生產者向堆棧List對象中放入數據,而多個消費者從List堆棧中取出數據,List最大容量還是1。修改(4)中的Run類,一個生產者和五個消費者的情況。MyStack.java類中使用if語句作為條件判斷。因為條件發生改變時並沒有得到及時的相應,所以多個呈wait狀態的線程被喚醒, 繼而執行list.remove(0)代碼而出現異常。解決這個的辦法是,將if改成whlie即可。

package test.run;

import service.P;
import service.C;
import entity.MyStack;
import extthread.P_Thread;
import extthread.C_Thread;

public class Run {
    public static void main(String[] args) {
        MyStack myStack = new MyStack();

        P p = new P(myStack);
        C r1 = new C(myStack);
        C r2 = new C(myStack);
        C r3 = new C(myStack);
        C r4 = new C(myStack);
        C r5 = new C(myStack);

        P_Thread pThread = new P_Thread(p);
        C_Thread rThread1 = new C_Thread(r1);
        C_Thread rThread2 = new C_Thread(r2);
        C_Thread rThread3 = new C_Thread(r3);
        C_Thread rThread4 = new C_Thread(r4);
        C_Thread rThread5 = new C_Thread(r5);
        pThread.start();
        rThread1.start();
        rThread2.start();
        rThread3.start();
        rThread4.start();
        rThread5.start();
    }

}
push=1
pop=anyString=0.1156301194548911
pop=0
pop=anyString=0.1156301194548911
pop操作中的:Thread-1 線程呈wait狀態
pop操作中的:Thread-4 線程呈wait狀態
pop操作中的:Thread-3 線程呈wait狀態
pop操作中的:Thread-2 線程呈wait狀態
pop操作中的:Thread-5 線程呈wait狀態
push=1
pop=anyString=0.2969600462095914
pop=0
pop=anyString=0.2969600462095914
pop操作中的:Thread-1 線程呈wait狀態
Exception in thread "Thread-4" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(Unknown Source)
	at java.util.ArrayList.get(Unknown Source)
	at entity.MyStack.pop(MyStack.java:32)
	at service.C.popService(C.java:15)
	at extthread.C_Thread.run(C_Thread.java:17)

   示例2:為了消除示例1中的異常, 將if語句改成whlie語句,這樣做確實消除了異常,但是卻出現了“假死”的情況。解決的方法當然還是將notify()方法改成notifyAll()方法。

push=1
pop=0
pop=anyString=0.8374332213169516
pop操作中的:Thread-5 線程呈wait狀態
pop操作中的:Thread-3 線程呈wait狀態
pop操作中的:Thread-2 線程呈wait狀態
pop操作中的:Thread-1 線程呈wait狀態
pop操作中的:Thread-4 線程呈wait狀態
push=1
pop=0
pop=anyString=0.8740566480464856
pop操作中的:Thread-3 線程呈wait狀態
pop操作中的:Thread-5 線程呈wait狀態

   示例3:將notify()方法改成notifyAll()方法可以解決“假死”的問題,具體輸出如下,有興趣的可以分析一下執行過程。

pop=anyString=0.5839911873460317
pop操作中的:Thread-4 線程呈wait狀態
pop操作中的:Thread-2 線程呈wait狀態
pop操作中的:Thread-5 線程呈wait狀態
pop操作中的:Thread-1 線程呈wait狀態
push=1
pop=0
pop=anyString=0.7258314594848488
push=1
pop=0
pop=anyString=0.2812645500310439
pop操作中的:Thread-5 線程呈wait狀態
pop操作中的:Thread-2 線程呈wait狀態
pop操作中的:Thread-4 線程呈wait狀態
pop操作中的:Thread-1 線程呈wait狀態
push=1
pop=0
pop=anyString=0.42098357833064437
push=1
pop=0
pop=anyString=0.6705247690886362
pop操作中的:Thread-4 線程呈wait狀態
pop操作中的:Thread-2 線程呈wait狀態
pop操作中的:Thread-5 線程呈wait狀態
pop操作中的:Thread-1 線程呈wait狀態
push=1
pop=0
pop=anyString=0.5187216316233966
push=1
pop=0
pop=anyString=0.5984864285943388
pop操作中的:Thread-5 線程呈wait狀態
pop操作中的:Thread-2 線程呈wait狀態
pop操作中的:Thread-4 線程呈wait狀態
pop操作中的:Thread-1 線程呈wait狀態
push=1
pop=0
pop=anyString=0.8992584810146089
push=1
pop=0
pop=anyString=0.8962891515250642
pop操作中的:Thread-4 線程呈wait狀態
pop操作中的:Thread-2 線程呈wait狀態
pop操作中的:Thread-5 線程呈wait狀態
pop操作中的:Thread-1 線程呈wait狀態
push=1
pop=0
pop=anyString=0.9142789789676089
push=1
pop=0
pop=anyString=0.5880907722890949
pop操作中的:Thread-5 線程呈wait狀態
pop操作中的:Thread-2 線程呈wait狀態
pop操作中的:Thread-4 線程呈wait狀態
pop操作中的:Thread-1 線程呈wait狀態

  (6)多生產與一消費:操作棧

  示例:在(5)的示例3的基礎上修改Run.java,使其有多個生產者和一個消費者,從輸出結果可以看出,push和pop值確實在0和1交替運行,但是會有線程wait狀態的輸出。

package test.run;

import service.C;
import service.P;
import entity.MyStack;
import extthread.C_Thread;
import extthread.P_Thread;

public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyStack myStack = new MyStack();

        P p1 = new P(myStack);
        P p2 = new P(myStack);
        P p3 = new P(myStack);
        P p4 = new P(myStack);
        P p5 = new P(myStack);
        P p6 = new P(myStack);

        P_Thread pThread1 = new P_Thread(p1);
        P_Thread pThread2 = new P_Thread(p2);
        P_Thread pThread3 = new P_Thread(p3);
        P_Thread pThread4 = new P_Thread(p4);
        P_Thread pThread5 = new P_Thread(p5);
        P_Thread pThread6 = new P_Thread(p6);
        pThread1.start();
        pThread2.start();
        pThread3.start();
        pThread4.start();
        pThread5.start();
        pThread6.start();

        C c1 = new C(myStack);
        C_Thread cThread = new C_Thread(c1);
        cThread.start();

    }

}
push=1
pop=0
pop=anyString=0.014834103871098847
push=1
pop=0
pop=anyString=0.6440453638062643
push=1
pop=0
pop=anyString=0.6046791928187536
push=1
pop=0
pop=anyString=0.6256159865535258
push=1
pop=0
pop=anyString=0.612883885725567
push=1
pop=0
pop=anyString=0.19664275758307848
push=1
pop=0
pop=anyString=0.3987413765108909
push=1
pop=0
pop=anyString=0.4436402093649049
push=1
pop=0
pop=anyString=0.06059233311382217
push=1
pop=0
pop=anyString=0.9833696404681963
push=1
pop=0
pop=anyString=0.1979360819377557
push=1
pop=0
pop=anyString=0.5182234836585098
push=1
pop=0
pop=anyString=0.586815755882819
push=1
pop=0
pop=anyString=0.2359007847065253
pop操作中的:Thread-6 線程呈wait狀態
push=1
pop=0
pop=anyString=0.9692705021437371
pop操作中的:Thread-6 線程呈wait狀態
push=1
pop=0
pop=anyString=0.8059112079970976
...

  (7)多生產與多消費:操作棧

  示例:修改Run.java使得有多個生產者和多個消費者,從輸出結果可以看出,也是在0和1之間交替,list對象的size()並沒有超過1。

package test.run;

import service.C;
import service.P;
import entity.MyStack;
import extthread.C_Thread;
import extthread.P_Thread;

public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyStack myStack = new MyStack();

        P p1 = new P(myStack);
        P p2 = new P(myStack);
        P p3 = new P(myStack);
        P p4 = new P(myStack);
        P p5 = new P(myStack);
        P p6 = new P(myStack);

        P_Thread pThread1 = new P_Thread(p1);
        P_Thread pThread2 = new P_Thread(p2);
        P_Thread pThread3 = new P_Thread(p3);
        P_Thread pThread4 = new P_Thread(p4);
        P_Thread pThread5 = new P_Thread(p5);
        P_Thread pThread6 = new P_Thread(p6);
        pThread1.start();
        pThread2.start();
        pThread3.start();
        pThread4.start();
        pThread5.start();
        pThread6.start();

        C r1 = new C(myStack);
        C r2 = new C(myStack);
        C r3 = new C(myStack);
        C r4 = new C(myStack);
        C r5 = new C(myStack);
        C r6 = new C(myStack);
        C r7 = new C(myStack);
        C r8 = new C(myStack);

        C_Thread cThread1 = new C_Thread(r1);
        C_Thread cThread2 = new C_Thread(r2);
        C_Thread cThread3 = new C_Thread(r3);
        C_Thread cThread4 = new C_Thread(r4);
        C_Thread cThread5 = new C_Thread(r5);
        C_Thread cThread6 = new C_Thread(r6);
        C_Thread cThread7 = new C_Thread(r7);
        C_Thread cThread8 = new C_Thread(r8);

        cThread1.start();
        cThread2.start();
        cThread3.start();
        cThread4.start();
        cThread5.start();
        cThread6.start();
        cThread7.start();
        cThread8.start();

    }

}
push=1
pop=0
pop=anyString=0.4902132952539602
pop操作中的:Thread-9 線程呈wait狀態
pop操作中的:Thread-12 線程呈wait狀態
pop操作中的:Thread-8 線程呈wait狀態
push=1
pop=0
pop=anyString=0.27018913089117513
pop操作中的:Thread-12 線程呈wait狀態
pop操作中的:Thread-9 線程呈wait狀態
push=1
pop=0
pop=anyString=0.5581725821570959
pop操作中的:Thread-11 線程呈wait狀態
pop操作中的:Thread-13 線程呈wait狀態
pop操作中的:Thread-7 線程呈wait狀態
pop操作中的:Thread-10 線程呈wait狀態
pop操作中的:Thread-6 線程呈wait狀態
push=1
pop=0
pop=anyString=0.753402968862627
pop操作中的:Thread-6 線程呈wait狀態
pop操作中的:Thread-10 線程呈wait狀態
pop操作中的:Thread-7 線程呈wait狀態
pop操作中的:Thread-13 線程呈wait狀態
pop操作中的:Thread-11 線程呈wait狀態
push=1
pop=0
pop=anyString=0.4822940142360146
pop操作中的:Thread-9 線程呈wait狀態
pop操作中的:Thread-12 線程呈wait狀態
pop操作中的:Thread-8 線程呈wait狀態
...

  12.通過管道進行線程間通信:字節流

  在Java語言中提供了各種各樣的輸入/輸出流Stream,使我們能夠很方便地對數據進行操作,其中管道流pipeStream是一種特殊的流,用於在不同線程間直接傳送數據。一個線程發送數據到輸出管道,另一個線程從輸入管道中讀數據。通過使用管道,實現不同線程間的通信,而無需借助於類似臨時文件之類的東西。

  在Java的JDK中提供了4個類來使線程間可以互相通信:

  (1)PipeInputStream和PipeOutputStream

  (2)PipeReader和PipedWriter

  示例:使用PipeInputStream和PipeOutputStream進行線程通過管道流傳輸字節流。Run類中main方法中的outputStream.connect(inputStream);是使兩個Stream之間產生通信鏈接,這樣才可以將數據進行輸出與輸入。由於Read線程啟動2秒后Write線程才啟動,由於沒有數據被寫入,所以線程阻塞在int readLength = input.read(byteArray);代碼中,直到有數據被寫入,才繼續向下運行。

package extthread;

import java.io.PipedOutputStream;

import service.WriteData;

public class ThreadWrite extends Thread {

    private WriteData write;
    private PipedOutputStream out;

    public ThreadWrite(WriteData write, PipedOutputStream out) {
        super();
        this.write = write;
        this.out = out;
    }

    @Override
    public void run() {
        write.writeMethod(out);
    }

}
package extthread;

import java.io.PipedInputStream;

import service.ReadData;

public class ThreadRead extends Thread {

    private ReadData read;
    private PipedInputStream input;

    public ThreadRead(ReadData read, PipedInputStream input) {
        super();
        this.read = read;
        this.input = input;
    }

    @Override
    public void run() {
        read.readMethod(input);
    }
}
package service;

import java.io.IOException;
import java.io.PipedInputStream;

public class ReadData {

    public void readMethod(PipedInputStream input) {
        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);
            }
            System.out.println();
            input.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package service;

import java.io.IOException;
import java.io.PipedOutputStream;

public class WriteData {

    public void writeMethod(PipedOutputStream out) {
        try {
            System.out.println("write :");
            for (int i = 0; i < 300; i++) {
                String outData = "" + (i + 1);
                out.write(outData.getBytes());
                System.out.print(outData);
            }
            System.out.println();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package test;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

import service.ReadData;
import service.WriteData;
import extthread.ThreadRead;
import extthread.ThreadWrite;

public class Run {

    public static void main(String[] args) {

        try {
            WriteData writeData = new WriteData();
            ReadData readData = new ReadData();

            PipedInputStream inputStream = new PipedInputStream();
            PipedOutputStream outputStream = new PipedOutputStream();

            // inputStream.connect(outputStream);
            outputStream.connect(inputStream);

            ThreadRead threadRead = new ThreadRead(readData, inputStream);
            threadRead.start();

            Thread.sleep(2000);

            ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
            threadWrite.start();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}
read  :
write :
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182...
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182...

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

  示例:使用PipeReader和PipedWriter在管道中傳遞字符流,原理和之前傳遞字節流一樣,只不過修改成了 String outData = "" + (i + 1); out.write(outData);代碼。

package service;

import java.io.IOException;
import java.io.PipedWriter;

public class WriteData {

    public void writeMethod(PipedWriter out) {
        try {
            System.out.println("write :");
            for (int i = 0; i < 100; i++) {
                String outData = "" + (i + 1);
                out.write(outData);
                System.out.print(outData);
            }
            System.out.println();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package service;

import java.io.IOException;
import java.io.PipedReader;

public class ReadData {

    public void readMethod(PipedReader input) {
        try {
            System.out.println("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);
            }
            System.out.println();
            input.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
read  :
write :
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950...
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950...

  14.實戰:等待/通知之交叉備份

  示例:創建20個線程,其中10個線程是將數據備份到A數據庫,另外10個線程將數據備份到B數據庫中,並且備份A數據庫和B數據庫是交叉進行的。打印的效果是交替運行的,這是因為修改volatile private boolean prevIsA = false;的值來實現線程A和線程B交替備份的效果的。

package service;

public class DBTools {

    volatile private boolean prevIsA = false;

    synchronized public void backupA() {
        try {
            while (prevIsA == true) {
                wait();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("AAAAA");
            }
            prevIsA = true;
            notifyAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void backupB() {
        try {
            while (prevIsA == false) {
                wait();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("BBBBB");
            }
            prevIsA = false;
            notifyAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package extthread;

import service.DBTools;

public class BackupA extends Thread {

    private DBTools dbtools;

    public BackupA(DBTools dbtools) {
        super();
        this.dbtools = dbtools;
    }

    @Override
    public void run() {
        dbtools.backupA();
    }

}
package extthread;

import service.DBTools;

public class BackupB extends Thread {

    private DBTools dbtools;

    public BackupB(DBTools dbtools) {
        super();
        this.dbtools = dbtools;
    }

    @Override
    public void run() {
        dbtools.backupB();
    }

}
package test.run;

import service.DBTools;
import extthread.BackupA;
import extthread.BackupB;

public class Run {

    public static void main(String[] args) {
        DBTools dbtools = new DBTools();
        for (int i = 0; i < 20; i++) {
            BackupB output = new BackupB(dbtools);
            output.start();
            BackupA input = new BackupA(dbtools);
            input.start();
        }
    }

}
AAAAA
AAAAA
AAAAA
AAAAA
AAAAA
BBBBB
BBBBB
BBBBB
BBBBB
BBBBB
AAAAA
AAAAA
AAAAA
AAAAA
AAAAA
BBBBB
BBBBB
BBBBB
BBBBB
BBBBB
...

  二、方法join的使用

  在很多情況下,主線程創建並啟動子線程,如果子線程中要進行大量的耗時運算,主線程往往將早於子線程結束之前結束。這時,如果主線程想等待子線程執行完之后再結束,比如子線程處理一個數據,主線程要取得這個數據中的值,就要用到join()方法了。join()方法的作用是等待線程對象的銷毀。

  1.學習方法join前的鋪墊

  示例:main方法中的sleep()中的值不能確定。

package extthread;

public class MyThread extends Thread {

    @Override
    public void run() {
        try {
            int secondValue = (int) (Math.random() * 10000);
            System.out.println(secondValue);
            Thread.sleep(secondValue);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package test;

import extthread.MyThread;

public class Test {

    public static void main(String[] args) {

        MyThread threadTest = new MyThread();
        threadTest.start();

        // Thread.sleep(?)
        System.out.println("要想threadTest線程執行完Thread.sleep(secondValue);之后再執行,"
                + "是無法確定sleep的值的");
    }

}
要想threadTest線程執行完Thread.sleep(secondValue);之后再執行,是無法確定sleep的值的
5433

  2.用join()方法來解決

  示例:修改1中示例的main方法,加入join()方法后,確實可以做到,主線程等待子線程執行完之后再執行主線程的代碼。

  方法join()的作用是使所屬的線程對象x(子線程threadTest)正常執行run()方法中的任務,而使當前線程z(主線程main)進行無限期的阻塞,等待線程x銷毀后再繼續執行z后面的代碼。

  方法join()具有使線程派對運行的作用,有些類似同步的運行效果。join與synchronized的區別是:join在內部使用wait()方法進行等待,而synchronized關鍵字使用的是“對象監視器”原理做同步。

package test;

import extthread.MyThread;

public class Test {

    public static void main(String[] args) {
        try {
            MyThread threadTest = new MyThread();
            threadTest.start();
            threadTest.join();
            System.out.println("main線程中想要等待子線程執行完之后執行的東西");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
4758
main線程中想要等待子線程執行完之后執行的東西

  3.方法join與異常

  示例:在join過程中,如果當前線程對象被中斷,則當前線程出現異常。線程B啟動線程A並且加入join()方法,B啟動500毫秒后,線程C啟動將線程B中斷,然后線程B出現異常。說明了方法join()和interrupt()方法如果彼此遇到,則會出現異常。但進程按鈕還呈“紅色”,這是因為線程A還在繼續運行,線程A並未出現異常,還是正常的執行狀態。

package extthread;

public class ThreadA extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            Math.random();
        }
    }
}
package extthread;

public class ThreadB extends Thread {

    @Override
    public void run() {
        try {
            ThreadA a = new ThreadA();
            a.start();
            a.join();

            System.out.println("線程B的run方法的最后一行!");
        } catch (InterruptedException e) {
            System.out.println("線程B的catch處!");
            e.printStackTrace();
        }
    }

}
package extthread;

public class ThreadC extends Thread {

    private ThreadB threadB;

    public ThreadC(ThreadB threadB) {
        super();
        this.threadB = threadB;
    }

    @Override
    public void run() {
        threadB.interrupt();
    }

}
package test.run;

import extthread.ThreadB;
import extthread.ThreadC;

public class Run {

    public static void main(String[] args) {

        try {
            ThreadB b = new ThreadB();
            b.start();

            Thread.sleep(500);

            ThreadC c = new ThreadC(b);
            c.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
線程B的catch處!
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Thread.join(Unknown Source)
	at java.lang.Thread.join(Unknown Source)
	at extthread.ThreadB.run(ThreadB.java:10)

  4.方法join(long)的使用

  示例1:方法join(long)中的參數是設定等待的時間。main線程等待子線程2秒后才是執行自己的線程。

package extthread;

public class MyThread extends Thread {

    @Override
    public void run() {
        try {
            System.out.println("begin Timer=" + System.currentTimeMillis());
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package test;

import extthread.MyThread;

public class Test {

    public static void main(String[] args) {
        try {
            MyThread threadTest = new MyThread();
            threadTest.start();

            threadTest.join(2000);

            System.out.println("  end timer=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
begin Timer=1525596937162
  end timer=1525596939162

  示例2:將main方法中的代碼改成sleep(2000),運行的效果還是等待了2秒。看起來似乎沒有什么區別,但是其實是對同步的處理不同。

package test;

import extthread.MyThread;

public class Test {

    public static void main(String[] args) {
        try {
            MyThread threadTest = new MyThread();
            threadTest.start();

            //threadTest.join(2000);
            Thread.sleep(2000);

            System.out.println("  end timer=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
begin Timer=1525597086235
  end timer=1525597088236

  5.方法join(long)與sleep(long)的區別

  方法join(long)的功能在內部是使用了wait(long)方法來實現的,所以方法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;
            }
        }
    }

  可以看出,當執行了wait(long)方法后,當前線程的鎖被釋放,那么其他線程就可以調用此線程中的同步方法了。

  而從sleep(long)方法的源碼可以看出,sleep(long)方法並不釋放鎖。

    public static void sleep(long millis, int nanos)
    throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        sleep(millis);
    }

  示例:線程A啟動,然后線程B被線程A啟動,在線程A啟動的1000毫秒后線程C啟動,線程C執行線程B的同步方法bService中的打印語句。由於線程A使用Thread.sleep(6000);一直持有線程B對象的鎖達到6秒,然后線程C只有在線程A時間達到6秒后釋放線程B對象鎖時,才可以調用線程B中的同步方法。證明了在Thread.sleep(6000);過程中不釋放鎖。

package extthread;

public class ThreadB extends Thread {

    @Override
    public void run() {
        try {
            System.out.println("b run begin timer="+ System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("b run   end timer="+ System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void bService() {
        System.out.println("bService    timer=" + System.currentTimeMillis());
    }

}
package extthread;

public class ThreadC extends Thread {

    private ThreadB threadB;

    public ThreadC(ThreadB threadB) {
        super();
        this.threadB = threadB;
    }

    @Override
    public void run() {
        threadB.bService();
    }

}
package extthread;

public class ThreadA extends Thread {

    private ThreadB b;

    public ThreadA(ThreadB b) {
        super();
        this.b = b;
    }

    @Override
    public void run() {
        try {
            synchronized (b) {
                b.start();
                Thread.sleep(6000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package test.run;

import extthread.ThreadA;
import extthread.ThreadB;
import extthread.ThreadC;

public class Run {

    public static void main(String[] args) {

        try {
            ThreadB b = new ThreadB();

            ThreadA a = new ThreadA(b);
            a.start();

            Thread.sleep(1000);

            ThreadC c = new ThreadC(b);
            c.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

  示例2:修改線程A類,改成join()方法,從輸出結果可以看出,線程A釋放了線程B的鎖,所以線程C可以調用線程B中的同步方法,證明了join(long)方法具有釋放鎖的特點。

package extthread;

public class ThreadA extends Thread {

    private ThreadB b;

    public ThreadA(ThreadB b) {
        super();
        this.b = b;
    }

    @Override
    public void run() {
        try {
            synchronized (b) {
                b.start();
                b.join();
                for (int i = 0; i < Integer.MAX_VALUE; i++) {
                    Math.random();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
b run begin timer=1525599034475
bService    timer=1525599035477
b run   end timer=1525599039485

  6.方法join()后面的代碼提前運行:出現意外

  示例:程序運行后,有可能會出現兩種不同的結果。

package extthread;

public class ThreadA extends Thread {
    private ThreadB b;

    public ThreadA(ThreadB b) {
        super();
        this.b = b;
    }

    @Override
    public void run() {
        try {
            synchronized (b) {
                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();
        }
    }
}
package extthread;

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();
        }
    }
}
package test.run;

import extthread.ThreadA;
import extthread.ThreadB;

public class Run1 {
    public static void main(String[] args) {
        try {
            ThreadB b = new ThreadB();
            ThreadA a = new ThreadA(b);
            a.start();
            b.start();
            b.join(2000);
            System.out.println("                    main end "+ System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

  結果1:

  ①b.join(2000);方法先搶到B鎖,然后將B鎖進行釋放。

  ②線程A搶到鎖,打印線程A begin,並且sleep(5000);

  ③線程A打印end,並且釋放鎖。

  ④這時join(2000)和線程B爭搶鎖,而join(2000)再次搶到鎖,發現時間已過,釋放鎖后打印main end。

  ⑤線程B搶到鎖打印begin。

  ⑥線程B在5秒之后再打印end。

begin A ThreadName=Thread-1  1525599339904
  end A ThreadName=Thread-1  1525599344908
                    main end 1525599344908
begin B ThreadName=Thread-0  1525599344909
  end B ThreadName=Thread-0  1525599349916

   結果2:

  ①b.join(2000);方法先搶到B鎖,然后將B鎖進行釋放。

  ②線程A搶到鎖,打印線程A begin,並且sleep(5000);

  ③線程A打印end,並且釋放鎖。

  ④這時join(2000)和線程B爭搶鎖,線程B搶到鎖后執行sleep(5000)后釋放鎖后打印end。

  ⑤main end在最后輸出。

begin A ThreadName=Thread-0  1525599613121
  end A ThreadName=Thread-0  1525599618124
begin B ThreadName=Thread-1  1525599618124
end B ThreadName=Thread-1 1525599623124
main end 1525599623124

  結果3:

  ①b.join(2000);方法先搶到B鎖,然后將B鎖進行釋放。

  ②線程A搶到鎖,打印線程A begin,並且sleep(5000);

  ③線程A打印end,並且釋放鎖。

  ④這時join(2000)和線程B爭搶鎖,而join(2000)再次搶到鎖,發現時間已過,釋放鎖后打印main end。

  ⑤線程B搶到鎖打印begin。

  ⑥這時main end也異步輸出。

  ⑦線程B打印end。

begin A ThreadName=Thread-0  1525599613121
  end A ThreadName=Thread-0  1525599618124
begin B ThreadName=Thread-1  1525599618124
main end 1525599618124
end B ThreadName=Thread-1 1525599623124

  7.方法join()后面的代碼提前運行:解釋意外

  示例:為了解釋原因,用RunFirst.java進行測試,RunFirst.java類的代碼中僅僅少了join(2000);這行代碼。從輸出結果對比可以發現,main end往往都是第一個打印的。所以可以確定的是,方法join(2000)大部分是先運行的,也就是先搶到線程B的鎖,然后快速進行釋放。

package test.run;

import extthread.ThreadA;
import extthread.ThreadB;

public class RunFirst {

    public static void main(String[] args) {
        ThreadB b = new ThreadB();
        ThreadA a = new ThreadA(b);
        a.start();
        b.start();
        System.out.println("   main end=" + System.currentTimeMillis());
    }

}
   main end=1525599679501
begin A ThreadName=Thread-0  1525599679501
  end A ThreadName=Thread-0  1525599684510
begin B ThreadName=Thread-1  1525599684511
  end B ThreadName=Thread-1  1525599689515

  三、類ThreadLocal的使用

  變量值的共享可以使用public static變量的形式,所有的線程都使用同一個public static變量。如果想實現每一個線程都有自己的共享變量,就需要使用JDK中提供的ThreadLocal類。

  ThreadLocal類主要解決的就是每個線程綁定自己的值,可以將ThreadLocal類類比喻成全局存放數據的盒子,盒子中可以存儲每個線程的私有數據。

  1.方法get()與null

  示例:從運行結果可以看出,第一次調用tl對象的get()方法時返回的值是null,通過調用set()方法賦值后順利取出值並打印出來。ThreadLocal解決的是變量在不同線程間的隔離性,也就是不同線程擁有自己的值,不同線程的值是可以放入ThreadLocal類中進行保存的。

package test;

public class Run {
    public static ThreadLocal<String> tl = new ThreadLocal<String>();

    public static void main(String[] args) {
        if (tl.get() == null) {
            System.out.println("get為null時候的打印");
            tl.set("給定的值!");
        }
        System.out.println(tl.get());
        System.out.println(tl.get());
    }

}
get為null時候的打印
給定的值!
給定的值!

  2.驗證線程變量的隔離性

  示例1:從輸出結果可以看出,雖然3個線程都向tl對象中set()數據值,但是每個線程還是能取出自己的數據,說明了數據的隔離性。

package tools;

public class Tools {

    public static ThreadLocal<String> tl = new ThreadLocal<String>();

}
package extthread;

import tools.Tools;

public class ThreadA extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                if (Tools.tl.get() == null) {
                    Tools.tl.set("ThreadA" + (i + 1));
                } else {
                    System.out.println("ThreadA get Value=" + Tools.tl.get());
                }
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package extthread;

import tools.Tools;

public class ThreadB extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                if (Tools.tl.get() == null) {
                    Tools.tl.set("ThreadB" + (i + 1));
                } else {
                    System.out.println("ThreadB get Value=" + Tools.tl.get());
                }
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
ThreadB get Value=Main1
ThreadA get Value=Main1
Main get Value=Main1
Main get Value=Main2
ThreadB get Value=Main2
ThreadA get Value=Main2
Main get Value=Main3
ThreadA get Value=Main3
ThreadB get Value=Main3
ThreadA get Value=Main4
Main get Value=Main4
ThreadB get Value=Main4
ThreadB get Value=Main5
ThreadA get Value=Main5
Main get Value=Main5
Main get Value=Main6
ThreadA get Value=Main6
ThreadB get Value=Main6
...

   示例2:線程B在線程A啟動1000毫秒之后才啟動,從輸出結果也可以看出,線程A輸出的時間不影響線程B輸出的時間。

package tools;

import java.util.Date;

public class Tools {

    public static ThreadLocal<Date> tl = new ThreadLocal<Date>();

}
package extthread;

import java.util.Date;
import tools.Tools;

public class ThreadA extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 0; i < 20; i++) {
                if (Tools.tl.get() == null) {
                    Tools.tl.set(new Date());
                }
                System.out.println("A " + Tools.tl.get().getTime());
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
package extthread;

import java.util.Date;

import tools.Tools;

public class ThreadB extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 0; i < 20; i++) {
                if (Tools.tl.get() == null) {
                    Tools.tl.set(new Date());
                }
                System.out.println("B " + Tools.tl.get().getTime());
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
package test;

import extthread.ThreadA;
import extthread.ThreadB;

public class Run {

    public static void main(String[] args) {
        try {
            ThreadA a = new ThreadA();
            a.start();

            Thread.sleep(1000);

            ThreadB b = new ThreadB();
            b.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}
A 1525615562052
A 1525615562052
A 1525615562052
A 1525615562052
A 1525615562052
A 1525615562052
A 1525615562052
A 1525615562052
A 1525615562052
A 1525615562052
B 1525615563063
A 1525615562052
B 1525615563063
A 1525615562052
B 1525615563063
A 1525615562052
B 1525615563063
A 1525615562052
B 1525615563063
A 1525615562052
B 1525615563063
A 1525615562052
B 1525615563063
A 1525615562052
B 1525615563063
A 1525615562052
B 1525615563063
...

  3.解決get()返回null問題

  示例:通過繼承ThreadLocal類並且重寫initialValue方法,重新賦予返回值,這樣可以使get()得到的返回值不為null,而是給定的值。

package ext;

public class ThreadLocalExt extends ThreadLocal<Object> {
    @Override
    protected Object initialValue() {
        return "默認值但是不是null";
    }
}
package test;

import ext.ThreadLocalExt;

public class Run {
    public static ThreadLocalExt tl = new ThreadLocalExt();

    public static void main(String[] args) {
        if (tl.get() == null) {
            System.out.println("為null時的輸出");
            tl.set("為null時給定一個值");
        }
        System.out.println(tl.get());
        System.out.println(tl.get());
    }

}
默認值但是不是null
默認值但是不是null

  4.再次驗證線程變量的隔離性

  示例:從輸出結果可以看出,線程A在main線程執行完的1000+5000毫秒之后才開始打印,打印的數據具有隔離性,不受main線程的影響。

package ext;

import java.util.Date;

public class ThreadLocalExt extends ThreadLocal<Object> {
    @Override
    protected Object initialValue() {
        return new Date().getTime();
    }
}
package tools;

import ext.ThreadLocalExt;

public class Tools {
    public static ThreadLocalExt tl = new ThreadLocalExt();
}
package extthread;

import tools.Tools;

public class ThreadA extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("在線程A中取值=" + Tools.tl.get());
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package test;

import tools.Tools;
import extthread.ThreadA;

public class Run {

    public static void main(String[] args) {
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("在main線程中取值=" + Tools.tl.get());
                Thread.sleep(100);
            }
            Thread.sleep(5000);
            ThreadA a = new ThreadA();
            a.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
在main線程中取值=1525616073293
在main線程中取值=1525616073293
在main線程中取值=1525616073293
在main線程中取值=1525616073293
在main線程中取值=1525616073293
在main線程中取值=1525616073293
在main線程中取值=1525616073293
在main線程中取值=1525616073293
在main線程中取值=1525616073293
在main線程中取值=1525616073293
在線程A中取值=1525616079362
在線程A中取值=1525616079362
在線程A中取值=1525616079362
在線程A中取值=1525616079362
在線程A中取值=1525616079362
在線程A中取值=1525616079362
在線程A中取值=1525616079362
在線程A中取值=1525616079362
在線程A中取值=1525616079362
在線程A中取值=1525616079362

  四、類InheritableThreadLocal的使用

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

  1.值繼承

  示例:從輸出結果可以看出,通過使用InheritableThreadLocalExt類,線程A可以繼承main主線程的值,即使main線程執行完之后5秒線程A才開始執行。

package ext;

import java.util.Date;

public class InheritableThreadLocalExt extends InheritableThreadLocal<Object> {
    @Override
    protected Object initialValue() {
        return new Date().getTime();
    }
}
package tools;

import ext.InheritableThreadLocalExt;

public class Tools {
    public static InheritableThreadLocalExt tl = new InheritableThreadLocalExt();
}
package extthread;

import tools.Tools;

public class ThreadA extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("在線程A中取值=" + Tools.tl.get());
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package test;

import tools.Tools;
import extthread.ThreadA;

public class Run {

    public static void main(String[] args) {
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("在main方法中取值=" + Tools.tl.get());
                Thread.sleep(100);
            }
            Thread.sleep(5000);
            ThreadA a = new ThreadA();
            a.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
在main方法中取值=1525616443288
在main方法中取值=1525616443288
在main方法中取值=1525616443288
在main方法中取值=1525616443288
在main方法中取值=1525616443288
在main方法中取值=1525616443288
在main方法中取值=1525616443288
在main方法中取值=1525616443288
在main方法中取值=1525616443288
在main方法中取值=1525616443288
在線程A中取值=1525616443288
在線程A中取值=1525616443288
在線程A中取值=1525616443288
在線程A中取值=1525616443288
在線程A中取值=1525616443288
在線程A中取值=1525616443288
在線程A中取值=1525616443288
在線程A中取值=1525616443288
在線程A中取值=1525616443288
在線程A中取值=1525616443288

  2.值繼承再修改

  示例:修改InheritableThreadLocalExt類,添加childValue方法,從輸出結果可以看出,線程A從main主線程取得值后還對取到的值進行了添加值的操作。需要注意的是如果子線程在取得值的同時,主線程將InheritableThreadLocal中的值進行更改,那么子線程取到的值還是舊值。

package ext;

import java.util.Date;

public class InheritableThreadLocalExt extends InheritableThreadLocal<Object> {
    @Override
    protected Object initialValue() {
        return new Date().getTime();
    }

    @Override
    protected Object childValue(Object parentValue) {
        return parentValue + ",childValue方法的返回值!";
    }
}
在main方法中取值=1525616738721
在main方法中取值=1525616738721
在main方法中取值=1525616738721
在main方法中取值=1525616738721
在main方法中取值=1525616738721
在main方法中取值=1525616738721
在main方法中取值=1525616738721
在main方法中取值=1525616738721
在main方法中取值=1525616738721
在main方法中取值=1525616738721
在線程A中取值=1525616738721,childValue方法的返回值!
在線程A中取值=1525616738721,childValue方法的返回值!
在線程A中取值=1525616738721,childValue方法的返回值!
在線程A中取值=1525616738721,childValue方法的返回值!
在線程A中取值=1525616738721,childValue方法的返回值!
在線程A中取值=1525616738721,childValue方法的返回值!
在線程A中取值=1525616738721,childValue方法的返回值!
在線程A中取值=1525616738721,childValue方法的返回值!
在線程A中取值=1525616738721,childValue方法的返回值!
在線程A中取值=1525616738721,childValue方法的返回值!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM