java --多線程

總結:
1.繼承方式建立線程,步驟
1.1建立一個類繼承Thread
1.2該類實現run方法
1.3在須要開闢線程的地方,建立該類的實例,一個實例表示一個線程
1.4調用Thread類的start()方法,啓動線程,則它會執行run()方法java

2.實現方式建立線程,步驟
2.1建立一個類實現Runnable接口
2.2該類實現run方法
2.3在須要開闢線程的地方,建立該類的實例,將該類的實例做爲形參傳入Thread類的構造器中,建立Thread類的實例,該實例即爲線程。 2.4調用Thread類的start()方法,啓動線程,則它會執行run()方法安全

3.Thread類經常使用方法
3.1 start():啓動線程,執行當前線程的run方法
3.2 run():執行子線程的邏輯業務
3.3 currentThread():獲取當前線程
3.4 getName()與setName():設置/獲取線程名稱
3.5 yield():釋放當前線程的cpu執行權,可是它會參與下一個片刻的cpu爭搶
3.6 join():在A線程中,B線程執行join()方法,等着B線程執行結束,A線程再執行
3.7 isAlive():判斷當前線程是否在還活着
3.8 sleep():使線程沉睡多少秒多線程

4.線程的生命週期
4.1新建:該線程處於建立狀態
4.2就緒:處於建立狀態的線程執行start()方法後,等待CPU時間片
4.3運行:就緒狀態的線程搶到了CPU執行權,執行run()方法
4.4阻塞:運行中的線程被掛起,即讓出CPU執行權
4.5死亡:線程完成了它的工做,或者線程被強制性停止app

5.線程同步
其實是加鎖
1.同步代碼塊
添加監視器在 操做共享數據的代碼塊上
2.同步方法
添加synchronized在 操做共享數據的代碼塊的方法上ide

6.線程通訊
線程通訊有三個方法
wait():掛起當前線程,釋放CPU執行權
notify():喚醒排隊中優先級最高的那個線程
notifyAll():喚醒全部排隊的線程測試

測試:兩個線程交替打印輸出1-100,步驟
1.實現多線程功能,兩個線程可能會打印出同一個數據
2.實現線程同步,一個線程輸出一個數據,但沒有實現交替
3.實現交替功能,在同步方法中
3.1喚醒線程
3.2該線程執行 操做共享數據方法
3.3掛起當前線程,即讓另外一個線程進來
3.4重複一、二、3this

###1.基本概念
1.程序:爲了完成某一特定功能基於某種語言編寫的一組指令的集合,即一段靜態的代碼。
2.進程:程序的一次運行過程,即正在運行的程序。
3.線程:程序中的一個分支,一個進程能夠同時運行多個線程,簡稱多線程。
###2.建立線程方式
####方式1:繼承Thread類線程

/**
 * 方式1:繼承方式建立線程
 * 步驟:
 * 1.建立一個類繼承Thread
 * 2.重寫Thread類的run()方法,該方法內實現子線程須要完成的功能
 * 3.在須要開闢該線程的地方建立該線程的實例對象
 * 4.調用Thread類的start()方法,啓動子線程,則會調用run()方法
 */
public class SubThread extends Thread{
	public void run() {
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

測試代碼:code

public static void main(String[] args) {
		SubThread st = new SubThread();
		//給子線程設置名字
		st.setName("子線程");
		//執行start()方法時,纔是真正的開闢了子線程
		st.start();
		
		//給主線程設置名字
		Thread.currentThread().setName("主線程");
		//主線程實現功能
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}

輸出結果對象

子線程:50
子線程:51
子線程:52
主線程:48
子線程:53
主線程:49
子線程:54
主線程:50
......

備註:由結果可知,子線程執行了。
####方式2:實現Runnable接口方式

package com.test.thread;

/**
 * 方式2:實現方式建立進程
 * 步驟:
 * 1.建立一個類,實現Runnable接口  
 * 2.重寫Thread類的run()方法,該方法內實現子線程須要完成的功能
 * 3.在須要開闢該線程的地方建立該線程的實例對象
 * 4.將此對象做爲形參傳遞給Thread類的構造器中,建立Thread類的實例對象,此對象即爲一個線程,要想建立多個線程,即須要實例化多個對象
 * 5.調用Thread類的start()方法,啓動子線程,則會調用run()方法
 */
public class SubThread2 implements Runnable{

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

}

測試代碼

public static void main(String[] args) {
		
		SubThread2 st2 = new SubThread2();
		
		//線程1
		Thread thread1 = new Thread(st2);
		//線程2
		Thread thread2 = new Thread(st2);
		
		thread1.start();
		thread2.start();
	}

###3.Thread經常使用方法
1.start():啓動線程,並執行該線程中run()方法
2.run():子線程藥執行的邏輯業務 3.currentThread():靜態方法,返回當前Thread
4.getName():獲取當前線程名稱
5.setName():給當前線程設置名稱
6.yield():調用此方法的線程釋放當前CPU的執行權

//主線程實現功能
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
			if (i>10) {
				//釋放當前CPU執行權,可是本身的功能還未執行完,下一個片刻接着爭搶cpu執行權
				Thread.yield();
			}
		}

7.join():在A線程中,B線程執行join()方法,則A線程暫停,直至B線程執行結束,A線程再執行。

//主線程實現功能
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
			if (i>10) {
				try {
					//當前線程暫停,子線程優先執行完;子線程執行完後,主線程接着執行完
					st.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

8.isAlive:判斷該線程是否還存活

//主線程實現功能
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
			if (i>10) {
				try {
					//當前線程暫停,子線程優先執行完;子線程執行完後,主線程接着執行完
					st.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			//很明顯st線程已經死掉了
			System.out.println(st.isAlive());
		}

9.sleep(n):靜態方法,調用該方法的線程睡眠n毫秒。

public class SubThread extends Thread{
	public void run() {
		for (int i = 0; i <= 100; i++) {
			try {
				//該線程睡眠1秒鐘
				SubThread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

###4.線程的調度
####1.調度策略
1.1 時間片輪轉:即給進程中的每一個線程相同的時間,輪流使用cpu
1.2 優先級:即優先級高的先使用cpu

public static void main(String[] args) {
		SubThread st = new SubThread();
		//給子線程設置名字
		st.setName("子線程");
		//設置子線程優先級
		st.setPriority(Thread.MAX_PRIORITY);
		//執行start()方法時,纔是真正的開闢了子線程
		st.start();
		
		//給主線程設置名字
		Thread.currentThread().setName("主線程");
		//設置主線程優先級
		Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
		//主線程實現功能
		for (int i = 0; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}

###5.實現方式多線程示例
在實際工做中,線程實現方式比繼承方式用的多。以下示例是多窗口售票

package com.test.thread;

class Window implements Runnable{

	//總共的票數
	//基於該對象的多個線程共享該資源
	int ticket = 100;
	
	@Override
	public void run() {
		while (true) {
			if (ticket > 0) {
				System.out.println(Thread.currentThread().getName()+"售票,票號爲:"+ticket--);
			}else
			{
				break;
			}
		}
		
	}
	
}


public class Main {
	
	public static void main(String[] args) {
		
		Window window = new Window();
		
		//線程1
		Thread thread1 = new Thread(window);
		//線程2
		Thread thread2 = new Thread(window);
		//線程3
		Thread thread3 = new Thread(window);
		
		//開啓線程
		thread1.start();
		thread2.start();
		thread3.start();
		
	}

}

###6.線程生命週期(5種狀態)
####1.新建
Thread類或其子類的對象被聲明建立時,則新生的線程處於建立狀態。
####2.就緒
處於新建狀態的線程被start()後,將進入線程隊列等待CPU時間片,此時它處於就緒狀態。
####3.運行
當就緒的線程被調度並獲取處理器資源時,便進入運行狀態,run()方法裏面定義了該線程要實現的功能。
####4.阻塞
線程被人爲掛起,或執行輸入輸出操做時,讓出CPU並臨時停止本身的執行,進入阻塞狀態
####5.死亡
線程完成了它的所有工做,或線程提早被強制性停止。 ###7.線程的同步機制
####1.線程安全出現的緣由?
多個線程同時操做共享數據,其中一個線程在操做共享數據的過程當中,未執行完畢的狀況下,其它的線程也參與進來,致使共享數據存在了安全問題。
####2.如何解決線程安全問題?
線程的同步機制
#####方法1:同步代碼塊

synchronized(同步監視器)
{
    //須要被同步的代碼塊,即操做共享數據的代碼
}
1.共享數據:多個線程能夠同時操做同一個數據  
2.同步監視器:俗稱"鎖",任何一個對象均可以充當。全部的線程都公用一個監視器。

實現代碼

class Window implements Runnable{

	//總共的票數
	//基於該對象的多個線程共享該資源
	int ticket = 100;
	
	//同步監視器
	//任何一個對象均可以充當監視器
	Object oj = new Object();
	
	@Override
	public void run() {
		while (true) {
			//同步
			//oj也能夠寫成this,表示本類的實例對象充當監視器
			synchronized (oj) {
				if (ticket > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"售票,票號爲:"+ticket--);
				}else
				{
					break;
				}
			}
		}
	}
}

實現原理:
監視器(oj)在同一時刻只放一個線程進入if(){}方法,該線程執行完任務退出後,再放其餘線程(這裏是全部的線程公平競爭,也包括剛剛執行過的線程)進入。所以,該監視器就像一把鎖,一旦有線程進入if()方法,便鎖住,不讓其餘線程進入;當該線程執行完退出後,監視器打開鎖,再放一個線程進來。
#####方法2:同步方法
將操做共享數據的方法聲明爲synchronized。即此方法爲同步方法,它可以保證一個線程在執行此方法時,其它線程在外等待直至該線程結束。同步方法的鎖即爲this。
代碼以下

class Window implements Runnable{

	//總共的票數
	//基於該對象的多個線程共享該資源
	int ticket = 100;
	
	//同步監視器
	//任何一個對象均可以充當監視器
	Object oj = new Object();
	
	@Override
	public void run() {
		while (true) {
			show();
		}
	}
	
	//將共享的方法聲明爲同步方法
	public synchronized void show() {
		
		if (ticket > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"售票,票號爲:"+ticket--);
		}
		
	}
}

###8.懶漢式單例模式線程安全

//懶漢式單例模式線程安全
class Singleton{
	private Singleton(){
		
	}
	private static Singleton instance = null;
	public static Singleton getInstance(){
		
		if (instance == null) {
			synchronized (Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}	
}

測試代碼

public static void main(String[] args) {
		
		Singleton singleton1 = Singleton.getInstance();
		Singleton singleton2 = Singleton.getInstance();
		System.out.println(singleton1 == singleton2);
		
	}

###9.線程同步練習
需求:銀行有一個帳戶,有兩個儲戶,同時向該帳戶存3000塊錢(每次存1000,分3次存完),每次存完錢都須要打印出該帳戶的餘額。
需求分析以下

/**
 * 銀行有一個帳戶
 * 有兩個儲戶,同時向該帳戶存3000塊錢(每次存1000,分3次存完),每次存完錢都須要打印出該帳戶的餘額
 * 預想結果:
 * 1.兩個儲戶交替存錢
 * 2.帳戶的錢每次都加1000
 * 分析:
 * 1.建立一個帳戶類
 * 2.建立一個儲戶類(兩個儲戶能夠看做兩個線程)
 * 
 */

Account類

class Account{
	//帳戶餘額
	private double balance;
	//存錢方法
	public synchronized void storeMoney(double money){
		balance = balance+money;
		try {
			Thread.currentThread().sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+",帳戶餘額:"+balance);
	}
}

Customer類

class Customer implements Runnable{

	Account Account = new Account();
	
	@Override
	public void run() {
			for (int i = 0; i < 3; i++) {
				Account.storeMoney(1000);
		}
	}
}

測試代碼

public class Main {
	
	public static void main(String[] args) {
		
		Customer customer = new Customer();
		Thread thread1 = new Thread(customer);
		thread1.setName("儲戶1");
		Thread thread2 = new Thread(customer);
		thread2.setName("儲戶2");
		thread1.start();
		thread2.start();
	}

}

###10.線程死鎖
死鎖:不一樣的線程分別佔用對方須要的同步資源不放棄,都在等待對方優先釋放本身須要的同步資源,這樣就造成了線程的死鎖。死鎖示例:

public class Main {
	
	static StringBuffer sb1 = new StringBuffer();
	static StringBuffer sb2 = new StringBuffer();
	
	public static void main(String[] args) {
		
		//線程1
		new Thread(){
			@Override
			public void run() {
				synchronized (sb1) {
					try {
						Thread.currentThread().sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					sb1.append("A");
					synchronized (sb2) {
						sb2.append("B");
						System.out.println(sb1);
						System.out.println(sb2);
					}
				}
			}
		}.start();
		
		//線程2
		new Thread(){
			@Override
			public void run() {
				synchronized (sb2) {
					try {
						Thread.currentThread().sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					sb2.append("C");
					synchronized (sb1) {
						sb1.append("D");
						System.out.println(sb1);
						System.out.println(sb2);
					}
				}
			}
		}.start();
		
	}

}

如上代碼就會出現死鎖現象,例如
線程1搶到資源sb1,而後休眠;在休眠期間,線程2搶到了資源sb2。此時線程1須要資源sb2,可是資源sb2被線程2搶佔着;同時線程2須要資源sb1,可是資源sb1被線程1搶佔着。線程1等着線程2釋放資源sb2,線程2等着線程1釋放資源sb1,線程1與線程2一直這樣僵持着,致使程序沒法運行下去,這就構成了死鎖。
###11.線程通訊
線程通訊中用到的三個方法:
wait():使當前線程掛起,並釋放CPU(同步資源),使其它線程能夠訪問並修改同步資源。
notify():喚醒正在排隊等待同步資源的線程(優先級最高的那一個),使其訪問/修改同步資源。
notifyall():喚醒正在排隊等待同步資源的全部線程。
如上三個方法是在java.lang.Object中提供的,且只能用在synchronized方法(或 synchronized代碼塊中)。

/**
 * 使用兩個線程交替打印輸出1-100
 *
 */
class PrintNum implements Runnable {

	int num = 1;

	@Override
	public void run() { 
		while (true) {
			this.show();
		}
	}
	
	public synchronized void show() {
		
		//喚醒
		notify();
		
		if (num <= 100) {
			try {
				Thread.currentThread().sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName() + ":" + num);
			num = num + 1;
		}
		
		try {
			//讓出資源
			wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

public class Main {
	
	public static void main(String[] args) {
		
		PrintNum printNum = new PrintNum();
		Thread thread1 = new Thread(printNum);
		Thread thread2 = new Thread(printNum);
		
		thread1.setName("線程1");
		thread2.setName("線程2");
		thread1.start();
		thread2.start();
	}
}
相關文章
相關標籤/搜索