1. 程式人生 > >多執行緒的基本用法和問題

多執行緒的基本用法和問題

1.多執行緒的引入

什麼是執行緒
* 執行緒是程式執行的一條路徑, 一個程序中可以包含多條執行緒
* 多執行緒併發執行可以提高程式的效率, 可以同時完成多項工作
(假如我們電腦同時做著幾件事:qq,聽歌,下載。
但cpu不是同時執行的,他是在這三個任務裡面分別執行一段,來回切換,
由於他的速度很快.表面上看起來是同時執行的。
當我們開啟很多個任務時,我們就會發現cpu的風扇很快,噪音很大,
因為cpu需要不停的分配時間,
因此說明:我們多執行緒的底層都是單執行緒,只不過多執行緒是利用了cpu的空餘時間)

2.多執行緒並行和併發的區別

* 並行就是兩個任務同時執行,就是甲任務進行的同時,乙任務也在進行。(需要多核CPU)
* 併發是指兩個任務都請求執行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行, 由於時間間隔較短,使人感覺兩個任務都在執行。(就是1所述)

3.Java程式執行原理和JVM的啟動是多執行緒的嗎

A:Java程式執行原理
* Java命令會啟動java虛擬機器,啟動JVM,
* 等於啟動了一個應用程式,也就是啟動了一個程序。
* 該程序會自動啟動一個 “主執行緒” ,然後主執行緒去呼叫某個類的 main 方法。
B:JVM的啟動是多執行緒的嗎
* JVM啟動至少啟動了垃圾回收執行緒和主執行緒,所以是多執行緒的。
public class a {

	/**
	 * @param args
	 * 證明jvm是多執行緒的
	 */
	public static void main(String[] args) {
		for(int i = 0; i < 100000; i++) {
			new Demo();
		}
		
		for(int i = 0; i < 10000; i++) {
			System.out.println("我是主執行緒的執行程式碼");
		}
	}

}

class Demo {

	@Override
	public void finalize() {
		System.out.println("垃圾被清掃了");
	}
	
}


類 Thread

java.lang.Object
——java.lang.Thread
所有已實現的介面:Runnable 
public class Thread extends Object implements Runnable
執行緒 是程式中的執行執行緒。
Java 虛擬機器允許應用程式併發地執行多個執行執行緒。 
每個執行緒都有一個優先順序,高優先順序執行緒的執行優先於低優先順序執行緒。
每個執行緒都可以或不可以標記為一個守護程式。
當某個執行緒中執行的程式碼建立一個新 Thread 物件時,該新執行緒的初始優先順序被設定為建立執行緒的優先順序,
並且當且僅當建立執行緒是守護執行緒時,新執行緒才是守護程式。 
每個執行緒都有一個標識名,多個執行緒可以同名。如果執行緒建立時沒有指定標識名,就會為其生成一個新名稱。 

public Thread()
分配新的 Thread 物件。
這種構造方法與 Thread(null, null, gname) 具有相同的作用,
其中 gname 是一個新生成的名稱。
自動生成的名稱的形式為 "Thread-"+n,其中的 n 為整數。 

public Thread(String name)
分配新的 Thread 物件。這種構造方法與 Thread(null, null, name) 具有相同的作用。 
引數:
name - 新執行緒的名稱。

public Thread(Runnable target)
分配新的 Thread 物件
這種構造方法與 Thread(null, target,gname) 具有相同的作用

public void start()
使該執行緒開始執行;Java 虛擬機器呼叫該執行緒的 run 方法。 
結果是兩個執行緒併發地執行;當前執行緒(從呼叫返回給 start 方法)和另一個執行緒(執行其 run 方法)。 
*多次啟動一個執行緒是非法的。特別是當執行緒已經結束執行後,不能再重新啟動。 
丟擲: 
IllegalThreadStateException - 如果執行緒已經啟動。

多執行緒程式實現的方式1

1.繼承Thread
* 定義類繼承Thread
* 重寫run方法
* 把新執行緒要做的事寫在run方法中
* 建立執行緒物件
* 開啟新執行緒, 內部會自動執行run方法
public class b {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MyThread mt = new MyThread();		//4,建立Thread類的子類物件
		//mt.run();//先執行run方法並沒有開啟執行緒,他就是一個普通方法,先輸出a後輸出b.
		mt.start();				//5開啟執行緒,先b後a交替出現,開啟執行緒也需要時間
		for(int i = 0; i < 1000; i++) {
			System.out.println("bbbbbbbbbbb");
		}
	}

}
class MyThread extends Thread {				//1,繼承Thread
	public void run() {				//2,重寫run方法
		for(int i = 0; i < 1000; i++) {		//3,將要執行的程式碼寫在run方法中
			System.out.println("aaa");
		}
	}
}


多執行緒程式實現的方式2

2.實現Runnable
* 定義類實現Runnable介面
* 實現run方法
* 把新執行緒要做的事寫在run方法中
* 建立自定義的Runnable的子類物件
* 建立Thread物件, 傳入Runnable
* 呼叫start()開啟新執行緒, 內部會自動呼叫Runnable的run()方法

介面 Runnable

已知實現類: 
 Thread, TimerTask 
public interface RunnableRunnable
 介面應該由那些打算通過某一執行緒執行其例項的類來實現。
 類必須定義一個稱為 run 的無引數方法。 

void run()
使用實現介面 Runnable 的物件建立一個執行緒時,啟動該執行緒將導致在獨立執行的執行緒中呼叫物件的 run 方法。 
方法 run 的常規協定是,它可能執行任何所需的動作。 
public class c  {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MyRunnable mr = new MyRunnable();	//4,建立Runnable的子類物件
		//Runnable target = mr;	mr = 0x0011
		Thread t = new Thread(mr);		//5,將其當作引數傳遞給Thread的建構函式
		t.start();				//6,開啟執行緒
		
		for(int i = 0; i < 1000; i++) {
			System.out.println("bb");
		}
	}

}

class MyRunnable implements Runnable {		//1,定義一個類實現Runnable

	@Override
	public void run() {				//2,重寫run方法
		for(int i = 0; i < 1000; i++) {		//3,將要執行的程式碼寫在run方法中
			System.out.println("aaaaaaaaaaaa");
		}
	}
	
}


實現Runnable的原理
檢視原始碼
* 1,看Thread類的建構函式,傳遞了Runnable介面的引用 
* 2,通過init()方法找到傳遞的target給成員變數的target賦值
* 3,檢視run方法,發現run方法中有判斷,如果target不為null就會呼叫Runnable介面子類物件的run方法
多執行緒兩種方式的區別
* 檢視原始碼的區別:
* a.繼承Thread : 由於子類重寫了Thread類的run(), 當呼叫start()時, 直接找子類的run()方法
* b.實現Runnable : 建構函式中傳入了Runnable的引用, 成員變數記住了它, 
  start()呼叫run()方法時內部判斷成員變數Runnable的引用是否為空, 
    不為空編譯時看的是Runnable的run(),執行時執行的是子類的run()方法
* 繼承Thread
* 好處是:可以直接使用Thread類中的方法,程式碼簡單
* 弊端是:如果已經有了父類,就不能用這種方法
* 實現Runnable介面
* 好處是:即使自己定義的執行緒類有了父類也沒關係,因為有了父類也可以實現介面,而且介面是可以多實現的
* 弊端是:不能直接使用Thread中的方法需要先獲取到執行緒物件後,才能得到Thread的方法,程式碼複雜

多執行緒匿名內部類實現執行緒的兩種方式

public class d {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//繼承Thread類
		new Thread() {					//1,new 類(){}繼承這個類
			public void run() {			//2,重寫run方法
				for(int i = 0; i < 3000; i++) {	//3,將要執行的程式碼,寫在run方法中
					System.out.println("aaaaaa");
				}
			}
		}.start();					//4,開啟執行緒
		
		
		//實現Runnable介面
		new Thread(new Runnable(){		       //1,new類(new介面()){}實現這個介面
			@Override
			public void run() {			//2,重寫run方法
				for(int i = 0; i < 3000; i++) {	//3,將要執行的程式碼,寫在run方法中
					System.out.println("bb");
				}
			}				
		}).start(); 					//4,開啟執行緒
	}

}


多執行緒獲取名字和設定名字

public class e {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Thread() {		//預設名字Thread-0, ...1 2 3....					
			public void run() {
				System.out.println(this.getName() + "....aa");
			}
		}.start();
		
		new Thread("hello") {//通過構造方法給name賦值
			public void run() {
				System.out.println(this.getName() + "....bb");
				this.setName("java");
				System.out.println(this.getName() + "....cc");
			}
		}.start();
		
		
		
		Thread t1 = new Thread() {
			public void run() {
				System.out.println(this.getName() + "....ee");
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				this.setName("李四");
				System.out.println(this.getName() + "....ff");
			}
		};
		
		t1.setName("張三");
		t1.start();
		t2.start();
	}

}


public static Thread currentThread()
返回對當前正在執行的執行緒物件的引用。 
public class f {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Thread() {
			public void run() {
				System.out.println(getName() + "....aaa");
			}
		}.start();
		
		new Thread(new Runnable() {
			public void run() {
				//Thread.currentThread()獲取當前正在執行的執行緒
				System.out.println(Thread.currentThread().getName() + "...bbb");
			}
		}).start();
		
		////獲取主函式執行緒的引用,並獲取名字
		System.out.println(Thread.currentThread().getName());//main
		Thread.currentThread().setName("我是主執行緒");
		System.out.println(Thread.currentThread().getName());//沒有順序執行
	}

}

休眠執行緒

public static void sleep(long millis)throws InterruptedException
在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行),

public static void sleep(long millis,int nanos)throws InterruptedException
在指定的毫秒數加指定的納秒數內讓當前正在執行的執行緒休眠(暫停執行),
 控制當前執行緒休眠若干毫秒
* 1秒= 1000毫秒
*  1秒 = 1000 * 1000 * 1000納秒 1000000000
public class g {

	/**
	 * @param args
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		
		/*for(int i = 20; i >= 0; i--) {
			Thread.sleep(1000);
			System.out.println("倒計時第" +i + "秒");
		}*/
		
		//異常catch處理
		new Thread() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(getName() + "...aaaaaa");
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
					System.out.println(getName() + "...bbbbbb");
				}
			}
		}.start();
	}

}

守護執行緒

public final void setDaemon(boolean on)
將該執行緒標記為守護執行緒或使用者執行緒。
當正在執行的執行緒都是守護執行緒時,Java 虛擬機器退出。 
該方法必須在啟動執行緒前呼叫。  
(設定一個執行緒為守護執行緒, 該執行緒不會單獨執行, 當其他非守護執行緒都執行結束後, 自動退出)
引數:
on - 如果為 true,則將該執行緒標記為守護執行緒。 
public class h {

	/**
	 * @param args
	 * 守護執行緒
	 */
	public static void main(String[] args) {
		Thread t1 = new Thread() {
			public void run() {
				for(int i = 0; i < 2; i++) {
					System.out.println(getName() + "...aaaaaaaa");
				}
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				for(int i = 0; i < 50; i++) {
					System.out.println(getName() + "...bb");
				}
			}
		};
		
		t2.setDaemon(true);	
		//設定為守護執行緒,還有個緩衝時間
		
		t1.start();
		t2.start();

	}

}


加入執行緒

public final void join() throws InterruptedException
等待該執行緒終止。 

public final void join(long millis)throws InterruptedException
  等待該執行緒終止的時間最長為 millis 毫秒。超時為 0 意味著要一直等下去。     
public class i {

	/**
	 * @param args
	 * join(), 當前執行緒暫停, 等待指定的執行緒執行結束後, 當前執行緒再繼續
	 * join(int), 可以等待指定的毫秒之後繼續
	 * 匿名內部類在使用他方法中的區域性變數時,這個區域性變數必須用final修飾
	 */
	public static void main(String[] args) {
		final Thread t1 = new Thread() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					System.out.println(getName() + "...aaaa");
				}
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					if(i == 2) {
						try {
							t1.join();//插隊,加入
							//t1.join(1);//插隊指定的時間,過了指定時間後,兩條執行緒交替執行
						} catch (InterruptedException e) {
							
							e.printStackTrace();
						}
					}
					System.out.println(getName() + "...bb");
				}
			}
		};
		
		t1.start();
		t2.start();

	}

}


禮讓執行緒

public static void yield()
暫停當前正在執行的執行緒物件,並執行其他執行緒。   
public class j {

	/**
	 * yield讓出cpu禮讓執行緒
	 */
	public static void main(String[] args) {
		new My_Thread().start();
		new My_Thread().start();
	}

}

class My_Thread extends Thread {
	public void run() {
		for(int i = 1; i <= 1000; i++) {
			if(i % 10 == 0) {
				Thread.yield();	//讓出CPU
				System.out.println(getName() + "讓出cpu" );
			}
			System.out.println(getName() + "..." + i);
		}
	}
}


優先順序

 public final void setPriority(int newPriority)
 更改執行緒的優先順序。 
 預設為5,最小為1,最大為10
public class k {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Thread t1 = new Thread(){
			public void run() {
				for(int i = 0; i < 100; i++) {
					System.out.println(getName() + "...aaa" );
				}
			}
		};
		
		Thread t2 = new Thread(){
			public void run() {
				for(int i = 0; i < 100; i++) {
					System.out.println(getName() + "...bb" );
				}
			}
		};
		
		/*t1.setPriority(10);		//			設定最大優先順序
		t1.setPriority(1);*/
		t1.setPriority(Thread.MAX_PRIORITY);		//設定最小的執行緒優先順序
		t2.setPriority(Thread.MIN_PRIORITY);		//設定最大的執行緒優先順序
		t1.start();
		t2.start();
	}

}


多執行緒同步程式碼塊

* 1.什麼情況下需要同步
* 當多執行緒併發, 有多段程式碼同時執行時, 我們希望某一段程式碼執行的過程中CPU不要切換到其他執行緒工作. 這時就需要同步.
* 如果兩段程式碼是同步的, 那麼同一時間只能執行一段, 在一段程式碼沒執行結束之前, 不會執行另外一段程式碼.
* 2.同步程式碼塊
* 使用synchronized關鍵字加上一個鎖物件來定義一段程式碼, 這就叫同步程式碼塊
* 多個同步程式碼塊如果使用相同的鎖物件, 那麼他們就是同步的
public class L {

	/**
	 * @param args
	 * 同步程式碼塊
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		final Printer p = new Printer();
		/**
		 * 靜態只能呼叫靜態,我們是建立了外部類物件,然後在呼叫他的方法.所以不用靜態
		 * 但如果是內部類,必須靜態類...
		 * */
		new Thread() {
			public void run() {
				while(true) {
					p.print1();
				}
			}
		}.start();	
		
		new Thread() {
			public void run() {
				while(true) {
					p.print2();
				}
			}
		}.start();
		
	}
}

class Printer {
	
	Demo d = new Demo();
	public void print1(){
		//System.out.println("a");
		synchronized(d){		//同步程式碼塊,鎖機制,鎖物件可以是任意的
			////鎖物件可以是任意物件,但是被鎖的程式碼需要保證是同一把鎖,不能用匿名物件
			//因為匿名物件不是同一個物件
			System.out.print("你");
			System.out.print("好");
			System.out.print("世");
			System.out.print("界");
			System.out.print("\r\n");
			}
		}
	public void print2(){
		//System.out.println("b");
		synchronized(d){
			System.out.print("我");
			System.out.print("愛");
			System.out.print("吃");
			System.out.print("棒");
			System.out.print("棒");
			System.out.print("糖");
			System.out.print("\r\n");
		}
		
	}
}

class Demo{}//任意物件


3.使用synchronized關鍵字修飾一個方法, 該方法中所有的程式碼都是同步的
public class L {

	/**
	 * @param args
	 * 同步程式碼塊
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		final Printer p = new Printer();
		new Thread() {
			public void run() {
				while(true) {
					p.print1();
				}
			}
		}.start();	
		
		new Thread() {
			public void run() {
				while(true) {
					p.print2();
				}
			}
		}.start();
		
	}
}

class Printer {
	
	//非靜態的同步方法的鎖物件是什麼?
	//答:非靜態的同步方法的鎖物件是this
	//靜態的同步方法的鎖物件是什麼?
	//是該類的位元組碼物件
	public synchronized void print1(){	//同步方法只需要在方法上加synchronized關鍵字即可
		////鎖物件不能用匿名物件,因為匿名物件不是同一個物件
		System.out.print("你");
		System.out.print("好");
		System.out.print("世");
		System.out.print("界");
		System.out.print("\r\n");
	
		}
	public void print2(){
		synchronized(this){
			System.out.print("我");
			System.out.print("愛");
			System.out.print("吃");
			System.out.print("棒");
			System.out.print("棒");
			System.out.print("糖");
			System.out.print("\r\n");
		}
	}
}

class Demo{}//任意物件


多執行緒安全問題

* 多執行緒併發操作同一資料時, 就有可能出現執行緒安全問題
* 使用同步技術可以解決這種問題, 把操作資料的程式碼進行同步, 不要多個執行緒一起操作
* 
 需求:鐵路售票,一共100張,通過四個視窗賣完.

public class m {

	/**
	 * 需求:鐵路售票,一共100張,通過四個視窗賣完.
	 * 通過四個執行緒出售
	 */
	public static void main(String[] args) {
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
	}

}

class Ticket extends Thread {
	private static int ticket = 100;//加static,所有物件共享這100張票
	//private Object obj = new Object();//相當於成員變數,前加static才能做鎖物件	
	//private static Object obj = new Object();	//如果用引用資料型別成員變數當作鎖物件,必須是靜態的
	public void run() {
		while(true) {
			synchronized(Ticket.class) {//加this不行,每new一次都是一個新的物件新的鎖
				if(ticket <= 0) {
					break;
				}
				
				try {
					Thread.sleep(10);	//執行緒1睡,執行緒2睡,執行緒3睡,執行緒4睡
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(getName() + "...這是第" + ticket-- + "號票");
			}
		}
	}
}


public class n {

	/**
	 * @param args
	 * 火車站賣票的例子用實現Runnable介面
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyTicket mt = new MyTicket();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start();
		
		
		/*Thread t1 = new Thread(mt);	//多次啟動一個執行緒是非法的
		t1.start();
		t1.start();
		t1.start();
		t1.start();*/
	}

}

class MyTicket implements Runnable {
	private int tickets = 100;//不用static,他不用建立多個物件,只需當做引數傳遞
	@Override
	public void run() {
		while(true) {
			//synchronized(Ticket.class),同理,鎖物件也可以用this
			synchronized(this) {
				if(tickets <= 0) {
					break;
				}
				try {
					Thread.sleep(10);	//執行緒1睡,執行緒2睡,執行緒3睡,執行緒4睡
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "...這是第" + tickets-- + "號票");
			}
		}
	}
}


public class o {

	/**
	 * @param args
	 * 需求:鐵路售票,一共100張,通過四個視窗賣完.
	 */
	public static void main(String[] args) {
		TicketsSeller t1 = new TicketsSeller();
		TicketsSeller t2 = new TicketsSeller();
		TicketsSeller t3 = new TicketsSeller();
		TicketsSeller t4 = new TicketsSeller();
		
		t1.setName("視窗1");
		t2.setName("視窗2");
		t3.setName("視窗3");
		t4.setName("視窗4");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

}

class TicketsSeller extends Thread {
	private static int tickets = 100;
	static Object obj = new Object();
	public TicketsSeller() {
		super();
		
	}
	public TicketsSeller(String name) {
		super(name);
	}
	public void run() {
		while(true) {
			synchronized(obj) {
				if(tickets <= 0) 
					break;
				try {
					Thread.sleep(10);//執行緒1睡,執行緒2睡,執行緒3睡,執行緒4睡
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				System.out.println(getName() + "...這是第" + tickets-- + "號票");
			}
		}
	}
}


多執行緒死鎖

 多執行緒同步的時候, 如果同步程式碼巢狀, 使用相同鎖, 就有可能出現死鎖
* 儘量不要巢狀使用
public class p {

	/**
	 * @param args
	 */
	private static String s1 = "筷子左";
	private static String s2 = "筷子右";

	public static void main(String[] args) {
		new Thread() {
			public void run() {
				while(true) {
					synchronized(s1) {
						System.out.println(getName() + "...獲取" + s1 + "等待" + s2);
						synchronized(s2) {
							System.out.println(getName() + "...拿到" + s2 + "開吃");
						}
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					synchronized(s2) {
						System.out.println(getName() + "...獲取" + s2 + "等待" + s1);
						synchronized(s1) {
							System.out.println(getName() + "...拿到" + s1 + "開吃");
						}
					}
				}
			}
		}.start();
	}
}


回顧以前說過的執行緒安全問題(面試常考)
* 看原始碼:Vector,StringBuffer,Hashtable,
* Collections.synchroinzed(xxx)方法,同步集合使其執行緒安全
* Vector是執行緒安全的,ArrayList是執行緒不安全的
* StringBuffer是執行緒安全的,StringBuilder是執行緒不安全的
* Hashtable是執行緒安全的,HashMap是執行緒不安全的