1. 程式人生 > >深入理解Java多執行緒(三)

深入理解Java多執行緒(三)

關於java多執行緒的概念以及基本用法:java多執行緒基礎

3, 執行緒間通訊

執行緒在作業系統中是獨立的個體,經過特殊的處理,執行緒間可以實現通訊,進而成為一個整體,提高CPU利用率

3.1,等待/通知機制

等待:wait()方法作用是使當前執行程式碼的執行緒阻塞,在呼叫wait()之前,執行緒必須獲得該物件的物件級別鎖,即只能在同步方法或同步塊中呼叫wait()方法。執行wait()方法後,當前執行緒會釋放鎖。如果呼叫wait()方法時沒有鎖,那麼會丟擲IllegalMonitorStateException

通知:notify()也要在同步方法或者同步程式碼塊中呼叫,呼叫之前,執行緒也必須獲得該物件的物件級別的鎖,若沒有,則會丟擲IllegalMonitorStateException。該方法用於通知那些可能等待該物件的物件鎖的其他執行緒,如果有多個執行緒等待,則由執行緒規劃器隨機挑選其中一個呈wait狀態的執行緒,對其發出通知,並使它等待獲取該物件的物件鎖。執行notify()方法後,當前執行緒不會馬上釋放該物件鎖,呈wait狀態的執行緒也不能馬上獲取該物件鎖,要等執行notify()方法的執行緒執行完,當前執行緒才會釋放鎖。
方法notifyAll()可以喚醒全部執行緒

新建MyThread1類:

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

新建MyThread2類:

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

測試類:

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

結果:

開始  wait time1534469153084
開始  notify time1534469156084
結束  notify time1534469156084
結束  wait time1534469156084

執行緒t1在執行wait()方法後進入阻塞狀態,3秒後執行緒t2執行,執行了notify()方法後執行緒t2在執行完程式碼塊後喚醒執行緒t1

方法wait(time)的作用是等待一段時間是否有執行緒對鎖進行喚醒,如果超過這個時間就自動喚醒

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

        }
    };

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

結果:

wait begin timer=1534473901885
wait end timer=1534473906886
3.2,通過管道進行執行緒間通訊:位元組流

Java語言裡提供了很多的輸入/輸出流Stream,便於資料讀寫,pipeStream(管道流)是一種特殊的流,用於在不同執行緒間直接傳送資料,一個執行緒傳送資料到輸出管道,另一個執行緒從輸入管道中讀取資料,可以實現執行緒間的通訊
Java的JDK提供了4個類

  • 位元組流:PipedInputStream和PipedOutPutStream
  • 字元流:PipedReader和PipedWriter

先看看位元組流:
新建WriteData類:

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

新建ReadData類:

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

}

新建兩個執行緒:

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

測試類:

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

結果:

read: 
write: 
1234567891011121314151617181920
1234567891011121314151617181920

再看看字元流:
WriteData類:

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

ReadData類:

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

}

兩個執行緒類:

public class ThreadRead extends Thread{

    private ReadData read;
    private PipedReader input;

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

    @Override
    public void run() {
        read.readMethod(input);
    }
}
public class ThreadWrite extends Thread{

    private WriteData write;
    private PipedWriter out;

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

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

測試類:

public class Run {

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

            PipedReader inputStream =new PipedReader();
            PipedWriter outputStream = new PipedWriter();

            //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 e1) {
            e1.printStackTrace();
        }
    }
}

結果:

read: 
write: 
1234567891011121314151617181920
1234567891011121314151617181920
3.2,方法join的使用

很多時候,主執行緒建立並啟動子執行緒,子執行緒要進行一些耗時的計算,主執行緒往往早於子執行緒結束,如果主執行緒想等待子執行緒結束,則需要join()方法

新建MyThread類:

public class MyThread extends Thread{

    @Override
    public void run() {
        try {
            int secondValue = (int)(Math.random()*1000);
            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("我要等子執行緒結束再開始");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

結果:

231
我要等子執行緒結束再開始

join()方法的作用是使所屬執行緒物件正常執行run()方法,使當前主執行緒處於阻塞狀態,等子執行緒結束後再執行主執行緒,join在內部使用wait()方法進行等待

方法join(time)和sleep(time)的區別?
這兩個方法都會等待time時間再執行,但是在同步的處理上是不一樣的
join(time)的功能是在內部使用wait(time)方法,所以join方法其實會釋放鎖,
join原始碼:

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

在執行join(time)方法後,當前執行緒的鎖被釋放,此時其他執行緒可以呼叫此執行緒的同步方法,而sleep(time)方法則不會釋放鎖

3.3,類ThreadLocal的使用

ThreadLocal為變數在每個執行緒中都建立了一個副本,所以每個執行緒都可以訪問自己內部的副本變數

新建Tools類:

public class Tools {

    public static ThreadLocal t1 = new ThreadLocal();
}

兩個執行緒類:

public class ThreadA extends Thread{

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

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

測試類:

public class Run {

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

結果:

Main get Value=Main1
ThreadA get Value=ThreadA1
ThreadB get Value=ThreadB1
ThreadB get Value=ThreadB2
ThreadA get Value=ThreadA2
Main get Value=Main2
Main get Value=Main3
ThreadB get Value=ThreadB3
ThreadA get Value=ThreadA3
Main get Value=Main4
ThreadB get Value=ThreadB4
ThreadA get Value=ThreadA4
ThreadB get Value=ThreadB5
ThreadA get Value=ThreadA5
Main get Value=Main5
Main get Value=Main6
ThreadA get Value=ThreadA6
ThreadB get Value=ThreadB6
ThreadB get Value=ThreadB7
ThreadA get Value=ThreadA7
Main get Value=Main7
ThreadB get Value=ThreadB8
Main get Value=Main8
ThreadA get Value=ThreadA8
Main get Value=Main9
ThreadA get Value=ThreadA9
ThreadB get Value=ThreadB9
Main get Value=Main10
ThreadB get Value=ThreadB10
ThreadA get Value=ThreadA10

由結果可以看到有雖然有3個執行緒set()資料值但是每個執行緒都能取出屬於自己的資料

3.4,類InheritableThreadLocal的使用

使用InheritableThreadLocal類可以在子執行緒中取得父執行緒繼承下來的值
如果在子執行緒取得值的同時,主執行緒將InheritableThreadLocal中的值進行更改,那麼子執行緒取到的值還是舊值