1. 程式人生 > >【Java併發程式設計】深入分析Thread(七)

【Java併發程式設計】深入分析Thread(七)

一、執行緒

   1.1什麼是執行緒?

    執行緒,有時被稱為輕量級程序(Lightweight Process,LWP),是程式執行流的最小單元。一個標準的執行緒由執行緒ID,當前指令指標(PC),暫存器集合和堆疊組成。另外,執行緒是程序中的一個實體,是被系統獨立排程和分派的基本單位,執行緒自己不擁有系統資源,只擁有一點兒在執行中必不可少的資源,但它可與同屬一個程序的其它執行緒共享程序所擁有的全部資源。一個執行緒可以建立和撤消另一個執行緒,同一程序中的多個執行緒之間可以併發執行。由於執行緒之間的相互制約,致使執行緒在執行中呈現出間斷性。執行緒也有就緒、阻塞和執行三種基本狀態。就緒狀態是指執行緒具備執行的所有條件,邏輯上可以執行,在等待處理機;執行狀態是指執行緒佔有處理機正在執行;阻塞狀態是指執行緒在等待一個事件(如某個訊號量),邏輯上不可執行。每一個程式都至少有一個執行緒,若程式只有一個執行緒,那就是程式本身。
  執行緒是程式中一個單一的順序控制流程。程序內一個相對獨立的、可排程的執行單元,是系統獨立排程和分派CPU的基本單位指執行中的程式的排程單位。在單個程式中同時執行多個執行緒完成不同的工作,稱為多執行緒。       以上摘自---百度百科。

   1.2 什麼是執行緒安全性?

    如果一個類可以安全地被多個執行緒使用,它就是執行緒安全的。你無法對此論述提出任何爭議,但也無法從中得到更多有意義的幫助。那麼我們如何辨別執行緒安全與非執行緒安全的類?我們甚至又該如何理解“安全”呢?任何一個合理的“執行緒安全性”定義,其關鍵在於“正確性”的概念。在<<JAVA併發程式設計實踐>>書中作者是這樣定義的:一個類是是執行緒安全的,是指在被多個執行緒訪問時,類可以持續進行正確的行為。或當多個執行緒訪問一個類時,如果不用考慮這些執行緒在執行時環境下的排程和交替執行,並且不需要額外的同步及在呼叫方程式碼不必作其他的協調,這個類的行為仍然是正確的,那麼稱這個類是執行緒安全的。

   1.3 執行緒的六種狀態

 執行緒從建立到銷燬期間有六種狀態:

  • New: 至今尚未啟動的執行緒的狀態。 
  • Runnable :可執行執行緒的執行緒狀態。
  • Blocked :受阻塞並且正在等待監視器鎖的某一執行緒的執行緒狀態。
  • Waiting :某一等待執行緒的執行緒狀態。
  • Timed_waiting:具有指定等待時間的某一等待執行緒的執行緒狀態。
  • Terminated:已終止執行緒的執行緒狀態。執行緒已經結束執行。
  • 如下圖所示:

二、Thread類

   2.1 屬性

//執行緒名字,通過構造引數來指定
private char        name[];
//表示執行緒的優先順序,優先順序越高,越優先被執行(最大值為10,最小值為1,預設值為5)
private int         priority;

private Thread      threadQ;
private long        eetop;

/* Whether or not to single_step this thread. */
private boolean     single_step;

//執行緒是否是守護執行緒:當所有非守護程序結束或死亡後,程式將停止 
private boolean     daemon = false;

/* JVM state */
private boolean     stillborn = false;

//將要執行的任務
private Runnable target;

/* 執行緒組表示一個執行緒的集合。此外,執行緒組也可以包含其他執行緒組。執行緒組構成一棵樹,在樹中,除了初始執行緒組外,每個執行緒組都有一個父執行緒組。  */
private ThreadGroup group;

/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;

/* The inherited AccessControlContext of this thread */
private AccessControlContext inheritedAccessControlContext;

/*第幾個執行緒,在init初始化執行緒的時候用來賦給thread.name */  
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

/*
 * InheritableThreadLocal values pertaining to this thread. This map is
 * maintained by the InheritableThreadLocal class.
 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

/*
 * The requested stack size for this thread, or 0 if the creator did
 * not specify a stack size.  It is up to the VM to do whatever it
 * likes with this number; some VMs will ignore it.
 */
private long stackSize;

/*
 * JVM-private state that persists after native thread termination.
 */
private long nativeParkEventPointer;

/*
 * Thread ID
 */
private long tid;

/* For generating thread ID */
private static long threadSeqNumber;

/*
 *執行緒從建立到最終的消亡,要經歷若干個狀態。
 *一般來說,執行緒包括以下這幾個狀態:建立(new)、就緒(runnable)、執行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)。
 * 
 */
private volatile int threadStatus = 0;

   2.3 start()操作

//啟動新建立的執行緒
public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
	/*這個方法不會被主執行緒呼叫或通過虛擬機器系統執行緒組建立起來。未來任何新增到該方法裡的新功能可能需要加入到虛擬機器中
	 * 
	 * 狀態new的值是0.
	 * */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    /* 通知執行緒組新執行緒將要啟動,以便它可以新增到執行緒組列表並且執行緒組沒有開始計數*/
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

   2.3 run()操作

public void run() {
       if (target != null) {
            target.run();
       }
 }

   2.4 start()和run()之間有什麼區別?

   2.4.1 程式碼示例:run()方法使用

package com.game.thread;

/**
 * 
 * @author liulongling
 *
 */
public class ThreadTest extends Thread{

	public ThreadTest(String name) {
		super.setName(name);
	}

	@Override
	public void run() {
		for(int i = 0; i < 5;i++)
		{
			System.out.println(super.getName()+":"+i);
		}
	}

	public static void main(String[] args) {
		ThreadTest test = new ThreadTest("A");
		ThreadTest test1 = new ThreadTest("B");
		
		test.run();
		test1.run();
		
		if(Thread.activeCount()>=1)
		{
			Thread.yield();
		}
	}
}
控制檯:
+------------------------------------------------------------------+

A:0 A:1 A:2 A:3 A:4 B:0 B:1 B:2 B:3 B:4

+------------------------------------------------------------------+

   2.4.2 程式碼示例:start()方法使用

package com.game.thread;

/**
 * 
 * @author liulongling
 *
 */
public class ThreadTest extends Thread{

	public ThreadTest(String name) {
		super.setName(name);
	}

	@Override
	public void run() {
		for(int i = 0; i < 5;i++)
		{
			System.out.println(super.getName()+":"+i);
		}
	}

	public static void main(String[] args) {
		ThreadTest test = new ThreadTest("A");
		ThreadTest test1 = new ThreadTest("B");
		
		test.start();
		test1.start();
		
		if(Thread.activeCount()>=1)
		{
			Thread.yield();
		}
	}
}
控制檯:
+------------------------------------------------------------------+

B:0 A:0 B:1 A:1 B:2 A:2 B:3 A:3 B:4 A:4

+------------------------------------------------------------------+

   2.4.3 結果分析

   以上結果可以發現run()和單執行緒一樣每次只能執行一個執行緒,而start()不一樣有多個執行緒交叉執行著。我們知道start()方法被用來啟動新建立的執行緒,而且start()內部通過本地系統呼叫了run()方法,這和直接呼叫run()方法的效果不一樣。當你呼叫run()方法的時候,只會是在原來的執行緒中呼叫,沒有新的執行緒啟動,start()方法會啟動新的執行緒,其中多個執行緒在CPU中是支援併發執行的。那麼有沒有什麼方法可以讓A執行緒優先執行呢?

   2.5 setPriority()操作

 public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }
 private native void setPriority0(int newPriority);
   setPriority使用了native關鍵字,在Java API中,一個native方法意味著這個方法沒有使用Java語言實現,只能通過程式碼示例去分析它的原理。在程式碼中將執行緒A的執行優先順序設定為最高,同時執行緒B的優先順序設定為最低,那麼預期的結果應該是執行緒A先執行完後再執行執行緒B,程式碼如下:
package com.game.thread;

/**
 * 
 * @author liulongling
 *
 */
public class ThreadTest extends Thread{

	public ThreadTest(String name) {
		super.setName(name);
	}

	@Override
	public void run() {
		for(int i = 0; i < 5;i++)
		{
			System.out.println(super.getName()+":"+i);
		}
	}

	public static void main(String[] args) {
		ThreadTest test = new ThreadTest("A");
		ThreadTest test1 = new ThreadTest("B");
		//MAX_PRIORITY是最高優先順序
		test.setPriority(MAX_PRIORITY);
		
		test.setPriority(MIN_PRIORITY);
		test.start();
		test1.start();
		
		if(Thread.activeCount()>=1)
		{
			Thread.yield();
		}
	}
}
控制檯:
+------------------------------------------------------------------+
A:0
A:1
A:2
A:3
A:4
B:0
B:1
B:2
B:3
B:4
+------------------------------------------------------------------+

   2.5 sleep(long millis)操作

public static native void sleep(long millis) throws InterruptedException;

  sleep也是使用了native關鍵字,呼叫了底層方法。sleep是指執行緒被呼叫時,佔著CPU不工作,形象地說明為“佔著CPU睡覺”,此時,系統的CPU部分資源被佔用,其他執行緒無法進入。多執行緒下使用時需要注意的是sleep方法不會釋放鎖。比如:執行緒A和執行緒B執行一段加鎖程式碼,執行緒A先進去,執行緒B在外面等待,其中程式碼程式有sleep方法讓執行緒休眠,休眠後鎖並不會被釋放,執行緒B也只能繼續在外面等待直到休眠時間結束。程式碼如下:

package com.game.thread;

import java.io.IOException;

/**
 * 
 * @author liulongling
 *
 */
public class ThreadTest{

    private int i = 10;
    private Object object = new Object();
    
    MyThread thread1 = new MyThread("A");
    MyThread thread2 = new MyThread("B");
     
    public static void main(String[] args) throws IOException  {
    	ThreadTest test = new ThreadTest();
    	test.thread1.start();
    	test.thread2.start();
    } 
     
     
    class MyThread extends Thread{
    	public MyThread(String name) {
    		super.setName(name);
		}
        @Override
        public void run() {
            synchronized (object) {
                System.out.println(Thread.currentThread().getName()+":"+i++);
                try {
                    System.out.println("執行緒"+Thread.currentThread().getName()+"進入睡眠狀態");
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    // TODO: handle exception
                }
                System.out.println("執行緒"+Thread.currentThread().getName()+"被喚醒");
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
控制檯:
+------------------------------------------------------------------+

A:10

執行緒A進入睡眠狀態

執行緒A被喚醒

A:11

B:11

執行緒B進入睡眠狀態

執行緒B被喚醒

B:12

+------------------------------------------------------------------+

   2.6 yield()操作

   呼叫yield方法會讓當前執行緒交出CPU許可權,讓CPU去執行其他的執行緒。它跟sleep方法類似,同樣不會釋放鎖。但是yield不能控制具體的交出CPU的時間,另外,yield方法只能讓擁有相同優先順序的執行緒有獲取CPU執行時間的機會。

 注意,呼叫yield方法並不會讓執行緒進入阻塞狀態,而是讓執行緒重回就緒狀態,它只需要等待重新獲取CPU執行時間,這一點是和sleep方法不一樣的。

   2.7 isAlive()

    /**
     * Tests if this thread is alive. A thread is alive if it has
     * been started and has not yet died.
     *
     * @return  <code>true</code> if this thread is alive;
     *          <code>false</code> otherwise.
     */
    public final native boolean isAlive();
  表示執行緒當前是否為可用狀態,如果執行緒已經啟動,並且當前沒有任何異常的話,則返回true,否則為false

   2.8 join()操作

  join有3個過載方法:

   2.8.1 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;
		}
	}
}
package com.game.thread;

import java.io.IOException;

/**
 * 
 * @author liulongling
 *
 */
public class ThreadTest{

	private int i = 10;
	private Object object = new Object();

	MyThread thread1 = new MyThread("A");
	MyThread thread2 = new MyThread("B");

	public static void main(String[] args) throws IOException  {
		ThreadTest test = new ThreadTest();
		test.thread1.start();
		System.out.println("執行緒"+Thread.currentThread().getName()+"等待");
		try {
			test.thread1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("執行緒"+Thread.currentThread().getName()+"執行");
		for (int j = 0; j < 5; j++)
		{
			System.out.println(Thread.currentThread().getName() + ":" + j);
		}
	} 


	class MyThread extends Thread{
		public MyThread(String name) {
			super.setName(name);
		}
		@Override
		public void run() {
			synchronized (object) {
				System.out.println(Thread.currentThread().getName()+":"+i++);
				try {
					System.out.println("執行緒"+Thread.currentThread().getName()+"進入睡眠狀態");
					Thread.currentThread().sleep(1000);
				} catch (InterruptedException e) {
					// TODO: handle exception
				}
				System.out.println("執行緒"+Thread.currentThread().getName()+"被喚醒");
				System.out.println(Thread.currentThread().getName()+":"+i);
			}
		}
	}
}
控制檯:
+------------------------------------------------------------------+

執行緒main等待

A:10

執行緒A進入睡眠狀態

執行緒A被喚醒

A:11

執行緒main執行

main:0

main:1

main:2

main:3

main:4

  MSDN上解釋join無參方法其作用為:阻塞 “呼叫執行緒” 直到某個執行緒結束。從上面結果可以分析出,A執行緒其實在main執行緒上執行,我們可以說main執行緒呼叫了A執行緒或稱main執行緒為“呼叫執行緒”,A執行緒呼叫join()方法後將呼叫執行緒阻塞直到A執行緒結束後控制檯才輸出來main:0...。我們去掉join方法看下控制檯輸出結果。
控制檯:
+------------------------------------------------------------------+

main:0

main:1

A:10

main:2

執行緒A進入睡眠狀態

main:3

main:4

執行緒A被喚醒

A:11

+------------------------------------------------------------------+

   2.8.2 join(long millis)

  join(long millis)的引數作用是指定“呼叫執行緒”的最大阻塞時間。程式碼如下:

package com.game.thread;

import java.io.IOException;

/**
 * 
 * @author liulongling
 *
 */
public class ThreadTest{

	private int i = 10;
	private Object object = new Object();

	MyThread thread1 = new MyThread("A");
	MyThread thread2 = new MyThread("B");

	public static void main(String[] args) throws IOException  {
		ThreadTest test = new ThreadTest();
		test.thread1.start();
		System.out.println("執行緒"+Thread.currentThread().getName()+"等待");
		try {
			test.thread1.join(500);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("執行緒"+Thread.currentThread().getName()+"執行");
		for (int j = 0; j < 5; j++)
		{
			System.out.println(Thread.currentThread().getName() + ":" + j);
		}
	} 


	class MyThread extends Thread{
		public MyThread(String name) {
			super.setName(name);
		}
		@Override
		public void run() {
			synchronized (object) {
				System.out.println(Thread.currentThread().getName()+":"+i++);
				try {
					System.out.println("執行緒"+Thread.currentThread().getName()+"進入睡眠狀態");
					Thread.currentThread().sleep(1000);
				} catch (InterruptedException e) {
					// TODO: handle exception
				}
				System.out.println("執行緒"+Thread.currentThread().getName()+"被喚醒");
				System.out.println(Thread.currentThread().getName()+":"+i);
			}
		}
	}
}

執行緒main等待

A:10

執行緒A進入睡眠狀態

執行緒main執行

main:0

main:1

main:2

main:3

main:4

執行緒A被喚醒

A:11

+------------------------------------------------------------------+   從上面結果可以分析出,在A執行緒還未結束,主執行緒已經開始執行。原因是我們給主執行緒設定的阻塞時間是500ms,小於A執行緒run()方法裡的1000ms休眠時間。

   2.8.3 join(long millis, int nanos)

  阻塞呼叫執行緒的時間最長為 millis 毫秒 + nanos 納秒...這裡就不舉例說明了,原理和上一個方法一樣。

   2.9 interrupt()操作

  interrupt的作用是中斷正被阻塞的執行緒,比如我給某一執行緒休眠了10s時間,如果我在這個執行緒上呼叫了interrupt方法,看看會有什麼效果。程式碼如下:

package com.game.thread;

import java.io.IOException;

/**
 * 
 * @author liulongling
 *
 */
public class ThreadTest{

	private int i = 10;
	private Object object = new Object();

	MyThread thread1 = new MyThread("A");
	MyThread thread2 = new MyThread("B");

	public static void main(String[] args) throws IOException  {
		ThreadTest test = new ThreadTest();
		test.thread1.start();
		test.thread1.interrupt();
	} 


	class MyThread extends Thread{
		public MyThread(String name) {
			super.setName(name);
		}
		@Override
		public void run() {
			synchronized (object) {
				System.out.println(Thread.currentThread().getName()+":"+i++);
				try {
					System.out.println("執行緒"+Thread.currentThread().getName()+"進入睡眠狀態");
					Thread.currentThread().sleep(10000);
				} catch (InterruptedException e) {
					System.out.println("你被中斷了");
				}
				System.out.println("執行緒"+Thread.currentThread().getName()+"被喚醒");
				System.out.println(Thread.currentThread().getName()+":"+i);
			}
		}
	}
}
控制檯:
+------------------------------------------------------------------+

A:10

執行緒A進入睡眠狀態

你被中斷了

執行緒A被喚醒

A:11 +------------------------------------------------------------------+

 使用了 interrupt的結果是在休眠程式碼處丟擲一個異常,並且阻塞馬上停止了。

   3.0 setDaemon()操作

  用來設定執行緒是否成為守護執行緒和判斷執行緒是否是守護執行緒。
  守護執行緒和使用者執行緒的區別在於:守護執行緒依賴於建立它的執行緒,而使用者執行緒則不依賴。舉個簡單的例子:如果在main執行緒中建立了一個守護執行緒,當main方法執行完畢之後,守護執行緒也會隨著消亡。而使用者執行緒則不會,使用者執行緒會一直執行直到其執行完畢。在JVM中,像垃圾收集器執行緒就是守護執行緒。Thread類有一個比較常用的靜態方法currentThread()用來獲取當前執行緒。

參考資料

     本部落格中未標明轉載的文章歸作者小毛驢所有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。