3.線程間的通信

前記:師夷長技以自強


1.用輪詢實現的線程間通信機制

如下:

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

class MyList{
    private List list = new ArrayList();
    public void add(){
        list.add("haha");
    }
    public int size(){
        return list.size();
    }
}

class ThreadA extends Thread{
    volatile private MyList list;

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

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

class ThreadB extends Thread{
    volatile private MyList list;

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

    @Override
    public void run() {
        try{
            while (true){
                if(list.size()==5){
                    System.out.println("==5,ThreadB exits!!!");
                    throw new InterruptedException();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class Test {
    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();
    }
}

output:
添加了1個元素
添加了2個元素
添加了3個元素
添加了4個元素
添加了5個元素
==5,ThreadB exits!!!
java.lang.InterruptedException
at ThreadB.run(Test.java:48)
添加了6個元素
添加了7個元素
添加了8個元素
添加了9個元素
添加了10個元素
在線程A添加了5個元素後,線程B檢測到了符合的條件然後拋出異常退出。然而,在這種情況下線程B是沒有讓出CPU在等待的。

2.wait和notify

2.1基本使用

wait函數是讓當前線程暫停運行,notify函數是使等待當前鎖的線程重新獲得cpu運行。需要注意的是,wait和notify在被調用之前都要獲取到相應的鎖,也就是和如下:

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

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

output:
start wait time=1591601206518
start notify time=1591601209523
end notify time=1591601209526
end wait time=1591601209528
可以看到,線程1停止三分鐘後被線程2通知後繼續運行。

2.2把第一個案例改爲wait和notify實現

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

class MyList{
    private static List list = new ArrayList();
    public static void add(){
        list.add("haha");
    }
    public static int size(){
        return list.size();
    }
}

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

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();
                    if (MyList.size()==5){
                        lock.notify();
                        System.out.println("sended message");
                    }
                    System.out.println("add "+(i+1)+"elements");
                    Thread.sleep(1000);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

public class Test{
    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();
        }
    }
}

output:
wait begin 1591604846284
add 1elements
add 2elements
add 3elements
add 4elements
sended message
add 5elements
add 6elements
add 7elements
add 8elements
add 9elements
add 10elements
wait end1591604856392
線程A剛開始獲取鎖然後調用wait釋放鎖,線程B得到鎖運行,並在添加第5個元素的時候喚醒了線程A,從而得到以上的運行效果。處理notify方法外,還有一個notifyAll方法。前者是僅僅通知等待隊列中的一個線程,後者是通知因同一個資源而進入等待隊列的進程,然後根據優先級選擇。
每個鎖對象都有兩個隊列,一個是就緒隊列,一個是阻塞隊列。就緒隊列存儲了將要獲得鎖的線程,阻塞隊列存儲了被阻塞的線程。線程被喚醒進入就緒隊列準備被執行調度,線程被wait則進入阻塞隊列等待下一次的喚醒。

2.3wait釋放鎖而notify不釋放鎖

wait被調用後當前線程將釋放鎖,但是notify將不會釋放,線程執行完同步代碼塊後才釋放鎖。

2.4 線程調用了wait方法後,再調用interruput方法會出現InterrruptedException異常

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 thread interrupted!!!");
        }
    }
}

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

output:
begin wait()
java.lang.InterruptedException
wait thread interrupted!!!
at java.base/java.lang.Object.wait(Native Method)
at java.base/java.lang.Object.wait(Object.java:516)
at Service.testMethod(Test.java:6)
at ThreadA.run(Test.java:26)
可見,線程A調用了wait方法後如果執行interrupt方法,就會引發InterruptedException異常。

2.5 notify只通知一個線程

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

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

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

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


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

output:
begin wait() ThreadName=Thread-0
begin wait() ThreadName=Thread-1
begin wait() ThreadName=Thread-2
end wait() ThreadName=Thread-0
如果在把通知線程的代碼改爲如下

class NotifyThread extends Thread{
   private Object lock;

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

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

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

2.6 notifyAll喚醒所有線程

把上例的通知線程改爲如下即可


class NotifyThread extends Thread{
    private Object lock;

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

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

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

2.7 wait(long)

如果在指定的時間內沒有線程喚醒當前線程,則超過這個時間則自動喚醒。

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

    public static void main(String[] args) {
        Thread thread = new Thread(runnable1);
        thread.start();
    }
}

output:
wait begin timer=1591623207205
wait end timer=1591623212285

2.8wait/notify模式注意點

notify提前如果其他線程的notify比當前線程更早,則notify是無效的。
wait等待的條件發生變化時我們知道wait線程可以被喚醒,也可以超時自動喚醒,那當線程被喚醒後其所等待的添加發生變化時也會引起異常。比如兩個線程同時刪除一個集合的元素,開始集合爲空兩個刪除線程都阻塞。後來另一個線程往集合放一個元素,會造成兩個線程執行刪除,而因爲只有一個元素則必定會引發異常。

3 生產者消費者模式

3.1當只有一個生產者和消費者時

此時只要notify就可以完成對異類線程的喚醒。如下:

class P {
    private String lock;

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

    public void setValue() {
        try {
            synchronized (lock) {
                while (!ValueObject.value.equals("")) {
                    System.out.println("P "+Thread.currentThread().getName()+" waiting!");
                    lock.wait();
                }
                System.out.println("P "+Thread.currentThread().getName()+" runnable!");
                String value = System.currentTimeMillis() + "_" + System.nanoTime();
                ValueObject.value = value;
                lock.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class C {
    private String lock;

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

    public void getValue() {
        try {
            synchronized (lock) {
                while (ValueObject.value.equals("")) {
                    System.out.println("C "+Thread.currentThread().getName()+" waiting!");
                    lock.wait();
                }
                System.out.println("C "+Thread.currentThread().getName()+" runnable!");
                ValueObject.value = "";
                lock.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ValueObject {
    public static String value = "";
}

class ThreadP extends Thread {
    private P p;

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

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

class ThreadC extends Thread {
    private C r;

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

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

public class Test {
    public static void main(String[] args) throws InterruptedException {
        String lock = new String("");
        P p = new P(lock);
        C c = new C(lock);
        ThreadP[] pThread = new ThreadP[2];
        ThreadC[] cThread = new ThreadC[2];
        for (int i = 0; i < 1; i++) {
            pThread[i] = new ThreadP(p);
            pThread[i].setName("P"+(i+1));
            cThread[i] = new ThreadC(c);
            cThread[i].setName("C"+(i+1));
            pThread[i].start();
            cThread[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());
        }
    }
}

output:(部分)
P P1 waiting!
C C1 runnable!
P P1 runnable!
P P1 waiting!
C C1 runnable!
P P1 runnable!
P P1 waiting!
C C1 runnable!
可以看出,生產者和消費者可以同步進行。

3.2多生產者和多消費者時

如果每次僅僅喚醒一個線程,那麼可能只喚醒了同類線程,等待的線程越來越多,最後造成程序的假死。

class P {
    private String lock;

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

    public void setValue() {
        try {
            synchronized (lock) {
                while (!ValueObject.value.equals("")) {
                    System.out.println("P "+Thread.currentThread().getName()+" waiting!");
                    lock.wait();
                }
                System.out.println("P "+Thread.currentThread().getName()+" runnable!");
                String value = System.currentTimeMillis() + "_" + System.nanoTime();
                ValueObject.value = value;
                lock.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class C {
    private String lock;

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

    public void getValue() {
        try {
            synchronized (lock) {
                while (ValueObject.value.equals("")) {
                    System.out.println("C "+Thread.currentThread().getName()+" waiting!");
                    lock.wait();
                }
                System.out.println("C "+Thread.currentThread().getName()+" runnable!");
                ValueObject.value = "";
                lock.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ValueObject {
    public static String value = "";
}

class ThreadP extends Thread {
    private P p;

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

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

class ThreadC extends Thread {
    private C r;

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

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

public class Test {
    public static void main(String[] args) throws InterruptedException {
        String lock = new String("");
        P p = new P(lock);
        C c = new C(lock);
        ThreadP[] pThread = new ThreadP[2];
        ThreadC[] cThread = new ThreadC[2];
        for (int i = 0; i < 2; i++) {
            pThread[i] = new ThreadP(p);
            pThread[i].setName("P"+(i+1));
            cThread[i] = new ThreadC(c);
            cThread[i].setName("C"+(i+1));
            pThread[i].start();
            cThread[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());
        }
    }
}

output:(部分)
P P1 waiting!
C C2 runnable!
C C2 waiting!
P P1 runnable!
P P1 waiting!
C C2 runnable!
C C2 waiting!
P P1 runnable!
P P1 waiting!
C C2 runnable!
C C2 waiting!
P P1 runnable!
C C2 runnable!
P P1 runnable!
P P1 waiting!
P P2 waiting!
C C1 runnable!
C C1 waiting!
P P1 runnable!
P P1 waiting!
P P2 waiting!
C C2 runnable!
C C2 waiting!
C C1 waiting!
main RUNNABLE
Monitor Ctrl-Break RUNNABLE
P1 WAITING
C1 WAITING
P2 WAITING
C2 WAITING
經過若干次的調用,最後所有的生產者和消費者都進入了等待狀態。解決的辦法也很簡單,就是把P.java和C.java中的notify改爲notifyAll。

4.管道通信

4.1字節流

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

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

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

class ThreadWrite extends Thread{
    private WriteData write;
    private PipedOutputStream out;

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

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

class ThreadRead extends Thread{
    private ReadData read;
    private PipedInputStream input;

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

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

public class Test{
    public static void main(String[] args) {
        try {
            WriteData writeData = new WriteData();
            ReadData readData = new ReadData();

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

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

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

4.2字符流

import java.io.*;

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

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

class ThreadWrite extends Thread{
    private WriteData write;
    private PipedWriter out;

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

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

class ThreadRead extends Thread{
    private ReadData read;
    private PipedReader input;

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

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

public class Test{
    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);
            inputStream.connect(outputStream);

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

5.join方法

5.1join方法的基本使用

join方法使所屬的線程對象x正常執行run方法中的任務,使當前線程z進行無限期阻塞,等待線程x銷燬後再繼續執行線程z後面的代碼。

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

public class Test{
    public static void main(String[] args) {
        try {
            MyThread threadTest = new MyThread();
            threadTest.start();
            threadTest.join();
            System.out.println("haha");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

output:
4603
haha

5.2 join方法與interrrupt

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

class ThreadB extends Thread{
    @Override
    public void run() {
        try {
            ThreadA threadA = new ThreadA();
            threadA.start();
            threadA.join();
            System.out.println("ThreadB run end!");
        } catch (InterruptedException e) {
            System.out.println("ThreadB run catch!");
            e.printStackTrace();
        }
    }
}

class ThreadC extends Thread{
    private ThreadB threadB;

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

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

public class Test{
    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();
        }
    }
}

output:
ThreadB run catch!
java.lang.InterruptedException
at java.base/java.lang.Object.wait(Native Method)
at java.base/java.lang.Thread.join(Thread.java:1353)
at java.base/java.lang.Thread.join(Thread.java:1427)
at ThreadB.run(Test.java:17)
當線程B在調用了join後被線程C interrupt,會發生異常,但線程按鈕還是呈“紅色”,因爲線程A還在繼續運行中,沒有發生異常。

5.3 join(long)方法

class MyThread extends Thread{
    @Override
    public void run() {
        try {
            System.out.println("begin Timer="+System.currentTimeMillis());
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
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();
        }
    }
}

output:
begin Timer=1591694019386
end timer=1591694021385
可以看到,運行的效果是等待了2秒鐘。

5.4join(long)和sleep(join)的區別

閱讀join的源碼發現裏面是使用wait實現的,因此join別調用後是可以釋放鎖的。而sleep方法被調用後卻不釋放鎖。


class ThreadA extends Thread{
    private ThreadB b;

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

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

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

class ThreadC extends Thread{
    private ThreadB threadB;

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

    @Override
    public void run() {

        threadB.bService();
    }
}


public class Test{
    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();
        }
    }
}

output:
b run begin timer=1591706030617
b run end timer=1591706035720
bService timer1591706036618
可見,bService是過了6秒後被調用的,也就是說線程A在調用了Thread.sleep(6000)後沒有釋放線程B對象的鎖,導致其同步方法bService不能被線程C立即調用。而join是可以釋放鎖的,如下


class ThreadA extends Thread{
    private ThreadB b;

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

    @Override
    public void run() {
        try {
            synchronized (b){
                b.start();
                b.join();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

class ThreadC extends Thread{
    private ThreadB threadB;

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

    @Override
    public void run() {

        threadB.bService();
    }
}


public class Test{
    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();
        }
    }
}

output:
b run begin timer=1591706475496
bService timer1591706476497
b run end timer=1591706480580

6.ThreadLocal使用

ThreadLocal類主要解決每個線程綁定自己的值,變量在不同線程間的隔離性。

public class Test{
    public static ThreadLocal t1 = new ThreadLocal();

    public static void main(String[] args) {
        if(t1.get() == null){
            System.out.println("no value!");
            t1.set("my value");
        }
        System.out.println(t1.get());
        System.out.println(t1.get());
    }
}

output:
no value!
my value
my value
可以通過set和get對ThreadLocal操作。

6.1 驗證線程變量的隔離性

class Tools{
    public static ThreadLocal t1 = new ThreadLocal();
}

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

public class Test{
    public static void main(String[] args) {
        try {
            ThreadA a = new ThreadA();
            ThreadB b = new ThreadB();
            a.start();
            b.start();
            for (int i = 0; i < 100; i++) {
                Tools.t1.set("Main"+i);
                System.out.println("Main get Value="+Tools.t1.get());
                Thread.sleep(200);
            }

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

output:(部分)
Main get Value=Main92
ThreadA get Value=ThreadA93
ThreadB get Value=ThreadB93
Main get Value=Main93
ThreadA get Value=ThreadA94
ThreadB get Value=ThreadB94
Main get Value=Main94
ThreadA get Value=ThreadA95
ThreadB get Value=ThreadB95
Main get Value=Main95
ThreadA get Value=ThreadA96
ThreadB get Value=ThreadB96
Main get Value=Main96
ThreadA get Value=ThreadA97
ThreadB get Value=ThreadB97
Main get Value=Main97
ThreadA get Value=ThreadA98
ThreadB get Value=ThreadB98
Main get Value=Main98
ThreadA get Value=ThreadA99
ThreadB get Value=ThreadB99
Main get Value=Main99
可見,這三個線程都能取出屬於自己的值,ThreadLocal類對每個線程的存儲是具有隔離性的。

6.2解決ThreadLocal中值爲null

可以創建一個繼承自ThreadLocal的類ThreadLocalEx,重寫其initialValue方法

import java.util.Date;

class ThreadLocalEx extends ThreadLocal{
    @Override
    protected Object initialValue() {
        return new Date().getTime();
    }
}

class Tools{
    public static ThreadLocalEx t1 = new ThreadLocalEx();
}

class ThreadA extends Thread{
    @Override
    public void run() {
        try{
            for (int i = 0; i < 10; i++) {
                System.out.println("ThreadA value="+Tools.t1.get());
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Test{
    public static void main(String[] args) {
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("Main value="+Tools.t1.get());
                Thread.sleep(100);
            }
            Thread.sleep(5000);
            ThreadA a = new ThreadA();
            a.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

output:
Main value=1591710156676
Main value=1591710156676
Main value=1591710156676
Main value=1591710156676
Main value=1591710156676
Main value=1591710156676
Main value=1591710156676
Main value=1591710156676
Main value=1591710156676
Main value=1591710156676
ThreadA value=1591710162774
ThreadA value=1591710162774
ThreadA value=1591710162774
ThreadA value=1591710162774
ThreadA value=1591710162774
ThreadA value=1591710162774
ThreadA value=1591710162774
ThreadA value=1591710162774
ThreadA value=1591710162774
ThreadA value=1591710162774
可以看到,Main和ThreadA中都有自己的默認值了。

7.InheritableThreadLocal類

該類用法與ThreadLocal類似,只是子線程可以繼承從父線程中的值。

8.總結

本文主要說明了線程之間是如何通信的,其中等待通知是基本的模式,還有生產者消費者,管道等通信方式,join方法的使用等。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章