1. 程式人生 > >Thinking in java:多執行緒

Thinking in java:多執行緒

關於多執行緒,看了許多大佬的帖子,自己也思索了很久,雖然許多地方還是不清楚,但還是有必要記錄一下自己的所得。

首先貼上狀態圖:

(1)New:建立一個執行緒時,執行緒進入這個狀態

(2)Runnable:呼叫start()後,進入這個狀態

(3)Running:執行run()時,進入這個狀態

(4)Blocked:阻塞狀態,分3種情況

  • 等待阻塞:呼叫wait()後進入等待阻塞。
  • 同步阻塞:當多個執行緒同時執行某一物件的同步程式碼塊時,只有一個執行緒能執行,其餘執行緒進入阻塞狀態。
  • 其他阻塞:呼叫sleep進入"睡眠“,join()另一個執行緒先執行,或有I/O請求進入其他阻塞。

一.建立執行緒的3種形式

(1)直接繼承Thread建立

public class Test
{
    public static void main(String[] args)
    {
        MyThread mt=new MyThread();
        mt.start();
    }
}
class MyThread extends Thread
{
    @Override
    public void run() {
        System.out.println("這是一個新執行緒");
    }
}

(2)實現Runnable介面

public class Test
{
    public static void main(String[] args)
    {
        MyThread mt=new MyThread();
        Thread t=new Thread(mt);
        t.start();
    }
}
class MyThread implements Runnable
{
    @Override
    public void run() {
        System.out.println("這是一個新執行緒");
    }
}

(3)實現Callable介面,用FutureTask進行封裝

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test
{
    public static void main(String[] args)
    {
        Callable mt=new MyThread();
        FutureTask ft=new FutureTask(mt);
        Thread t=new Thread(ft);
        t.start();
        try {
            System.out.println(ft.get());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
class MyThread implements Callable
{
    @Override
    public String call() {
        System.out.println("這是一個新執行緒");
        return "測試結束";
    }
}

在這種實現方式中,執行緒可以具備返回值。

二.執行緒的三種阻塞狀態

在將阻塞之前,有必要了解一下synchronized關鍵字的意思,用synchronized修飾的程式碼塊代表著執行緒同步,假如一個類A有3個同步方法,有一物件a是A的例項,如果有一執行緒正在執行物件a的某一個同步方法,那麼其餘任何執行緒都不能執行物件a的同步方法。專業點說就是當前執行的執行緒獲得了物件a的"同步鎖",其他執行緒要去執行a的同步方法必須先去獲得"同步鎖",但發現”同步鎖“沒有釋放,因此只能等著當前執行的執行緒執行完並且釋放”同步鎖“。

(1)sleep()阻塞,join()阻塞和I/O阻塞

sleep()方法使執行緒進入"睡眠"狀態,但在睡眠狀態中不會釋放同步鎖,示例:

public class Test
{
    public static void main(String[] args) throws InterruptedException
    {
        int i=0;
        ClassTest ct=new ClassTest();
        ThreadOne t1=new ThreadOne(ct);
        Thread.sleep(1000);
        i++;
        ThreadTwo t2=new ThreadTwo(ct);
        while (true)
        {
            Thread.sleep(1000);
            System.out.println(++i);
        }
    }
}
class ThreadOne extends Thread
{
    public ClassTest ct;
    public ThreadOne(ClassTest ct)
    {
        this.ct=ct;
        start();
    }
    @Override
    public void run() {
        try {
            ct.printOne();
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class ThreadTwo extends Thread
{
    public ClassTest ct;
    public ThreadTwo(ClassTest ct)
    {
        this.ct=ct;
        start();
    }

    @Override
    public void run() {
        ct.printTwo();
    }
}
class ClassTest
{
    public synchronized void printOne() throws InterruptedException
    {
        Thread.sleep(5000);
        System.out.println("--------");
    }
    public synchronized void printTwo()
    {
        System.out.println("++++++++");
    }
}

輸出如下:

在這個程式碼中,用兩個執行緒t1和t2去分別執行ct物件的printOne()和printTwo()方法,t2比t1後1秒執行,結果是在第5秒時,兩個依次輸出,在printTwo()中,沒有設定執行緒"睡眠"但依舊等到printOne()輸出後才輸出,證明sleep()不會釋放同步鎖。

join()方法的作用是等待另一執行緒執行完,在執行自身執行緒。示例:

public class Test
{
    public static void main(String[] args) throws InterruptedException
    {
        ThreadOne t1=new ThreadOne();
        t1.start();
        t1.join();
        while (true)
        {
            System.out.println(0);
        }
    }
}
class ThreadOne extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }
}

輸出如下:

截圖不能截完,自行測試,當執行緒t1join()之後,直到t1執行完才開始執行主執行緒,主執行緒阻塞,相當於t1執行緒與主執行緒變成了順序執行。

I/O阻塞直接看示例

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

public class Test
{
    public static void main(String[] args)
    {
        try {
            PipedInputStream in = new PipedInputStream();
            PipedOutputStream out = new PipedOutputStream(in);
            ThreadIn read = new ThreadIn(in);
            ThreadOut write = new ThreadOut(out);
            write.start();
            read.start();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
class ThreadIn extends Thread
{
    PipedInputStream in;
    public ThreadIn(PipedInputStream in)
    {
        this.in=in;
    }

    @Override
    public void run() {
        try {
            while (true) {
                in.read(new byte[1024]);
                System.out.println("讀一次");
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
class ThreadOut extends Thread
{
    PipedOutputStream out;
    public ThreadOut(PipedOutputStream out)
    {
        this.out=out;
    }

    @Override
    public void run() {
        try {
            while (true)
            {
                out.write("abcde".getBytes());
                sleep(2000);
                System.out.println("等待2秒");
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

輸出如下:

"讀執行緒"每次都要等"寫執行緒"寫入資料之後才讀取,當沒有資料讀取的時候就阻塞。

(2)等待阻塞與同步阻塞

等待阻塞是由wait()引發的,wait()的作用是將當前執行緒掛起,和sleep()作用相似,但wait()會釋放同步鎖,當wait()掛起時,其餘執行緒也可以獲得當前物件的”同步鎖“,notify和notifyAll可以解除由wait()掛起的執行緒,它們都必須存在於synchronized塊中,且必須作用於同一物件。示例如下:

import java.io.IOException;

public class Test
{
    public static void main(String[] args)
    {
        try {
            ClassTest ct = new ClassTest();
            ThreadOne t1 = new ThreadOne(ct);
            ThreadTwo t2 = new ThreadTwo(ct);
            t1.start();
            Thread.sleep(1000);
            t2.start();
            while (true)
            {
                int ch=System.in.read();
                if(ch=='a')
                {
                    System.out.println("按下A鍵,改變ct.blag=true");
                    ct.blag=true;
                }
                if(ch=='b')
                {
                    System.out.println("按下B鍵,改變ct.blag=false");
                    ct.blag=false;
                    Thread.sleep(1000);
                    System.out.println(ct.blag);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
class ThreadOne extends Thread
{
    ClassTest ct;
    public ThreadOne(ClassTest ct)
    {
        this.ct=ct;
    }

    @Override
    public void run() {
        ct.print();
    }
}
class ThreadTwo extends Thread
{
    ClassTest ct;
    public ThreadTwo(ClassTest ct)
    {
        this.ct=ct;
    }

    @Override
    public void run() {
        ct.waitPrint();
    }
}
class ClassTest
{
    volatile boolean blag=false;
    public synchronized void print()
    {
        int i=0;
        System.out.println("print佔用");
        try {
            while (true) {
                while (!blag) {
                    Thread.sleep(3000);
                    System.out.println(i++);
                }
                System.out.println("print釋放同步鎖");
                wait();
                System.out.println("結束wait");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public synchronized void waitPrint()
    {
        System.out.println("waitPrint獲得同步鎖");
        while (blag) {}
        System.out.println("waitPrint結束");
        notify();
    }
}

輸出如下:

在這個測試用例裡面,t1執行緒最開始獲得同步鎖,並每隔3秒輸出一個i值,t2執行緒阻塞,等待t1執行完,主執行緒改變blag狀態使得t1執行緒和t2執行緒可以釋放同步鎖。等待阻塞就是當執行緒呼叫wait方法後,自身會"掛起",進入阻塞狀態,但會釋放同步鎖,此例中t1在"掛起"後t2可以呼叫ct物件的同步方法。同步阻塞即多個執行緒同時呼叫一個物件的同步方法,只有一個執行緒能執行,此例中t1先呼叫print,t2只有等t1釋放同步鎖後才能執行。

注意:blag必須宣告為volatile,每個執行緒都有自己的快取(執行緒棧),它們從主存拿取資料,如果不宣告為volatile,即便主執行緒改變了blag的值,t2執行緒使用的也可能時自己執行緒本身快取裡面的blag值。