1. 程式人生 > >24 多執行緒(上)

24 多執行緒(上)

24.01_多執行緒(多執行緒的引入)(瞭解)

  • 1.什麼是執行緒
    • 執行緒是程式執行的一條路徑, 一個程序中可以包含多條執行緒
    • 多執行緒併發執行可以提高程式的效率, 可以同時完成多項工作
  • 2.多執行緒的應用場景
    • 紅蜘蛛同時共享螢幕給多個電腦
    • 迅雷開啟多條執行緒一起下載
    • QQ同時和多個人一起視訊
    • 伺服器同時處理多個客戶端請求

24.02_多執行緒(多執行緒並行和併發的區別)(瞭解)

  • 並行就是兩個任務同時執行,就是甲任務進行的同時,乙任務也在進行。(需要多核CPU)
  • 併發是指兩個任務都請求執行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行,由於時間間隔較短,使人感覺兩個任務都在執行。
  • 比如我跟兩個網友聊天,左手操作一個電腦跟甲聊,同時右手用另一臺電腦跟乙聊天,這就叫並行。
  • 如果用一臺電腦我先給甲發個訊息,然後立刻再給乙發訊息,然後再跟甲聊,再跟乙聊。這就叫併發。

24.03_多執行緒(Java程式執行原理和JVM的啟動是多執行緒的嗎)(瞭解)

  • A:Java程式執行原理

    • Java命令會啟動java虛擬機器,啟動JVM,等於啟動了一個應用程式,也就是啟動了一個程序。該程序會自動啟動一個 “主執行緒” ,然後主執行緒去呼叫某個類的 main 方法。
  • B:JVM的啟動是多執行緒的嗎

    • JVM啟動至少啟動了垃圾回收執行緒和主執行緒,所以是多執行緒的。

24.04_多執行緒(多執行緒程式實現的方式1)(掌握)

  • 1.繼承Thread

    • 定義類繼承Thread
    • 重寫run方法
    • 把新執行緒要做的事寫在run方法中
    • 建立執行緒物件
    • 開啟新執行緒, 內部會自動執行run方法
      	public class Demo2_Thread {
      
      		/**
      		 * @param args
      		 */
      		public static void main(String[] args) {
      			MyThread mt = new MyThread();							//4,建立自定義類的物件
      			mt.start();												//5,開啟執行緒
      			
      			for(int i = 0; i < 3000; i++) {
      				System.out.println("bb");
      			}
      		}
      	
      	}
      	class MyThread extends Thread {									//1,定義類繼承Thread
      		public void run() {											//2,重寫run方法
      			for(int i = 0; i < 3000; i++) {							//3,將要執行的程式碼,寫在run方法中
      				System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
      			}
      		}
      	}
    

24.05_多執行緒(多執行緒程式實現的方式2)(掌握)

  • 2.實現Runnable
    • 定義類實現Runnable介面

    • 實現run方法

    • 把新執行緒要做的事寫在run方法中

    • 建立自定義的Runnable的子類物件

    • 建立Thread物件, 傳入Runnable

    • 呼叫start()開啟新執行緒, 內部會自動呼叫Runnable的run()方法

        public class Demo3_Runnable {
        	/**
        	 * @param args
        	 */
        	public static void main(String[] args) {
        		MyRunnable mr = new MyRunnable();						//4,建立自定義類物件
        		//Runnable target = new MyRunnable();
        		Thread t = new Thread(mr);								//5,將其當作引數傳遞給Thread的建構函式
        		t.start();												//6,開啟執行緒
        		
        		for(int i = 0; i < 3000; i++) {
        			System.out.println("bb");
        		}
        	}
        }
        
        class MyRunnable implements Runnable {							//1,自定義類實現Runnable介面
        	@Override
        	public void run() {											//2,重寫run方法
        		for(int i = 0; i < 3000; i++) {							//3,將要執行的程式碼,寫在run方法中
        			System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        		}
        	}
        	
        }
      

24.06_多執行緒(實現Runnable的原理)(瞭解)

  • 檢視原始碼
    • 1,看Thread類的建構函式,傳遞了Runnable介面的引用
    • 2,通過init()方法找到傳遞的target給成員變數的target賦值
    • 3,檢視run方法,發現run方法中有判斷,如果target不為null就會呼叫Runnable介面子類物件的run方法

24.07_多執行緒(兩種方式的區別)(掌握)

  • 檢視原始碼的區別:

    • a.繼承Thread : 由於子類重寫了Thread類的run(), 當呼叫start()時, 直接找子類的run()方法
    • b.實現Runnable : 建構函式中傳入了Runnable的引用, 成員變數記住了它, start()呼叫run()方法時內部判斷成員變數Runnable的引用是否為空, 不為空編譯時看的是Runnable的run(),執行時執行的是子類的run()方法
  • 繼承Thread

    • 好處是:可以直接使用Thread類中的方法,程式碼簡單
    • 弊端是:如果已經有了父類,就不能用這種方法
  • 實現Runnable介面

    • 好處是:即使自己定義的執行緒類有了父類也沒關係,因為有了父類也可以實現介面,而且介面是可以多實現的
    • 弊端是:不能直接使用Thread中的方法需要先獲取到執行緒物件後,才能得到Thread的方法,程式碼複雜

24.08_多執行緒(匿名內部類實現執行緒的兩種方式)(掌握)

  • 繼承Thread類

      new Thread() {													//1,new 類(){}繼承這個類
      	public void run() {											//2,重寫run方法
      		for(int i = 0; i < 3000; i++) {							//3,將要執行的程式碼,寫在run方法中
      			System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
      		}
      	}
      }.start();
    
  • 實現Runnable介面

      new Thread(new Runnable(){										//1,new 介面(){}實現這個介面
      	public void run() {											//2,重寫run方法
      		for(int i = 0; i < 3000; i++) {							//3,將要執行的程式碼,寫在run方法中
      			System.out.println("bb");
      		}
      	}
      }).start(); 
    

匿名內部類相當於該類的子類物件

24.09_多執行緒(獲取名字和設定名字)(掌握)

  • 1.獲取名字
    • 通過getName()方法獲取執行緒物件的名字
  • 2.設定名字
    • 通過建構函式可以傳入String型別的名字
    •   new Thread("xxx") {   //通過構造方法給執行緒設定名字
        	public void run() {
        		for(int i = 0; i < 1000; i++) {
        			System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");  //執行緒有一個預設的名字,Thread-0
        		}
        	}
        }.start();
        
        new Thread("yyy") {
        	public void run() {
        		for(int i = 0; i < 1000; i++) {
        			System.out.println(this.getName() + "....bb");
        		}
        	}
        }.start(); 
      
    • 通過setName(String)方法可以設定執行緒物件的名字
    •   Thread t1 = new Thread() {   //父類引用指向子類物件
        	public void run() {
        		for(int i = 0; i < 1000; i++) {
        			System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
        		}
        	}
        };
        
        Thread t2 = new Thread() {
        	public void run() {
        		for(int i = 0; i < 1000; i++) {
        			System.out.println(this.getName() + "....bb");
        		}
        	}
        };
        t1.setName("芙蓉姐姐");   //通過setName(String)方法可以設定執行緒物件的名字
        t2.setName("鳳姐");
        
        t1.start();
        t2.start();
      

24.10_多執行緒(獲取當前執行緒的物件)(掌握)

  • Thread.currentThread(),獲取當前執行緒,主執行緒也可以獲取
public static void main(String[] args) {
		new Thread() {
			public void run() {
				System.out.println(getName() + "....aaaaaa");
			}
		}.start();
		
		
		new Thread(new Runnable() {
			public void run() {
				//Thread.currentThread()獲取當前正在執行的執行緒
				System.out.println(Thread.currentThread().getName() + "...bb");
			}
		}).start();
		
		Thread.currentThread().setName("我是主執行緒");
		System.out.println(Thread.currentThread().getName());//獲取主執行緒的名字
	}

24.11_多執行緒(休眠執行緒)(掌握)

sleep()是Thread類中的靜態方法,可以直接類名.呼叫

  • Thread.sleep(毫秒,納秒), 控制當前執行緒休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000納秒 1000000000

      	new Thread() {
      		public void run() {
      			for(int i = 0; i < 10; i++) {
      				System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
      				try {
      					Thread.sleep(10);
      				} catch (InterruptedException e) {
      					e.printStackTrace();
      				}
      			}
      		}
      	}.start();
      	
      	new Thread() {
      		public void run() {
      			for(int i = 0; i < 10; i++) {
      				System.out.println(getName() + "...bb");
      				try {
      					Thread.sleep(10);
      				} catch (InterruptedException e) {
      					e.printStackTrace();
      				}
      			}
      		}
      	}.start();
    

24.12_多執行緒(守護執行緒)(掌握)

  • setDaemon(), 設定一個執行緒為守護執行緒, 該執行緒不會單獨執行, 當其他非守護執行緒都執行結束後, 自動退出
    •   Thread t1 = new Thread() {
        	public void run() {
        		for(int i = 0; i < 50; i++) {
        			System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
        			try {
        				Thread.sleep(10);
        			} catch (InterruptedException e) {
        				e.printStackTrace();
        			}
        		}
        	}
        };
        
        Thread t2 = new Thread() {
        	public void run() {
        		for(int i = 0; i < 5; i++) {
        			System.out.println(getName() + "...bb");
        			try {
        				Thread.sleep(10);
        			} catch (InterruptedException e) {
        				e.printStackTrace();
        			}
        		}
        	}
        };
        
        t1.setDaemon(true);  //將t1設定為守護執行緒,當t2執行完後t1就自動結束
        
        t1.start();
        t2.start();
      

24.13_多執行緒(加入執行緒)(掌握)

  • join(), 當前執行緒暫停, 等待指定的執行緒執行結束後, 當前執行緒再繼續
  • join(int), 可以等待指定的毫秒之後繼續
    •   final Thread t1 = new Thread() {   //匿名內部類使用該方法中的區域性變數時要使用final進行修飾,t1.join()處在第
                                                           //二個匿名內部類中,且t1是區域性變數,所以要用final修飾
        	public void run() {
        		for(int i = 0; i < 50; i++) {
        			System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
        			try {
        				Thread.sleep(10);
        			} catch (InterruptedException e) {
        				e.printStackTrace();
        			}
        		}
        	}
        };
        
        Thread t2 = new Thread() {
        	public void run() {
        		for(int i = 0; i < 50; i++) {
        			if(i == 2) {
        				try {
        					//t1.join();			//t1執行緒插隊,加入
        					t1.join(30);			//插隊30毫秒,有固定的時間,過了固定時間,插隊完後繼續交替執行
        					Thread.sleep(10);
        				} catch (InterruptedException e) {
        					
        					e.printStackTrace();
        				}
        			}
        			System.out.println(getName() + "...bb");
        		
        		}
        	}
        };
        
        t1.start();
        t2.start();
      

24.14_多執行緒(禮讓執行緒)(瞭解)

  • Thread類中的靜態方法yield(),讓出cpu
public static void main(String[] args) {
		new MyThread().start();
		new MyThread().start();
	}

}

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

24.15_多執行緒(設定執行緒的優先順序)(瞭解)

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

24.16_多執行緒(同步程式碼塊)(掌握)

  • 1.什麼情況下需要同步
    • 當多執行緒併發, 有多段程式碼同時執行時, 我們希望某一段程式碼執行的過程中CPU不要切換到其他執行緒工作. 這時就需要同步.
    • 如果兩段程式碼是同步的, 那麼同一時間只能執行一段, 在一段程式碼沒執行結束之前, 不會執行另外一段程式碼.
  • 2.同步程式碼塊
    • 使用synchronized關鍵字加上一個鎖物件來定義一段程式碼, 這就叫同步程式碼塊
    • 多個同步程式碼塊如果使用相同的鎖物件, 那麼他們就是同步的
public static void main(String[] args) {
		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() {
		//synchronized(new Demo()) {							//同步程式碼塊,鎖機制,鎖物件可以是任意的
		synchronized(d) {
			System.out.print("黑");
			System.out.print("馬");
			System.out.print("程");
			System.out.print("序");
			System.out.print("員");
			System.out.print("\r\n");
		}
	}
	
	public void print2() {
		//synchronized(new Demo()) {							//鎖物件不能用匿名物件,因為匿名物件不是同一個物件
		synchronized(d) {		
			System.out.print("傳");
			System.out.print("智");
			System.out.print("播");
			System.out.print("客");
			System.out.print("\r\n");
		}
	}

class Demo{}

24.17_多執行緒(同步方法)(掌握)

  • 使用synchronized關鍵字修飾一個方法, 該方法中所有的程式碼都是同步的
class Printer2 {
	//非靜態的同步方法的鎖物件是神馬?
	//答:非靜態的同步方法的鎖物件是this
	//靜態的同步方法的鎖物件是什麼?
	//是該類的位元組碼物件
	public static synchronized void print1() {							//同步方法只需要在方法上加synchronized關鍵字即可
		System.out.print("黑");
		System.out.print("馬");
		System.out.print("程");
		System.out.print("序");
		System.out.print("員");
		System.out.print("\r\n");
	}
	
	public static void print2() {
		synchronized(Printer2.class) {		//該類的位元組碼物件
			System.out.print("傳");
			System.out.print("智");
			System.out.print("播");
			System.out.print("客");
			System.out.print("\r\n");
		}
	}
}

24.18_多執行緒(執行緒安全問題)(掌握)

  • 多執行緒併發操作同一資料時, 就有可能出現執行緒安全問題
  • 使用同步技術可以解決這種問題, 把操作資料的程式碼進行同步, 不要多個執行緒一起操作
/**
	 * 需求:鐵路售票,一共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;  //靜態變數,讓這幾個執行緒共享同一個變數
	//private static Object obj = new Object();		//如果用引用資料型別成員變數當作鎖物件,必須是靜態的
	public void run() {
		while(true) {
			synchronized(Ticket.class) {  //多執行緒共同修改同一變數ticket,應使用同步
				if(ticket <= 0) {
					break;
				}
				try {
					Thread.sleep(10);				//執行緒1睡,執行緒2睡,執行緒3睡,執行緒4睡
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				System.out.println(getName() + "...這是第" + ticket-- + "號票");
			}
		}
	}
}

24.19_多執行緒(火車站賣票的例子用實現Runnable介面)(掌握)

	/**
	 * @param args
	 * 火車站賣票的例子用實現Runnable介面
	 */
	public static void main(String[] args) {
		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;      //只例項化了一次物件,四條執行緒用的是同一物件,所以用的也是這同一個變數
	@Override
	public void run() {
		while(true) {
			synchronized(this) {    //只例項化了一次物件,四條執行緒用的是同一物件,所以可以用本類物件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-- + "號票");
			}
		}
	}
}

24.20_多執行緒(死鎖)(瞭解)

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

        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();
        }
      

24.21_多執行緒(以前的執行緒安全的類回顧)(掌握)

  • A:回顧以前說過的執行緒安全問題
    • 看原始碼:Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
    • Vector是執行緒安全的,ArrayList是執行緒不安全的
    • StringBuffer是執行緒安全的,StringBuilder是執行緒不安全的
    • Hashtable是執行緒安全的,HashMap是執行緒不安全的
    • Collections.synchroinzed(xxx)可以將執行緒不安全的變成執行緒安全的