黑馬程序員----java面向對象05(多線程)

---------------------- ASP.Net+Android+IOS開發.Net培訓、期待與您交流! ----------------------


一.線程概述

 * 學習筆記:
 * 1、當多個程序例如QQ、暴風、音樂同時運行時,其實CPU是一個一個的執行的,只不過進行的快速切換
 * 2、例如,搬東西,五個人一起把A區的一批貨物搬到B區,這就是一個進程,而每個人搬運就是線程
 * 3、java中進程就是正在執行的程序,而線程就是進程中的一個獨立單元,控制着進程的執行
 * 4、JVM執行的時候會有一個進程java.exe
 * 5、創建線程有兩種方法
 



二.創建線程第一種方法(繼承Thread類)


 * 1、定義類並繼承Thread類
 * 2、複寫run方法
 * 目的:爲了將自定義代碼存儲在run方法中,讓線程運行
 * 3、創建此類對象,也就是創建了一個線程
 * 4、調用start方法,也就是啓動了這個線程
 * start方法的兩個作用:啓動線程和調用run方法
 * 5、發現每次運行結果都不一樣,因爲多個線程都在獲取CPU的執行權,CPU執行誰,誰就運行
 * 注意:在某一個時刻,只有一個線程在運行(多核除外),只不過CPU在做着快速的切換
 * 6、多線程運行可以形象的理解爲程序在搶奪CPU的執行權
 * 這是多線程的一個特性:隨機性

代碼示例:

public class ThreadDemo {
	public static void main(String[] args) {
		Test t = new Test();   //創建了一個線程
		t.start();    //啓動了這個線程,調用run方法
		t.run();      //不啓動線程,僅僅調用run方法
		
		for(int i =0;i<100;i++){
			System.out.println("主函數在執行"+i);
		}
	}		
}

class Test extends Thread{
	public void run(){
		for(int i = 0;i<100;i++){
			System.out.println("複寫了run方法"+i);		
		}
	}
}




三.創建線程第二種方法(實現Runnable接口)

* 線程第二種方式:實現Runable接口
 * 示例:多窗口賣票
 * 步驟;
 * 1、定義類實現Runnable接口
 * 2、覆蓋Runnable接口中的run方法
 * 作用:將線程要運行的代碼存儲在run方法中
 * 3、通過Thread類建立線程對象
 * 4、把Runnable接口的子類對象作爲參數傳遞給Thread類的構造函數
 * 作用:調用run方法的對象必須是Runnable接口的子類對象,所以要把此對象傳遞給Thread類的構造函數
 * 5、調用Thread類的start方法,並調用Runnable接口的子類的run方法


代碼示例:

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

		Ticket t = new Ticket();   //這並沒有創建線程,因爲該類沒有繼承Thread類
		Thread t1 = new Thread(t,"一");    //這纔是創建了一個線程
		Thread t2 = new Thread(t,"二");
		Thread t3 = new Thread(t,"三");
		Thread t4 = new Thread(t,"四");
		Thread t5 = new Thread(t,"五");   
		
		t1.start();//啓動線程並調用run方法
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}

class Ticket implements Runnable{
	private int i=100;  //共有100張票,因爲只創建了一個對象,所以i只有一個,是共享的
	public void run(){
		for(int a=1;a<=1000;a++){
			if(i>0){
				System.out.println(Thread.currentThread().getName()+"號窗口賣"+i--+"號票");
				
			}
		}
	}
}




四.繼承和實現的區別

 * 繼承和實現的區別:
 * 繼承只能夠繼承一個類,有侷限性
 * 而如果一個學生類已經繼承了人類,那麼就不能給繼承Thread類了,導致不能夠創建線程
 * 而實現恰好打破了這種侷限性,當一個類已經有了自己的父類,這時仍然可以實現Runnable創建線程
 *
 * 繼承中,線程代碼存放在Thread子類的run方法中
 * 實現中,線程代碼存放在Runnable子類的run方法中
 * 
 *  實現的好處:避免了單繼承的侷限性



五.多線程練習

* 創建兩個線程,並和主線程同時運行
 * 1、自定義線程名稱
 * 線程默認名稱爲:Thread-0,編號從零開始
 * 通過調用父類構造函數對name進行初始化,
 * 並定義this.getName()或者(Thread.currentThread().getName)獲取名稱
 * 2、  static Thread currentThread()   獲取當前線程對象
 *  getName()   獲取線程名稱
 *  setName()   設置線程名稱


代碼示例:

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

		Demo d1 = new Demo("one");  //創建一個線程
		Demo d2 = new Demo("two");  //創建一個新線程
		
		d1.start();
		d2.start();
	
		for(int i=0;i<30;i++){
			System.out.println("bbbbbbbbbbbb");
		}
	}
}


class Demo extends Thread{
	Demo(String name){
		super(name);
	}
	public void run(){
		for(int i=0;i<30;i++){   //局部變量i在每個線程區域中都有一個獨立的塊
			System.out.println("aaaaaaaaaaaaaaa"+Thread.currentThread().getName());
		}
	}
} 




六.多線程安全問題

* 多線程安全問題:
 * 問題原因:當多個線程在執行同一個線程共享數據時,第一個線程剛剛執行了一部分,
 * 另一個線程就參與了進來執行,導致共享數據的錯誤
 * 解決辦法:在多個線程操作共享數據時,控制在一個線程執行過程中,其他線程不能幹參與執行
 * 
 * java對多線程安全問題提供了專業的解決辦法,就是同步代碼塊
 * 格式:
 * synchronize(對象){
 * 需要被同步的代碼塊       //只要操作了共享數據的代碼就是需要被同步的代碼
 * }
 * 
 * 相當於火車上的廁所,例如一個人去上廁所,走到門前首先判斷下,沒人,然後進去,關上門,上鎖,在他還沒出來之前
 * 外面的人看到門上鎖顯示有人,那麼就進不去,只能等裏面的人出來以後才能進去
 * 
 * 同步的前提:
 * 1、必須有兩個或兩個以上的線程。例如如果火車上只有一個人,那麼就沒必要鎖門了
 * 2、必須是多個線程使用同一個鎖。例如如果1號車廂的廁所是打開來,而2號車廂的廁所是鎖着來,2號車廂的人還是進不去廁所
 * 
 * 同步的好處:解決了多線程的安全問題
 * 同步的弊端:線程執行時需要判斷鎖,較爲耗費資源。 例如回家直接推開門進去和需要開鎖,較爲麻煩
 * 
 * 補充知識點:實例其實就是對象,但是它是有所屬的。比如說,我們可以說他是“人”,
 * 但是我們不能單獨說他是“兒子”,我們必須說他是某某的“兒子”。
 * 所以,我們定義了類 CA,並通過類 CA 創建了對象 objA。
 * 我們就可以說 objA 是類 CA 的實例。


代碼示例:

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

		Ticket1 t = new Ticket1();   //這並沒有創建線程,因爲該類沒有繼承Thread類
		Thread t1 = new Thread(t,"一");    //這纔是創建了一個線程
		Thread t2 = new Thread(t,"二");

		t1.start();//啓動線程並調用run方法
		t2.start();
	}
}

class Ticket1 implements Runnable{
	private int i=100;  //共有100張票,因爲只創建了一個對象,所以i只有一個,是共享的
	Object obj = new Object();
	public void run(){
		for(int a=1;a<=1000;a++){
			synchronized(obj){
				if(i>0){
					try {
						Thread.sleep(10);    //這個方法本身拋出了異常
					} catch (InterruptedException e) {
					}
					System.out.println(Thread.currentThread().getName()+"號窗口賣"+i--+"號票");		
				}
			}
		}
	}
}



七.多線程同步函數

* 目的:該程序是否有問題,如果由,該怎麼找出來
 * 
 * 找問題方法:
 * 1、明確哪些代碼是多線程運行代碼 ( run方法中的代碼和所調用的方法)
 * 2、明確共享數據 (Bank類的對象、num屬性)
 * 3、明確多線程運行中哪些代碼可以操作共享數據
 * 
 * 然後分析問題:
 * 1、如果一個線程在運行到num=num+a後執行權就被其他線程搶走了,這時候就執行不到打印語句了
 * 
 * 解決問題:
 * 1、這時候就需要把多線程執行到得共享數據Synchronized了
 * 
 * 同步函數:public Synchronized void add(){
 *  同步函數的鎖是this
 *  同步代碼塊的鎖是對象
 *  靜態同步函數的鎖是 類名.class 字節碼文件

代碼示例:

public class Synchronized_Cunqian {
	public static void main(String[] args){
		Client c = new Client();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}
//銀行
class Bank{
	private int num=0;
	Object obj = new Object();
	public void add(int a){   //同步函數 public Synchronized void add(int a){
		synchronized(obj){
			num=num+a;
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println("現在銀行有"+num+"元錢");
		}
	}
} 

//客戶存錢
class Client implements Runnable{
	Bank b = new Bank();
	public void run(){
		for(int i=0;i<3;i++){
			b.add(100);
		}
	}
}



八.線程間通信

* 多線程通信:其實就是多個線程在操作同一個資源,只不過操作的動作不同
 * 示例:一個線程向共享區存入人的姓名和年齡,而另一個線程向共享區提取數據
 * 
 * 記住:多線程出現了問題;檢查兩個條件是否滿足
 * 1、是否同步了兩個或兩個以上的線程
 * 2、必須是多個線程使用同一個鎖

代碼示例:

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

		Person p = new Person();

		Input i = new Input(p);
		Output o = new Output(p);

		Thread t = new Thread(i);
		Thread t1 = new Thread(o);

		t.start();
		t1.start();
	}
}

// 人的屬性
class Person {
	String name;
	String sex;
}

// 存入
class Input implements Runnable {
	private Person p;

	Input(Person p) {
		this.p = p;
	}

	public void run() {
		int i=0;
		while (true) {
			synchronized (p) {
				if (i==0) {
					p.name = "小花";
					p.sex = "女";
				}
				else {
					p.name = "李四";
					p.sex = "男";
				}
				i = (i+1)%2;
			}
		}
	}
}

// 提取
class Output implements Runnable {
	private Person p;

	Output(Person p) {
		this.p = p;
	}

	public void run() {
		while (true) {
			synchronized (p) {
				System.out.println(p.name + "........" + p.sex);
			}
		}
	}
}



九.等待喚醒機制

* 多線程中等待喚醒機制
 * 
 * 問題:目前的代碼執行後存入數據代碼可能會被執行很多次以後,提取數據的代碼才能被執行
 * 
 * 目的:想要每存入一組數據,就提取改組數據,然後再存入
 * 方法:
 * 1、首先定義一個控制共享區的變量,如果爲true,就代表裏面有數據;如果爲false,就代表裏面沒數據
 *    當一個線程開始執行存入代碼時,判斷下共享區裏面是否有數據(是否爲false),如果沒有就存入,有的話就wait
 *     存入以後改變這個變量改爲true,並且喚醒提取代碼的線程notify,
 * 2、現在提取代碼的線程已經被喚醒,首先if判斷共享區裏面是否有數據(是否爲true),如果由就提取,沒有就wait
 *  提取出以後改變這個變量爲false,並且喚醒存入代碼的線程notify,
 * 
 * 
 * 注意:
 * 1、不管是存入還是提取,鎖都要一樣,唯一性
 * 2、因爲wait()方法、notify()方法都是Object中的方法,所以需要對象進行調用,因爲是Object中的方法,所以任何對象都可以
 * 3、wait()方法在Object類中拋出了異常,所以需要在內部進行捕獲處理

代碼示例:

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

		Person p = new Person();

		// Input i = new Input(p);
		// Output o = new Output(p);

		// Thread t = new Thread(i);
		// Thread t1 = new Thread(o);

		new Thread(new Input(p)).start(); 
		new Thread(new Output(p)).start();
	}
}

// 人的屬性
class Person {
	private String name;
	private String sex;
	private boolean flag = false;

	public void setShow(String name, String sex) {
		synchronized (this) {
			if (this.flag) {
				try {
					this.wait();
				} catch (Exception e) {

				}
			}
			this.name = name;
			this.sex = sex;

			this.flag = true;
			this.notify();
		}
	}

	public void print() {
		synchronized (this) {
			if (!this.flag) {
				try {
					this.wait();
				} catch (Exception e) {
				}
			}
			System.out.println(this.name + "........" + this.sex);
			this.flag = false;
			this.notify();
		}
	}
}

// 存入
class Input implements Runnable {
	private Person p;

	Input(Person p) {
		this.p = p;
	}

	public void run() {
		int i = 0;
		while (true) {
			if (i == 0) {
				p.setShow("小花", "女");
			} else {
				p.setShow("小黑", "男");
			}
			i = (i + 1) % 2;
		}
	}
}

// 提取
class Output implements Runnable {
	private Person p;

	Output(Person p) {
		this.p = p;
	}

	public void run() {
		while (true) {
			p.print();
		}
	}
}




十.等待喚醒機制實例(生產者與消費者)

* 多線程喚醒機制例子:生產者與消費者
 * 
 * 1、爲什麼要使用while
 * :因爲如果當生產者多線程被喚醒以後,共享區裏明明有數據,這時候沒有經過判斷就又創建了一個商品
 * 共享區內就會發生錯誤
 * 2、爲什麼使用notifyAll
 * :因爲當生產者線程wait以後,可能會喚醒其他生產者線程,因爲需要先經過判斷,這樣也生產不了,
 * 導致所有線程全部等待

代碼實例:

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

	Resource r = new Resource();
	
	Produce p = new Produce(r);   //爲了保證r對象是同一個
	Consume c = new Consume(r);	  //爲了保證r對象是同一個
	
	Thread t1 = new Thread(p);
	Thread t2 = new Thread(p);
	Thread t3 = new Thread(c);
	Thread t4 = new Thread(c);
	
	t1.start();
	t2.start();
	t3.start();
	t4.start();
	
	}
}

//共享資源
class Resource{
	private String name;  //商品名稱
	private	int count=1;   //計數
	private boolean flag= false;
	
	//生產
	public synchronized void intPut(String name){
		while(flag){
			try{
				wait();
			}catch(Exception e){
				
			}
		}
		this.name = name;
		count++;
		System.out.println(Thread.currentThread().getName()+this.name+this.count);
		flag=true;
		this.notifyAll();
	}
	
	//消費
	public synchronized void OutPut(){
		while(!flag){
			try{
				wait();
			}catch(Exception e){
				
			}
		}
		System.out.println(Thread.currentThread().getName()+this.name+this.count+"...............");
		flag=false;
		this.notifyAll();
	}
}

//生產者
class Produce implements Runnable{
	private Resource r;
	Produce(Resource r){
		this.r = r;
	}
	public void run(){
		while(true){
//			
				r.intPut("麪包");
		}
	}
}

//消費者
class Consume implements Runnable{
	private Resource r;
	Consume(Resource r){
		this.r = r;
	}
	
	public void run(){
		while(true){
			
				r.OutPut();
			
		}
	}
		
}




十一.lock鎖(消費者與生產者代碼更嚴謹)

* 思路:
 * 1、因爲在JDK1.5以後,lock就替換了synchronized,而condition就替換了wait()、notify();
 * 2、現在通過lock.lock()的方式就可以手動上鎖,通過lock.unlock()的方式可以手動開鎖
 * 3、通過con.await()的方式讓線程等待,注意,這時候會拋出一個異常,而這個異常不能夠在這裏處理,所以要聲明
 * 4、通過con.signalAll()的方式喚醒全部線程
 * 
 *
 * 可是這樣又出現了問題:
 * 因爲con.signalAll()是喚醒全部線程,那麼本方線程也可能去讀取while,這樣就佔用了資源
 *
 * 解決辦法:
 * 1、在這裏可以通過創建多個Condition實例,
 * 2、此例創建兩個,一個控制喚醒消費者線程,另一個控制喚醒生產者線程


代碼示例:

public class Produce_Consume {
	public static void main(String[] args) {
		Resource_ r = new Resource_();

		Produce_ p = new Produce_(r);
		Consume_ c = new Consume_(r);

		/*
		 * Thread有一個構造函數 Thread(Runnable r),作用是分配新的Thread對象
		 * 因爲只有Thread對象纔可以啓動線程並調用run方法
		 */
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(p);
		Thread t3 = new Thread(c);
		Thread t4 = new Thread(c);

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

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

	private Lock lock = new ReentrantLock(); // (多態)通過Lock接口的實現類ReentrantLock創建了lock對象
	private Condition con_pro = lock.newCondition(); // lock對象通過調用newCondition方法創建了綁定此Lock實例的Condition實例con
	private Condition con_con = lock.newCondition();

	// 生產
	public void produce_(String name) throws Exception {
		
			lock.lock();
			try {
				while (flag) {
					con_pro.await(); // 這裏不能處理,因爲只有當爲true時纔會等待,那麼此時線程處於等待狀態,怎麼可能繼續運行處理代碼呢?
				}	
					this.name = name + "...." + count++;
					System.out.println(Thread.currentThread().getName()
							+ this.name );
					flag = true;
					con_con.signal();
			} finally {
				lock.unlock(); // 因爲不管線程是否等待,都必須打開鎖,所以把開鎖功能放在finally中
			}
	}

	// 消費
	public void consume_() throws Exception {
		
			lock.lock();
			try {
				while (!flag) {
					con_con.await();
				}
					System.out.println(Thread.currentThread().getName()
							+ this.name + "...............");
					flag = false;
					con_pro.signal();
			} finally {
				lock.unlock();
			}
	}
}

// 生產者
class Produce_ implements Runnable {
	private Resource_ r;

	Produce_(Resource_ r) {
		this.r = r;
	}

	public void run() {
		while (true) {
			try {
				r.produce_("+商品+");
			} catch (Exception e) {

			}
		}
	}
}

// 消費者
class Consume_ implements Runnable {
	private Resource_ r;

	Consume_(Resource_ r) {
		this.r = r;
	}

	public void run() {
		while (true) {
			try {
				r.consume_();
			} catch (Exception e) {

			}
		}
	}
}



十二.停止線程

如何停止線程?
只有一種,run方法結束。
開啓多線程運行,運行代碼通常是循環結構。
只要控制住循環,就可以讓run方法結束,也就是線程結束。

特殊情況:
當線程處於了凍結狀態。
就不會讀取到標記。那麼線程就不會結束。

當沒有指定的方式讓凍結的線程恢復到運行狀態是,這時需要對凍結進行清除。
強制讓線程恢復到運行狀態中來。這樣就可以操作標記讓線程結束。

Thread類提供該方法 interrupt();


代碼示例:

class StopThread implements Runnable
{
	private boolean flag =true;
	public synchronized void run()
	{
		while(flag)
		{
			try{
				wait();
			}catch(Exception e){
				System.out.println(Thread.currentThread().getName()+"....Exception");
				flag = false;
			}
			System.out.println(Thread.currentThread().getName()+"....run");
		}
	}
}




class  StopThreadDemo
{
	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();
				t1.interrupt();
				t2.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName()+"...."+num);
		}
		System.out.println("over");
	}
}



十三.守護線程

* 守護線程(又叫用戶線程、後臺線程)
 * 注意:
 * 1、該方法必須在啓動線程之前調用
 * 2、如果只有守護線程在運行,那麼JVM退出
 * 
 * 這時候程序中線程t1和t2因爲被定義爲守護線程,當它們都wait時,主線程執行完畢,JVM退出,程序執行完畢

代碼示例:

public class SetDaemon {
	public static void main(String[] args) 
	{
		StopThread st = new StopThread();
		
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		
		//定義守護線程
		t1.setDaemon(true);
		t2.setDaemon(true);
		
		t1.start();
		t2.start();

		int num = 0;

		while(true)
		{
			if(num++ == 60)
			{
				//st.changeFlag();
				//t1.interrupt();
				//t2.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName()+"...."+num);
		}
		System.out.println("over");
	}
}

class StopThread implements Runnable
{
	private boolean flag =true;
	public synchronized void run()
	{
		while(flag)
		{
			try{
				wait();
			}catch(Exception e){
				System.out.println(Thread.currentThread().getName()+"....Exception");
				flag = false;
			}
			System.out.println(Thread.currentThread().getName()+"....run");
		}
	}
}



十四.join方法

join:
當A線程執行到了B線程的join()方法時,A就會等待。等B線程都執行完,A纔會執行。

join可以用來臨時加入線程執行。

代碼示例:

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


class  JoinDemo
{
	public static void main(String[] args) throws Exception
	{
		Demo1 d = new Demo1();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		
		//t1.join();  //這句話的意思是:當主線程執行到這裏時,會釋放執行權,因爲只有當t1執行完以後主線程才能重新獲得執行權
		
		t2.start();
		
		t1.join();  /*這句話的意思是:當主線程執行到這裏時,會釋放執行權,
					*而因爲之前已經有t1和t2兩個線程都被啓動,所以這時候t1和t2兩個線程爭奪執行權,
					*當t1執行完以後主線程纔可以擁有執行權,這時候就是主線程和t2爭奪執行權了
					*/

		for(int x=0; x<80; x++)
		{
			System.out.println("main....."+x);
		}
		System.out.println("over");
	}
}




十五.優先級&yield方法

* toString()方法:返回該線程的字符串表示形式,包括 【線程名稱】【優先級】【線程組】 
 * 
 * 對於優先級,更改線程的優先級格式:
 * setPriority(MAX_PRIORITY 或 MIN_PRIORITY 或 NORM_PRIORITY);

代碼示例:

public class ToString_Demo {
	public static void main(String[] args) throws Exception
	{
		Demo2 d = new Demo2();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();	
		
		t1.setPriority(Thread.MAX_PRIORITY);  
		   		//MAX_PRIORITY是10 ; MIN_PRIORITY是1 ; ONRM_PRIORITY是5
		
		t2.start();

		for(int x=0; x<80; x++)
		{
			System.out.println("main....."+x);
		}
		System.out.println("over");
	}
}

class Demo2 implements Runnable
{
	public void run()
	{
		for(int x=0; x<70; x++)
		{
			System.out.println(Thread.currentThread().toString()+"....."+x);
		}
	}
}


* static void yield()   
 * 作用:暫停執行當前線程,改爲執行其他線程

代碼示例: 

public class YieldDemo {
	public static void main(String[] args) throws Exception  //異常拋給了JVM
	{
		Demo2 d = new Demo2();
		
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		
		t1.start();	
		t2.start();
	}
}


class Demo3 implements Runnable
{
	public void run()
	{
		for(int x=0; x<70; x++)
		{
			System.out.println(Thread.currentThread().toString()+"....."+x);
			Thread.yield();
		}
	}
}



---------------------- ASP.Net+Android+IOS開發.Net培訓、期待與您交流! ----------------------詳細請查看:http://edu.csdn.net

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章