線程篇


1、概述
    線程的前提是有進程,因此說線程以前的瞭解進程的概念及其與線程的聯繫。
進程:是一個正在執行中的程序。每個進程執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元。
線程:是進程中的一個獨立的控制單元,線程在控制着進程的執行。一個進程中至少有一個線程,每一個獨立線程表明一個獨立操做。線程隸屬於某個進程,它自身沒有入口和出口;也不能自動運行,要由進程啓動執行,進行控制。
二者的區別:
    1)進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存。
    2)線程都有本身默認的名稱。Thread-編號,該編號從 0 開始。

多線程:一個進程中有多個線程,稱爲多線程。例如:虛擬機啓動的時候就是多線程,JVM 啓動時至少有一個主線程和一個負責垃圾回收的線程。
主線程:在 JVM 啓動時會有一個進程 Java.exe。該進程中至少一個線程負責 Java程序的執行,而這個線程運行的代碼存在於main 方法中,則該線程稱爲主線程。
多線程的意義:多個程序同時執行,從而提升程序運行效率。
線程的弊端:線程太多會致使效率的下降,由於線程的執行依靠的是 CPU 的來回切換。
多線程原理:
    當運行多線程程序時發現運行結果每次都不一樣。由於多個線程都在獲取CPU的執行權。CPU執行到誰,誰就運行。明確一點,在某一時刻,只能有一個程序在運行。(多核除外)CPU在作着快速的切換,以達到看上去是同時運行的效果。咱們能夠形象把多線程運行行爲看作在互相搶奪CPU的執行權。這就是多線程的一個特性,隨機性。誰搶到誰執行,至於執行多長時間,CPU說了算。後期能夠控制,可是比較困難。系統能夠同時執行多個任務,應用程序內的多任務並行就是依靠多線程實現的。
2、線程建立
建立線程有兩種方式:繼承Thread類、實現Runnable接口。
一、繼承Thread類:
步驟:
    1)定義類繼承 Thread。
    2)複寫 Thread 中的 run()方法(將線程要運行的代碼存放在該 run()方法中) 。
    3)調用線程的 start()方法啓動線程,從而調用 run()方法。
Note:
    Thread 類用於描述線程。該類定義了一個功能,用於存儲線程要運行的代碼。該存儲功能就是 run()方法。所以,run()方法的目的就是將自定義的代碼存儲在 run()方法,讓線程運行。在 main 方法中調用 start()方法做用是開啓線程並執行線程的 run ()方法。而在 main 方法中直接調用 run ()方法,僅僅是對象調用方法,建立了線程,並無運行。
二、實現Runnable接口:
步驟:
    1)定義類實現 Runnable 接口。
    2)覆蓋 Runnable 接口中的 run()方法(將線程要運行的代碼存放在該 run()方法中 )。
    3)經過 Thread 類創建線程對象。
    4)將 Runnable 接口的子類對象做爲實際參數傳遞給 Thread 類的構造函數。
    5)調用 Thread 類的 start 方法開啓線程從而調用 Runnable 接口子類的 run()方法。
Note:
    建立(聲明)一個實現 Runnable 接口的類對象。類必須定義一個稱爲 run 的無參方法。此外 Runnable 爲 Thread 的子類的類提供了一種激活方式。而後該類實現 run()方法,能夠分配該類的實例,在建立 Thread 時做爲一個參數來傳遞並啓動。
下面演示兩種方式建立線程:
java

class Demo extends Thread {
    public void run() {
        for (int i = 0; i < 60; i++) {
            System.out.println(Thread.currentThread().getName() + "::Demo run ---");
        }
    }
}

class Demo1 implements Runnable {
    public void run() {
        for (int i = 0; i < 60; i++) {
            System.out.println(Thread.currentThread().getName() + "::Demo1 run ---");
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        Demo d = new Demo();
        d.start();
        
        Demo1 d1 = new Demo1();
        new Thread(d1).start();;

        for (int i = 0; i < 60; i++) {
            System.out.println(Thread.currentThread().getName() + "::Main run ---");
        }
    }
}

結果以下所示: 安全


三、兩種建立方式的區別
    繼承Thread:線程代碼存放在Thread子類run()方法中。
    實現Runnable:線程代碼存放在接口子類run()方法中。    
    實現方式的好處:避免了單繼承的侷限性。定義線程時,建議使用實現方式。
四、線程運行狀態
    被建立:等待啓動,調用start啓動。
    運行狀態:具備執行資格和執行權。
    臨時狀態(阻塞):有執行資格,可是沒有執行權。
    凍結狀態:遇到sleep(time)方法和wait()方法時,失去執行資格和執行權,sleep()方法時間到或者調用notify()方法時,獲得執行資格,變爲臨時狀態。
    消忙狀態:stop()方法,或者run()方法結束。
圖解以下: 多線程


3、線程同步機制
一、多線程的安全問題緣由:當多條語句在操做同一線程共享數據時,一個線程對多條語句只執行了一部分,尚未執行完, 另外一個線程參與進來執行。致使共享數據的錯誤。
二、解決辦法:對多條操做共享數據的語句,只能讓一個線程執行完。在執行過程當中,其餘線程不能夠參與執行———同步。
Note:
    同步的前提:必需要有兩個或者兩個以上的線程,必須是多個線程使用同一個鎖,必須保證同步中只能有一個線程在運行。
    同步優勢:解決了多線程的安全問題。
    同步弊端:多個線程須要判斷鎖,較爲消耗資源。
在多線程操做共享數據的運行代碼中,須要加鎖的兩種狀況:
    1)含有選擇判斷語句
    2)含 try(){}catch(){}語句
理解:對象如同鎖,持有鎖的線程能夠在同步中執行。沒有持有鎖的線程即便獲取CPU 執行權,也進不去,由於沒有獲取鎖。Java 對於多線程的安全問題提供了專業的解決方式:同步代碼塊、同步函數。
三、同步代碼塊:
格式:
    synchronized(對象)
    {
          //須要被同步的代碼
    }
示例: 函數

/*
 * 建立4個線程同時賣100張票
 * 
 */
class Ticket implements Runnable {
	private int num = 100;

	public void run() {
		while (true) {
			//同步代碼塊,利用this做爲鎖,或者用Class對象做爲鎖,只要保證鎖惟一便可
			synchronized (this) {
				if (num > 0) {
					try {
						//延時是爲了讓4個線程執行權趨於均衡
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("Ticket" + num--);
				}
			}
		}
	}
}
public class ThreadDemo3 {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		Thread t1 = new Thread(ticket);
		Thread t2 = new Thread(ticket);
		Thread t3 = new Thread(ticket);
		Thread t4 = new Thread(ticket);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

四、同步函數
格式:在函數上加上synchronized修飾符便可。
Note:那麼同步函數用的是哪個鎖呢?
    函數須要被對象調用。那麼函數都有一個所屬對象引用,就是this,因此同步函數使用的鎖是this
示例: this

/*
 * 建立4個線程同時賣100張票
 * 
 */

class Ticket implements Runnable {
	private int num = 100;

	public synchronized void run() {
		while (true) {
			// 同步代碼塊,利用this做爲鎖,或者用Class對象做爲鎖,只要保證鎖惟一便可
			// synchronized (this) {
			if (num > 0) {
				try {
					// 延時是爲了讓4個線程執行權趨於均衡
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("Ticket" + num--);
			}
			// }
		}
	}
}

public class ThreadDemo3 {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		Thread t1 = new Thread(ticket);
		Thread t2 = new Thread(ticket);
		Thread t3 = new Thread(ticket);
		Thread t4 = new Thread(ticket);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

}
上述兩個示例結果都會隨着運行而改變,可是基本完成100張車票的售賣,結果以下:

五、靜態函數的同步
    若是同步函數被靜態修飾後,使用的鎖是什麼呢?
    經過驗證,發現不在是this。由於靜態方法中也不能夠定義this。靜態進內存時,內存中沒有本類對象,可是必定有該類對應的字節碼文件對象。如:類名.class 該對象的類型是Class這就是靜態函數所使用的鎖。而靜態的同步方法,使用的鎖是該方法所在類的字節碼文件對象——類名.class
示例: spa

class Single {
	private static Single s = null;

	private Single() {
	}

	public static Single getInstance() {
		//解決效率問題
		if (s == null) {
			//加鎖
			synchronized (Single.class) {
				//判斷s是否有非空指向
				if (s == null) {
					s = new Single();
				}
			}
		}
		return s;
	}
}
六、死鎖
多線程程序中,當同步中嵌套同步時,就有可能出現死鎖現象。
示例:
class DeadLock implements Runnable {
	private boolean flag;

	DeadLock(boolean flag) {
		this.flag = flag;
	}

	public void run() {
		if (flag) {
			synchronized (MyLock.locka) {
				System.out.println("...if locka");
				synchronized (MyLock.lockb) {
					System.out.println("...else lockb");
				}
			}
		} else {
			synchronized (MyLock.lockb) {
				System.out.println("...else lockb");
				synchronized (MyLock.locka) {
					System.out.println("...else locka");
				}
			}
		}
	}
}
//自定義鎖類
class MyLock {
	static MyLock locka = new MyLock();
	static MyLock lockb = new MyLock();
}

public class ThreadDemo7 {

	public static void main(String[] args) {

		DeadLock dlt = new DeadLock(true);
		DeadLock dlf = new DeadLock(false);

		Thread t1 = new Thread(dlt);
		Thread t2 = new Thread(dlf);

		t1.start();
		t2.start();
	}
}
結果以下所示:

4、線程間通訊
其實就是多個線程在操做同一個資源,可是操做的動做不一樣。
示例:
線程

/*
 * 練習:一個線程存取姓名和性別,另外一個線程打印出該兩項
 * 
 */

//資源
class Res {
	String name;
	String sex;
	boolean flag;//判斷資源是否存在的標識符

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}

}
//存放線程
class Input implements Runnable {
	Res r = new Res();

	Input(Res r) {
		this.r = r;
	}

	public void run() {
		int x = 0;
		while (true) {
			synchronized (r) {
				//判斷有資源就等待
				if (r.flag) {
					try {
						r.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				//交替存入caven和琴琴這兩我的的信息
				if (x == 0) {
					r.name = "caven";
					r.sex = "man";
				} else {
					r.name = "琴琴";
					r.sex = "女女女女女";
				}
				x = (x + 1) % 2;
				r.flag = true;
				r.notify();//喚醒其餘線程
			}
		}
	}
}
//取出線程
class Output implements Runnable {
	private Res r;

	Output(Res r) {
		this.r = r;
	}

	public void run() {
		while (true) {
			synchronized (r) {
				//判斷資源爲空就等待
				if (!r.flag) {
					try {
						r.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println(r.name + "...." + r.sex);
				r.flag = false;
				r.notify();
			}
		}
	}
}

public class ThreadDemo1 {

	public static void main(String[] args) {

		Res r = new Res();

		Input in = new Input(r);
		Output out = new Output(r);

		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);

		t1.start();
		t2.start();
	}

}
部分結果以下所示:

5、等待喚醒機制
一、緣由:由於要對持有監視器(鎖)的線程操做,因此要使用在同步中,由於只有同步才具備鎖。
二、說明:Object 中的方法 notify()、notifyAll()、wait()等都在同步中使用。爲何這些操做線程的方法要定義 Object 類中呢?
    答:由於這些方法在操做同步中線程時,都必需要標識它們所操做線程只有的鎖,只有同一個鎖上的被等待線程,能夠被同一個鎖上 notify 喚醒。不能夠對不一樣鎖中的線程進行喚醒。 等待和喚醒必須是同一個鎖。而鎖能夠是任意對象,因此能夠被任意對象調用的方法定義 Object 類中。
三、wait():在其餘線程調用此對象的notify()方法或notifyAll()方法前,致使當前線程等待。換句話說,此方法的行爲就好像它僅執行wait(0)調用同樣。當前線程必須擁有此對象鎖(監視器)。該線程發佈對此監視器的全部權並等待,直到其餘線程經過調用notify()方法,或notifyAll()方法通知在此對象的監視器上等待的線程醒來。而後該線程將等到從新得到對監視器的全部權後才能繼續執行。
Note:
    wait()方法在使用時只能 try(處理異常)不能拋異常。當出現多個線程同時操做一個對象時時,要用 while 循環和 notifyAll();比較通用的方式。定義while判斷標記是爲了讓被喚醒的線程再一次判斷標記。定義 notifyAll(), 是由於須要喚醒對方線程。只用 notify(),容易出現只喚醒本方線程的狀況。致使程序中的全部線程都等待。 code

6、JDK5.0 升級版線程功能
一、Lock 接口:
    實現提供了比使用 synchronized 方法和語句可得到的更普遍的鎖定操做。此實現容許更靈活的結構, 能夠具備差異很大的屬性, 能夠支持多個相關的 Condition對象(能夠理解爲 lock 替代了 synchronized)。
二、ReentrantLock類:
    Lock 的子類。一個可重入的互斥鎖,它具備與使用  synchronized  方法和語句所訪問的隱式監視器鎖相同的一些基本行爲和語義,但功能更強大。
Note:JDK1.5  中提供了多線程升級解決方案:顯示的鎖機制,以及顯示的鎖等待喚醒機制。
    1)將同步 Synchronized 替換成現實 Lock 操做。
    2)將 Object 中的 wait,notify notifyAll,替換了 Condition 對象。該對象能夠 Lock 鎖進行獲取。
    3)釋放鎖的動做必定要執行。
JDK5.0新特型改寫後的生產者消費者案例:
對象

/*
 * JDK5.0 升級後的生產者消費者案例演示
 */
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//資源
class Resources {
	private String name;
	private int count = 1;
	private boolean flag = false;

	<span><span class="comment">//建立兩Condition對象,分別來控制等待或喚醒本方和對方線程</span><span> </span></span>
	private Lock lock = new ReentrantLock();
	private Condition conSet = lock.newCondition();
	private Condition conOut = lock.newCondition();

	public/* synchronized */void set(String name) {
		lock.lock();//上鎖
		try {
			while (flag) {
				try {
					/<span><span class="comment">/本方等待</span><span>  </span></span>
					conSet.await();
					/* wait(); */
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			this.name = name + "---" + count++;
			System.out.println(Thread.currentThread().getName() + "生產者"
					+ this.name);
			flag = true;
			conOut.signal();// <span><span class="comment">//喚醒對方</span><span>  </span></span>
			/* this.notifyAll(); */
		} finally {
			lock.unlock();//解鎖動做,必定要執行
		}
	}

	public/* synchronized */void out() {
		lock.lock();
		try {
			while (!flag) {
				try {
					conOut.await();
					/* wait(); */
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "--消費者--"
						+ this.name);
				flag = false;
				conSet.signal();
				/* this.notifyAll(); */
			}
		} finally {
			lock.unlock();
		}
	}
}

// 生產者
class Producers implements Runnable {
	private Resources res;

	public Producers(Resources res) {
		this.res = res;
	}

	public void run() {
		while (true) {
			res.set("商品");
		}
	}
}

// 消費者
class Comsumers implements Runnable {
	private Resources res;

	public Comsumers(Resources res) {
		this.res = res;
	}

	public void run() {
		while (true) {
			res.out();
		}
	}
}

public class ThreadDemo3 {
	public static void main(String[] args) {

		Resources res = new Resources();

		Producers pro = new Producers(res);
		Comsumers con = new Comsumers(res);

		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);

		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
部分結果以下所示:

三、中止線程(JDK5.0以後)
原理:只有讓run()方法結束。
兩種方式:
    1)定義循環結束標記:由於線程運行代碼通常都是循環,只要控制了循環,就能夠結束 run(),也就線程結束。
    2)使用 interrupt(中斷)方法:該方法結束線程的凍結狀態,使線程回到運行狀態中來。
Note:stop()方法已過期,再也不使用。
示例: 繼承

class StopThread implements Runnable {
	private boolean flag = true;

	// 改變flag
	public void changeFlag(boolean flag) {
		this.flag = flag;
	}

	public synchronized void run() {
		int i = 0;
		// 利用flag來標記線程是否繼續
		// 特殊狀況:
		// 當線程處於凍結狀態的時候就不會讀取到標記,即線程結束不了
		while (flag) {
			try {
				wait();
			} catch (Exception e) {
				System.out.println(Thread.currentThread().getName()
						+ "Exception...");
				changeFlag(false);
			}
			System.out.println(Thread.currentThread().getName() + "...." + i++);
		}
	}
}
public class ThreadDemo4 {

	public static void main(String[] args) {
		StopThread st = new StopThread();

		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);

		t1.start();
		t2.start();

		int num = 0;
		while (true) {
			if (num++ == 60) {
				st.changeFlag(false);
				// 利用interrupt來結束線程
				t1.interrupt();
				t2.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName() + "...." + num);
		}
	}
}
結果以下所示:

7、其餘方法
一、守護線程
    setDacmon(Boolean on)    //將該線程標記爲守護線程或用戶線程。
Note:Thread 類該方法必須在啓動線程前調用,當正在運行的線程都是守護線程時,java 虛擬機退出。該方法首先調用 checkAccess()方法,且不帶任何參數,可能拋出SecurityException(在當前線程中)。主線程爲前臺線程,守護線程能夠理解爲後臺線程。後臺線程特色:開啓後,與前臺線程共同搶劫 CPU 的執行權運行。當全部的前臺線程結束後,後臺線程會自動結束。
二、join()方法
1)概述:Thread 類的final修飾的方法,等待線程終止。該方法拋異常 InterruptedException搶奪 CPU 執行權(主線程要等待 join ()執行的線程執行結束,再恢復繼續運行)。通常臨時加入線程時,使用 join()。
2)特色:當 A 線程執行到了 B 線程的.join()方法時,A 就會等待。等 B 線程都執行完,A纔會執行。join 能夠用來臨時加入線程執行。
三、優先級和 yield()方法
在Thread類覆蓋了toString()方法。
    String toString()    //返回該線程的字符串表現形式。 包括線程名稱、優先級和線程組。
    static void yield()  //暫停當前正在執行的線程對象,並執行其餘線程。減小線程的執行頻率。
ThreadGroup 類:線程組。表示一個線程的集合。
優先級:表明搶資源的頻率。範圍:1——10。只有 1(MIN_PRIORITY)、5(NORM_PRIORITY)、10(MAX_PRIORITY)最明顯。全部線程(包括主線程)默認優先級是 5。
    setPriorty(int newPriority)    //更改線程的優先級。
    eg:   t1.setPriority(Thread.MAX_PRIORITY);    //設置 t1 的線程優先級爲 10

   本篇幅所描述的僅表明我的見解,若有出入請諒解。

------  Java培訓Android培訓IOS培訓.Net培訓、期待與您交流! ------

相關文章
相關標籤/搜索