多線程(認識多線程、線程的常用操作方法)

 

認識多線程

進程與線程:

進程是程序的一次動態執行過程,它經歷了從代碼加載、到執行完畢的一個完整過程,這個過程也是進程本身從產生、發展到最終消亡的過程。

多線程是實現併發機制的一種有效手段。進程和線程一樣,都是實現併發的一個基本單位。

Java的多線程實現,有一下兩種方式:

·繼承Thread類

·實現Runnable接口

Thread類

Thread類是在java.lang包下定義的,一個類只要繼承了Thread類,此類就稱爲多線程操作類。在Thread子類中,必須明確的腹瀉Thread中的run()方法,此方法稱爲線程的主體。

·多線程的定義語法:

class 類名稱 extends Thread {	//繼承Thread類
	屬性...;					//類中定義屬性
	方法...;					//類中定義方法
	//覆寫Thread中的run()方法,此方法是線程的主體
	public void run(){
		線程主體;
	}
}

如以下的一個類中就具備了多線程的操作功能:

class MyThread extends Thread	//繼承自Thread類
{
	private String name;	//表示線程的名稱
	public MyThread(String name){	
		this.name=name;	//通過構造方法配置name屬性
	}
	public void run(){	//覆寫run方法,作爲線程的主體
		for(int i=0;i<10;i++){
			System.out.println(name+"運行,i="+i);
		}
	}
}

實例化該類以後,通過對象可以調用run方法:

public class ThreadDemo01 
{
	public static void main(String args[]){
		MyThread m=new MyThread("A");   //實例化MyThread類
		MyThread m1=new MyThread("B");
		m.run();   //調用run 方法
		m1.run();
	}
}

同過程序的運行結果可以發現,程序是先執行A後再執行B的,並沒有達到所謂的併發執行的效果,如果是想要啓動一個線程必須要使用Thread類中定義的start()方法。

public class ThreadDemo01 
{
	public static void main(String args[]){
		MyThread m=new MyThread("A");  //實例化MyThread類
		MyThread m1=new MyThread("B");
		m.start();   //調用Thread類中定義的start啓動一個線程
		m1. start ();
	}
}

注意:如果當前線程已經啓動了(就是說已經調用了start方法),再次調用start方法就會出現java.lang.IllegalThreadStateException異常

Runnable接口

在Java中也可以通過實現Runnable接口的方式實現多線程,Runnable接口中只定義了一個抽象方法:

·public void run();

通過Runnable接口實現多線程:

class 類名稱 implements Runnable	//實現Runnable接口
{
	屬性...;						//類中定義屬性
	方法...;						//類中定義方法
	public void run(){			//覆寫Runnable接口中的run方法
		線程主體;
	}
}

如以下代碼:

class MyThread implements Runnable	//繼承自Thread類
{
	private String name;	//表示線程的名稱
	public MyThread(String name){	
		this.name=name;	//通過構造方法配置name屬性
	}
	public void run(){	//覆寫run方法,作爲線程的主體
		for(int i=0;i<10;i++){
			System.out.println(name+"運行,i="+i);
		}
	}
}
public class ThreadDemo02
{
	public static void main(String args[]){
		MyThread m1=new MyThread("A");	//實例化對象
		MyThread m2=new MyThread("B");
		Thread t1=new Thread(m1);	//實例化Thread類
		Thread t2=new Thread(m2);
		t1.start();	//啓動線程
		t2.start();	
	}
}

以上代碼通過實現Runnable接口實現了多線程,但是要注意的是,一個線程啓動的方法是在Thread類中的start()方法,此時Runnable接口中是沒有該方法的,所以要實例化Thread類,調用其start方法。

Runnable接口與Thread類的區別

使用Thread類在操作多線程的時候無法達到資源共享的目的,而使用Runnable接口實現的多線程操作可以實現資源共享。如以下的代碼:

實現Runnable接口:

class MyThread implements Runnable	//繼承自Thread類
{
	private int Number1=5;	//表示線程的名稱

	public void run(){	//覆寫run方法,作爲線程的主體
	
					for(int i=0;i<100;i++){
						if(Number1>0)
					System.out.println("ticket"+Number1--);
				}
	}
}
public class ThreadDemo02
{
	public static void main(String args[]){
		MyThread m=new MyThread();
		new Thread(m).start();
		new Thread(m).start();
		
	}
}

此時可以發現啓動了兩個線程,但是票的總數是5。

繼承自Thread類:

class MyThread extends Thread	//繼承自Thread類
{
	private int Number1=5;	//表示線程的名稱

	public void run(){	//覆寫run方法,作爲線程的主體
	
					for(int i=0;i<100;i++){
						if(Number1>0)
					System.out.println("ticket"+Number1--);
				}
	}
}
public class ThreadDemo03
{
	public static void main(String args[]){
		MyThread m1=new MyThread();
		MyThread m2=new MyThread();
		MyThread m3=new MyThread();
		m1.start();		//啓動線程
		m2.start();		//啓動線程	
		m3.start();		//啓動線程
	}
}

此時啓動了三個線程,票的總數就是15,因爲在每一個MyThread對象中都包含各自的ticket屬性。

Thread類與Runnable接口的使用結論

實現Runnable接口比繼承Thread類有如下明顯的優點:

·適合多個相同程序代碼的線程去處理同一個資源。

·可以避免由於單繼承侷限所帶來的影響。

·增強了程序的健壯性,代碼能夠被多個線程共享,代碼與數據是獨立的。

綜合以上來看:開發中使用Runnable接口是比較合適的,應該充分使用其以上特性。

線程的狀態

多線程在操作中也是有一個固定的操作狀態的:

·創建對象:準備好了一個多線程的對象: Thread t=new Thread();

·就緒狀態:調用了start()方法,等待CPU進行調度。

·運行狀態:執行了run()方法。

·阻塞狀態:暫時停止執行,可能將資源交給其它資源使用。

·終止狀態(死亡狀態):線程執行完畢,不再進行使用。

線程的常用操作方法


取得當前線程的名稱

程序可以通過currentThread()方法取得當前正在運行的線程的名稱。代碼如下:

class MyThread implements Runnable	//實現Runnable接口
{
	public void run(){	//覆寫run 方法
		System.out.println(Thread.currentThread().getName()+"運行");
	}
}
public class currentThreadDemo
{
	public static void main(String args[]){
		MyThread m=new MyThread();	//實例化Runnable子類對象
		new Thread(m).start();	//讓JVM爲線程設置名稱並啓動
		new Thread(m,"線程").start();	//爲線程設置名稱並啓動線程
		m.run();	//直接調用run方法 獲得當前main方法線程名稱
		//程序輸出
		//Thread-0運行
		//main運行
		//線程運行
	}
}

由以上代碼可以得知:如果不爲一個線程設置名稱,則JVN自動按Thread-0、Thread-1、Thread-2、….的格式爲線程設置名稱。注意:main方法的線程名稱爲main

擴展:既然主方法都是以線程的形式出現的,那麼JAVA運行時至少應該是啓動了兩個線程。每當JAVA程序運行的時候,實際上都啓動了一個JVM,每一個JVM實際上就是在操作系統中啓動了一個進程,java本身具有垃圾收集機制,所以Java運行時至少啓動了兩個線程:

主線程、GC(垃圾回收的線程)

判斷線程是否啓動

判斷線程是否啓動使用isAlive()方法,返回boolean得到當前線程是否活動。

class MyThread implements Runnable	//實現Runnable接口
{
	public void run(){	//覆寫run 方法
		System.out.println(Thread.currentThread().getName()+"運行");
	}
}
public class ThreadAliveDemo
{
	public static void main(String args[])throws IllegalThreadStateException{
		MyThread m=new MyThread();	//實例化Runnable子類對象
		Thread t=new Thread(m,"自定義線程");
		System.out.println("線程執行前:"+t.isAlive());		//false
		t.start();
		System.out.println("線程啓動之後:"+t.isAlive());	//true
	}
}

線程的強制運行

在線程操作中,可以使用join()方法讓一個線程強制運行,線程強制運行期間,其它線程無法運行,必須等待此線程完成之後纔可以繼續執行。

class MyThread implements Runnable	//實現Runnable接口
{
	public void run(){	//覆寫run 方法
		for(int i=0;i<50;i++){
			System.out.println(Thread.currentThread().getName()+"運行,i="+i);
		}
		
	}
}
public class ThreadJoinDemo
{
	public static void main(String args[]){
		MyThread m=new MyThread();	//實例化Runnable子類對象
		Thread t=new Thread(m,"自定義線程");
		t.start();
		for(int i=0;i<50;i++){
			if(i>10)
				try{
				//join方法會出現異常
				t.join();
			}catch(InterruptedException e){
			}
			System.out.println("main線程運行,i="+i);
		}
	}
}

以上代碼中,程序一開始自定義跟main線程會交替運行,但是當main線程中i+10時,就會強制的先執行自定義線程,待其執行完以後,再執行main線程,這就是線程的強制運行。

線程的休眠

在程序中允許一個線程進行暫時的休眠,直接使用Thread.sleep()方法即可。

class MyThread implements Runnable	//實現Runnable接口
{
	public void run(){	//覆寫run 方法
		for(int i=0;i<50;i++){
			try{
				//sleep方法會出現異常
				Thread.sleep(1000);		//程序會暫停1000毫秒再執行
			}catch(InterruptedException e){
			}
			System.out.println(Thread.currentThread().getName()+"運行,i="+i);
		}
	}
}
public class ThreadSleepDemo
{
	public static void main(String args[]){
		MyThread m=new MyThread();	//實例化Runnable子類對象
		Thread t=new Thread(m,"自定義線程");
		t.start();
	}
}

以上的程序會間隔1秒鐘執行一次。Sleep()方法就時暫時的休眠,指定了一定了的時間後程序會繼續執行。

線程的中斷

一個線程可以被另一個線程中斷其操作的狀態,使用interrupt()方法。

class MyThread implements Runnable	//實現Runnable接口
{
	public void run(){	//覆寫run 方法
		
			try{
				//sleep方法會出現異常
				System.out.println("1、進入run方法");
				Thread.sleep(2000);		//程序會暫停1000毫秒再執行
				System.out.println("2、已經完成了休眠");
			}catch(InterruptedException e){
				System.out.println("3、休眠被終止!");
				return;		//返回方法調用處
			
			
		}
		System.out.println("4、run方法正常結束");
	}
}
public class ThreadInterruptDemo
{
	public static void main(String args[]){
		MyThread m=new MyThread();	//實例化Runnable子類對象
		Thread t=new Thread(m,"自定義線程");
		t.start();
			try{
				//sleep方法會出現異常
				Thread.sleep(10000);
			}catch(InterruptedException e){
			}
		
		t.interrupt();
	}
}

後臺線程

在Java程序中,只要前臺有一個線程在運行,則整個Java進程都不會消失,所以此時可以設置一個後臺線程,這樣即使Java進程結束了,此後臺線程依然會執行。要想實現這樣的操作,直接使用setDaemon()方法即可。

class MyThread implements Runnable{	// 實現Runnable接口
	public void run(){	// 覆寫run()方法
		while(true){
			System.out.println(Thread.currentThread().getName() + "在運行。") ;
		}
	}
};
public class ThreadDaemonDemo{
	public static void main(String args[]){
		MyThread mt = new MyThread() ;	// 實例化Runnable子類對象
		Thread t = new Thread(mt,"線程");		// 實例化Thread對象
		t.setDaemon(true) ;	// 此線程在後臺運行
		t.start() ;	// 啓動線程
	}
};

此方法瞭解即可,不重要。

線程的優先級

優先級越高的線程,被執行的順序就比較靠前,在Thread中存在三個常量:MAX_PRIORITY、

MIN_PRIORITY、NORM_PRIORITY執行順序爲:MAX_PRIORITY> NORM_PRIORITY>

MIN_PRIORITY、代碼如下:

class MyThread implements Runnable	//實現Runnable接口
{
	public void run(){	
			System.out.println(Thread.currentThread().getName());
	}
}
public class ThreadPriorDemo
{
	public static void main(String args[]){
		Thread t1=new Thread(new MyThread(),"線程A");
		Thread t2=new Thread(new MyThread(),"線程B");
		Thread t3=new Thread(new MyThread(),"線程C");
		t1.setPriority(Thread.MAX_PRIORITY);
		t2.setPriority(Thread.NORM_PRIORITY);
		t3.setPriority(Thread.MIN_PRIORITY);
		t1.start();
		t2.start();
		t3.start();
	}
}

實際上:MAX_PRIORITY=10、 NORM_PRIORITY=5、MIN_PRIORITY=1

同步與死鎖

先看如以下的代碼:

class MyThread implements Runnable{
	int ticket=5;	//假設一共有5張票
	public void run(){	
		for(int i=0;i<50;i++){
			if(ticket>0){	 //還有票的話就繼續賣
				try{
					Thread.sleep(500);	//爲了保證正確,加入延遲
				}catch(InterruptedException e){
					e.printStackTrace();
				}
				System.out.println("賣票:ticeket="+ticket--);
			}
		}
	}
}
public class synchronizedDemo02
{
	public static void main(String args[]){
		MyThread m1=new MyThread();	//定義線程對象
		new Thread(m1).start();		//定義Thread對象
		new Thread(m1).start();
		new Thread(m1).start();
	}
}

代碼最終結果如下:

程序的問題:從運行結果中可以發現,程序中加入了延遲操作以後,票數會變爲負數,出現這種情況的原因是:一個線程有可能在還沒有對票數進行減操作之前,其它線程已經將票數減少了,這樣一來就出現了票數爲負的情況。

問題解決:如果想解決這種問題,就必須使用線程的同步操作,所謂的同步就是指多個操作在同一個時間段內只能有一個線程進行,其它線程要等待此線程完成之後纔可以執行。

使用同步

要想解決資源共享的同步操作問題,可以使用同步代碼塊或者是同步方法兩種方式完成。

同步代碼塊:

之前介紹了代碼塊分爲四種:

1、  普通代碼塊:是直接定義在方法之中的。

2、  構造塊:直接定義在類中,優先於構造方法執行,重複調用。

3、  靜態塊:使用static關鍵字聲明:優先於構造塊執行,只執行一次。

4、  同步代碼塊:使用synchronized關鍵字聲明的代碼塊,成爲同步代碼塊。

同步代碼塊格式:

Synchronized(同步對象){
    需要同步的代碼;
}

同步代碼塊必須指明同步的對象,一般情況下會將當前對象進行同步,使用this表示。

使用同步後的代碼:

class MyThread implements Runnable{
	int ticket=5;
	public void run(){	
		synchronized(this){		//同步代碼塊,同步當前對象
		for(int i=0;i<50;i++){
			if(ticket>0){
					try{
						Thread.sleep(300);
					}catch(InterruptedException e){
						e.getStackTrace();
					}
					System.out.println("賣票"+(ticket--));	
				}
			}
		}
	}
}
public class synchronizedDemo02
{
	public static void main(String args[]){
		MyThread m1=new MyThread();
		new Thread(m1).start();
		new Thread(m1).start();
		new Thread(m1).start();
	}
}

因爲使用了方法的同步之後,在同一時間段內只能又一個線程執行,所以程序的執行效率會明顯降低很多。

同步方法

除了可以將需要同步的代碼設置成同步代碼塊之外,也可以使用synchronized關鍵字將一個方法聲明成同步方法。

同步方法定義格式:

snchronized 方法返回值 方法名稱(參數列表){};

使用同步方法以後的代碼:

class MyThread implements Runnable{
	int ticket=5;
	public void run(){	
		for(int i=0;i<50;i++){
		this.sale();	//調用當前類中的同步方法
		}
	}
	public synchronized void sale(){	//同步方法
	if(ticket>0){
			try{
				Thread.sleep(300);
			}catch(InterruptedException e){
				e.getStackTrace();
			}
			System.out.println("賣票"+(ticket--));	
		}
	}
}
public class synchronizedDemo03
{
	public static void main(String args[]){
		MyThread m1=new MyThread();
		new Thread(m1).start();
		new Thread(m1).start();
		new Thread(m1).start();
	}
}

死鎖

1、  共享時需要進行同步操作

2、  程序中過多的同步會產生死鎖。

擴展

方法定義的完整格式:

訪問權限{public、default、protected、private} [final、static、synchronized] 返回值類型 方法名稱(參數類型 參數名稱,…..) throws Excption1,Exception2{
   rturn 返回值(沒有返回值則表示返回調用處);
}

Object類對線程的支持---等待和喚醒

Object類是所有類的父類,在此類中又一下幾個方法是對線程操作有所支持的。

線程的喚醒又兩個方法:notify、notifyAll。一般來說,所有等待的線程會按照順序進行排列,如果現在採用notify()方法,則會喚醒第一個等待的線程執行,而如果使用了notifyAll()方法,則會喚醒所有正在等待狀態中得線程,哪個線程的優先級高,則就有可能執行。

線程的生命週期


一個新的線程被創建之後,通過start()方法進入到運行狀態,在運行狀態中可以使用yield()方法禮讓,但是仍然可以進行,如果現在一個線程需要暫停,可是使用suspend()、sleep()、wait(),如果現在線程不需要再執行,則可以通過stop方法結束(如果run方法執行完畢也表示線程的結束),或者一個新的線程直接調用stop()方法也可以進行結束。

·suspend()方法:暫時掛起線程。

·resume()方法:恢復掛起的線程

·stop()方法:停止線程

因爲以上方法會產生死鎖的問題,所以很少使用,如果想停止一個線程,則可以使用一下委婉的方式:

class MyThread implements Runnable{
	private boolean flag = true ;	// 定義標誌位
	public void run(){
		int i = 0 ;
		while(this.flag){
			System.out.println(Thread.currentThread().getName()
				+"運行,i = " + (i++)) ;
		}
	}
	public void stop(){
		this.flag = false ;	// 修改標誌位
	}
};
public class StopDemo{
	public static void main(String args[]){
		MyThread my = new MyThread() ;
		Thread t = new Thread(my,"線程") ;	// 建立線程對象
		t.start() ;	// 啓動線程
		try{
			Thread.sleep(30) ;
		}catch(Exception e){
			
		}
		my.stop() ;	// 修改標誌位,停止運行
	}
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章