天天看點

黑馬程式員——Java基礎---傳統多線程

-------  android教育訓練、java教育訓練、期待與您交流! ----------

一.了解程序和線程

1.在多任務系統中每個獨立執行的程式稱為程序,也就是“正在進行的程式”,我們現在使用的作業系統一般都是多任務的,即能夠同時執行多個應用程式,實際情況是作業系統負責對CPU等裝置的資源進行配置設定和管理,雖然這些裝置某一時刻隻能做一件事情,但已非常小的時間間隔交替執行多個程式,就可以給人以同時執行多個程式的感覺。

2.一個程序中又可以包含一個或多個線程,一個線程就是一個程式内部的一條執行線索,如果要一個程式中實作多段代碼同時交替運作,就需要産生多個線程,并指定每個線程上所要運作的程式代碼,這就是多線程。

3.多線程與單線程的對比

黑馬程式員——Java基礎---傳統多線程

二.用Thread類建立線程

1.要将一段代碼在一個新的線程上運作,該代碼應該在一個類的run函數中,并且run函數所在的類是Thread類的子類。倒過來看,子類要覆寫Thread類中的run函數,在子類的run函數中調用想在新的線程上運作的子類對象的run方法。

2.啟動一個新的線程,我們不是直接調用Thread類的子類對象的run方法,而是調用Thread子類對象的start方法(從Thread類繼承到的);Thread類對象的start方法将産生一個新的線程,并在該線程上運作該Thread類對象中的run方法,根據面向對象的運作時的多态性,該線程上世紀運作的是Thread子類(也就是我們寫的那個類)對象中的run方法。

3.由于線程的代碼在run方法中,那麼該方法執行完成後線程也就相應的結束了,因而我們可以通過控制run方法中循環的條件來控制線程的結束。

注:

<pre name="code" class="java">class TestThread extends Thread {
	public void run() {
		while (true) {
			System.out.println("run()" + Thread.currentThread().getName());
		}
	}
}
           
public static void main(String[] args) {
		new TestThread().start();
		while (true) {
			System.out.println("main()" + Thread.currentThread().getName());
		}
	}
           

三.背景線程與聯合線程

1.如果我們在啟動(調用了start方法)之前調用了setDaemon(true)方法,這個線程就變成了背景線程

2.對Java程式來說隻要還有一個前台線程在運作,這個線程就不會結束,如果一個程序中隻有背景線程運作,這個程序就會結束。

3.pp.join()的作用是把所對應的線程合并到調用pp.join();語句的線程中。

注3:

<pre name="code" class="java">class TestThread extends Thread {
	public void run() {
		while (true) {
			System.out.println("run()" + Thread.currentThread().getName());
		}
	}
}
           
public static void main() throws Exception {
		TestThread tt = new TestThread();
		tt.start();
		int index = 0;
		while (true) {
			if (index++ == 100) {
				tt.join();
				// 或tt.jion(10000);指定合并的時間,即主線程等待的時間
				System.out
						.println("mian():" + Thread.currentThread().getName());
			}
		}
	}
           

四.使用Runnable接口建立多線程

1.适合多個相同代碼的線程去處理同一資源的情況,把虛拟CPU(線程)同程式的代碼、資料有效分離,較好地展現了面向對象的設計思想

2.可以避免由于Java的單繼承特性帶來的局限。我們經常碰到這樣一種情況,即當我們要将已經繼承了某一個類的子類放入多線程中,由于一個類不能有兩個父類,所有不能用繼承Thread類的方式,那麼這個類就隻能采用實作Runnable接口的方式。

3.當線程需要被構造時,需要的代碼和資料通過一個對象作為構造函數的參數傳遞進去這個對象就是實作了Runnable接口的類的執行個體。

4.事實上,幾乎所有多線程應用都可用Runnable接口方式。

5.Thread類的構造方法:

Thread()

Thread(Runnable)

那麼繼承Thread類與實作Runnable接口實作的多線程有什麼差別?

注5.實作四個線程賣100張票:

繼承Thread類的方式:

<pre name="code" class="java">public class ExtendThread {
	public static void main(String[] args) {
		new TestThread1().start();
		new TestThread1().start();
		new TestThread1().start();
		new TestThread1().start();
	}
}
           
class TestThread1 extends Thread {
	int ticket = 100;
	@Override
	public void run() {
		while (true) {
			if (ticket > 0)
		System.out.println(Thread.currentThread()
.getName()+":"+ ticket--);
			else
				break;
		}
	}
}
           

結果無法實作。

修改一下繼續:

<pre name="code" class="java">public class ExtendThread {
	public static void main(String[] args) {
		 TestThread1 tt=new TestThread1();
		 tt.start();
		 tt.start();
		 tt.start();
		 tt.start();
<span style="white-space:pre">	</span>}
}
           

此種方式隻能打開一個線程還是無法實作。

之後用實作Runnable接口的形式:

<pre name="code" class="java">public class implemet Runnable {
	public static void main(String[] args) {
		TestThread2 tt = new TestThread2();
		new Thread(tt).start();
		new Thread(tt).start();
		new Thread(tt).start();
		new Thread(tt).start();
	}
}
           
class TestThread2 implements Runnable {
	int ticket = 100;
	public void run() {
		while (true) {
			if (ticket > 0)
				System.out.println(Thread.currentThread()
						.getName()+":"+ ticket--);
			else
				break;
		}
	}
}
           

比較:若為繼承Thread類的方式,則每個線程各有100張票,共400張票,而不會共用100張票。若為實作Runnable接口,因為隻建立一個資料對象,卻建立4個線程,是以能夠共用100張票。

五.多線程在實際中的應用

1.網絡聊天程式的收發

1>如果一方從鍵盤上讀取了資料發送給對方,程式運作到讀取對方會送的資料,并一直等待對方回送資料,如果對沒有回應,程式不能再做任何事情,至此程式處于阻塞狀态,即使使用者想正常終止程式運作都不可能,更不可能給對方發送一條消息,催促對方趕快回答這樣的事情了。

2>如果程式沒有事先從鍵盤上讀取資料并向外發送,程式将一直在“從鍵盤上讀取資料”處阻塞,即使有資料從網上發送過來,程式無法到達“讀取對方回送的資料”處,程式不能收到别處先主動發過來的資料。

2.表記錄複制的中途取消

3.WWW伺服器為麼一個來訪者都建立專線服務

六.多線程的同步(多線程的安全)

1.什麼是線程同步

注1:

public class ThreadSynchronized {
	public static void main(String[] args) {
		TestThread2 tt = new TestThread2();
		new Thread(tt).start();
		new Thread(tt).start();
		new Thread(tt).start();
		new Thread(tt).start();
	}
}
class TestThread2 implements Runnable {
	int ticket = 100;
	public void run() {
		while (true) {
			if (ticket > 0){
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread() //
						.getName() + ":" + ticket--);
			}
			else
				break;
		}
	}
}
           

線程出現安全問題,以為代碼在執行到Thread.sleep(1);處時CPU切換到其他線程執行,當再切換回來繼續執行時狀态改變,進而出現執行結果錯誤

2.同步代碼塊

注2:解決上面的問題

class TestThread2 implements Runnable {
	int ticket = 100;
	String str=new String();
	public void run() {
		while (true) {
			synchronized (str) {
				if (ticket > 0){
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread() //
							.getName() + ":" + ticket--);
				}
				else
					break;
			}
			
		}
	}
}
           

在run()方法外的資料對象,有多個調用run()的線程所共享,由于每建立一個線程時,run()方法都會被調用一次,是以run()内建立的對象及資料由線程所獨有而不共享。

同步代碼塊中的線程若想實作同步,括号中所用的對象必須是所有線程所共享的唯一對象。

3.同步函數

class TestThread2 implements Runnable {
	int ticket = 100;
	String str=new String();
	public void run() {
		while (true) {
			sale();
			if(ticket<=0)
				break;			
		}
	}
	public synchronized void sale(){
		if (ticket > 0){
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread() //
					.getName() + ":" + ticket--);
		}
	}
}
           

在函數上加上synchronized關鍵字,函數中的代碼能夠實作同步,這個同步時用的對象鎖為this.

4.代碼塊與函數的同步

注4.當同步代碼塊也使用this作為對象鎖時,可以實作和同步函數的同步

5.死鎖問題

注六:線程同步使用的情況:

a>有共享資料

b>若在某一段代碼中CPU被切換,切彙時會導緻資料的不一緻性

七.線程間的通信

1.wait():告訴目前線程放棄螢幕并進入睡眠狀态直到其他線程進入螢幕并調用notify()為止。

2.notify():喚醒同一螢幕中調用wait()的第一個線程。用于類似飯有一個空位後通知所有等候就餐的顧客中的第一個可以入座的情況。

3.notifyAll():喚醒同一對象螢幕中調用wait()的所有線程,具有最高優先級的線程仙貝喚醒并執行。類似于某個不定期的教育訓練班終于招生蠻滿額後,通知所有學員都來上課的情況。

4.線程的等待和喚醒

黑馬程式員——Java基礎---傳統多線程

注七:

黑馬程式員——Java基礎---傳統多線程
class Q {
	private String name = "unknown";
	private String sex = "unknown";
	private boolean bFull = false;
	public synchronized void put(String name, String sex) {
		if(bFull)
			try {
				wait();
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
		this.name = name;
		try {
			Thread.sleep(1);
		} catch (Exception e) {
		}
		this.sex = sex;
		bFull=true;
		notify();
	}
	public synchronized void get() {
		if(!bFull)
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		System.out.print(name + " : ");
		System.out.println(sex);
		
		bFull=false;
		notify();
	}
}
class Producer implements Runnable {
	Q q;
	public Producer(Q q) {
		this.q = q;
	}
	public void run() {
		int i = 0;
		while (true) {
			if (i == 0)
				q.put("zhangsan", "male");
			else
				q.put("lisi", "female");
			i = (i + 1) % 2;
		}
	}
}
class Consumer implements Runnable {
	Q q;
	public Consumer(Q q) {
		this.q = q;
	}
	public void run() {
		while (true)
			q.get();
	}
}
public class ProducerConsumer {
	public static void main(String[] args) {
		Q q = new Q();
		new Thread(new Producer(q)).start();
		new Thread(new Consumer(q)).start();
	}
}
           

八.線程生命的控制

黑馬程式員——Java基礎---傳統多線程

程式中如何控制線程的生命

stop(),suspend(),resume()容易引起死鎖,不建議使用

主線程控制子線程執行的例子:

class ThreadTest implements Runnable {
	private boolean bStop = false;
	public void run() {
		while (!bStop) {
			System.out.println(Thread.currentThread() //
					+ "is running.");
		}
	}
	public void stopMe() {
		bStop = true;
	}
}
public class ThreadControlThread {
	public static void main(String[] args) {
		ThreadTest tt = new ThreadTest();
		new Thread(tt).start();
		for (int i = 1; i <= 100; i++) {
			if (i > 30)
				tt.stopMe();
			System.out.println("main is running.." + i);
		}
	}
}
           

九.定時器

>Timer類

>TimerTask類

應用舉例:

1.//10秒後執行run中的代碼

public class TraditionalTimer {
	public static void main(String[] args) throws Exception {
		new  Timer().schedule(new TimerTask() {
			@Override
			public void run() {
				System.out.println("bombind!");
			}
		}, 10000);
		
		while(true){
			System.out.println(new Date().getSeconds());
			Thread.sleep(1000);
		}
	}
}
           

2.10秒後首次執行run中的代碼,之後每隔3秒執行一次

public class TraditionalTimer {
	public static void main(String[] args) throws Exception {
		new  Timer().schedule(new TimerTask() {
			@Override
			public void run() {
				System.out.println("bombind!");
			}
		}, 10000,3000);
		
		while(true){
			System.out.println(new Date().getSeconds());
			Thread.sleep(1000);
		}
	}
}
           

另一種實作方式:

class MyTamerTask extends TimerTask {
	@Override
	public void run() {
		System.out.println("bombind!");
		new Timer().schedule(new MyTamerTask(), 3000);
	}
}
public class TraditionalTimer {
	public static void main(String[] args) throws Exception {
		new Timer().schedule(new MyTamerTask(),10000);
		while (true) {
			System.out.println(new Date().getSeconds());
			Thread.sleep(1000);
		}
	}
}
           

3.每2秒和4秒交替執行

public class TraditionalTimer2 {
	public static void main(String[] args) throws Exception {
		new Timer().schedule(new MyTimerTask2(), 2000);
		while (true) {
			System.out.println(new Date().getSeconds());
			Thread.sleep(1000);
		}
	}
}
class MyTimerTask2 extends TimerTask{
	static int count=0;
	@Override
	public void run() {
		count=(count+1)%2;
		System.out.println("bombind!");
		new Timer().schedule(new MyTimerTask2(), 2000+2000*count);
	}
}
           

4.與具體時間點有關的定時器應用使用:

schedule(TimerTask task, long delay, long period)

方法。

比如,每天的淩晨3:00定時執行某個任務。

5.更複雜的定時器應用可以使用開源工具quartz。