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值。