學Java第二十天(線程完整篇)

知識點

1.線程概述

1.1 什麼是線程
 線程是程序執行的一條路徑, 一個進程中可以包含多條線程
 一個應用程序可以理解成就是一個進程
 多線程併發執行可以提高程序的效率, 可以同時完成多項工作

1.2 多線程應用場景
 VNC同時共享屏幕給多個電腦
 迅雷開啓多條線程一起下載
 QQ同時和多個人一起視頻
 服務器同時處理多個客戶端請求

1.3並行和併發的區別
 並行就是兩個任務同時運行,就是甲任務進行的同時,乙任務也在進行。(需要多核CPU)
 併發是指兩個任務都請求運行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行,由於間時間隔較短,使人感覺兩個任務都在運行(畫圖-任務調度)。
在這裏插入圖片描述
在這裏插入圖片描述
1.4 Java程序運行原理
 Java命令會啓動java虛擬機(JVM),等於啓動了一個應用程序,也就是啓動了一個進程。
 該進程會自動啓動一個 “主線程” ,然後主線程去調用某個類的 main 方法
 一個應用程序有且只有一個主線程,程序員不能New主線程,可以New子線程。

1.5 JVM啓動的是多線程嗎?
 JVM啓動至少啓動了垃圾回收線程和主線程,所以是多線程的。
 main方法的代碼執行的位置就是在主線程(路徑)
 一個進程有多個線程
 finalize()這個方法在子線程(垃圾回收線程)執行

public class Demo01 {
	public static void main(String[] args) {
/*JVM的啓動是多線程的嗎?【面試題】*/
		
		System.out.println("AAAAA");
		System.out.println("BBBBB");
		System.out.println("CCCCC");
		System.out.println("DDDDD");
		
		//打印線程名稱
		System.out.println(Thread.currentThread());//主線程
		
		for(int i = 0;i<2;i++){
			new Student();
			System.gc();//啓動垃圾回收
		}
	}
}
class Student{
	//被垃圾回收器回收時,會調用
	//對象從內存釋放時,會調用 
	@Override
	protected void finalize() throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("student 被回收了...");
		//打印線程名稱
		System.out.println(Thread.currentThread());//子線程
	}
}

2.Java中線程的實現方式(重點)

2.1方式一、繼承Thread
使用步驟:
1.定義類繼承Thread
2.重寫run方法
3.把新線程要做的事寫在run方法中
4.創建線程對象
5.開啓新線程, 內部會自動執行run方法

public class Demo01 {
	public static void main(String[] args) {
		/*主線程,程序員不能創建,程序員只能創建子線程*/
		
		//1.創建子線程對象
		MyThread t1 = new MyThread();
		
		/**不能通過下面的方式來執行任務
		 * 因爲這種試的任務是在主線程執行的*/
		//t1.run();
		
		//2.正確的執行任務的方式,調用start,內部會開啓新線程,調用run方法
		t1.start();
		
		//3.再創建子線程
		MyThread t2 = new MyThread();
		t2.start();
				
		//4.循環創建子線程
		for(int i=0;i<10;i++){
			MyThread th = new MyThread();
			th.start();
		}
		
	}

}

class MyThread extends Thread{
	
	@Override
	public void run() {
		System.out.println("銀行信用卡還款短信任務..." + Thread.currentThread());
	
		System.out.println("線程名稱" + this.getName());
	}
}


2.2方式二、實現Runnable接口
實現步驟:
1.定義類實現Runnable接口
2.實現run方法
3.把新線程要做的事寫在run方法中
4.創建自定義的Runnable的子類對象,創建Thread對象傳入Runnable
5.調用start()開啓新線程, 內部會自動調用Runnable的run()方法

public class Demo01 {
	public static void main(String[] args) {
/*		線程實現的方式 (2) - 定義類實現Runnable接口
		//1.創建runable對象
		BankTask task = new BankTask();
		
		//2.創建Thread對象
		Thread t1 = new Thread(task);
		
		//3.啓動線程
		t1.start();
		
		//4.再開啓2個線程
		Thread t2 = new Thread(task);
		t2.start();
		
		Thread t3 = new Thread(task);
		t3.start();
	}
}

class BankTask  implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("銀行儲蓄卡自動結算利息任務..." + Thread.currentThread());
		
		//System.out.println("線程名稱:" + this.getName());
		System.out.println("線程名稱:" +Thread.currentThread().getName());
	}
	
}

2.3兩種方式的區別
區別:
 繼承Thread : 由於子類重寫了Thread類的run(), 當調用start()時直接找子類的run()方法
 實現Runnable : 構造函數中傳入了Runnable的引用, 有個成員變量記住了它, 調用run()方法時內部判斷成員變量Runnable的引用是否爲空。
在這裏插入圖片描述
繼承Thread
 好處是:可以直接使用Thread類中的方法,代碼簡單
 弊端是:如果已經有了父類,就不能用這種方法
實現Runnable接口
 好處是:即使自己定義的線程類有了父類也沒關係,因爲有了父類也可以實現接口,代碼更靈活
 弊端是:不能直接使用Thread中的方法,需要先獲取到線程對象後,才能得到Thread的方法,代碼複雜
在這裏插入圖片描述
在這裏插入圖片描述
2.4 匿名內部類實現線程的兩種方式

public static void main(String[] args) {
		//匿名內部類實現線程的兩種方式		
		/*Thread t1 = new Thread(){
			@Override
			public void run() {
				System.out.println("任務1...." + Thread.currentThread());
			}
		};
		t1.start();*/
		
		new Thread(){
			public void run() {
				System.out.println("任務1...." + Thread.currentThread());
			};
		}.start();
		
		
		/*Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("任務2...." + Thread.currentThread());
			}
		});
		t2.start();*/
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("任務2...." + Thread.currentThread());
			}
		}).start();
	}

2.5 獲取線程名字和設置名字
 通過Thread的getName()方法獲取線程對象的名字
 通過setName(String)方法可以設置線程對象的名字
 通過構造函數可以傳入String類型的名字
 每個線程系統都會默認分配個名字,主線程:main,子線程thread-0 …

public class Demo01 {
	public static void main(String[] args) {
/*		獲取線程名字和設置名字(掌握) 
		//1.獲取主線程對象
		Thread mainThread = Thread.currentThread();
		System.out.println(Thread.currentThread());
		System.out.println(mainThread);
		System.out.println("名稱:" + mainThread.getName());
		
		//2.設置線程的名稱
		mainThread.setName("主線程");
		System.out.println(mainThread);
		
		//3.設置子線程的名稱
		MyThread myThread = new MyThread("子線程1");
		myThread.start();
	}
}

class MyThread extends Thread{
	
	public MyThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		System.out.println("銀行代發工資任務..." + Thread.currentThread());
	}
}
 

在這裏插入圖片描述
2.6 獲取當前線程的對象
 Thread.currentThread()方法用於獲取當前線程對象
 在不同的方法中,獲取的線程對象名稱是有可能不一樣的
 在main中獲取的是主線程對象
 在子線程的run方法中獲取的是子線程對象

public class Demo01 {

	public static void main(String[] args) {
		//獲取當前線程的對象(掌握)
		Thread mainThread = Thread.currentThread();
		mainThread.setName("主線程");
		//打印主線程對象
		System.out.println(mainThread);
		
		//打印主線程對象類名
		System.out.println(mainThread.getClass());
		
		System.out.println("================");
		//開啓子線程
		MyThread mt = new MyThread();
		mt.start();
	}
}

class MyThread extends Thread{
	@Override
	public void run() {
		System.out.println("任務...");
		Thread subThread = Thread.currentThread();
		//打印子線程對象
		System.out.println(subThread);
		//打印子線程對象類名
		System.out.println(subThread.getClass().getName());
		
	}
}
 

在這裏插入圖片描述

3.線程的其它方法

3.1線程休眠(掌握)

 Thread.sleep(毫秒), 控制當前線程休眠若干毫秒
 1秒= 1000毫秒
 1秒 = 1000毫秒* 1000微妙 * 1000納秒(1000000000 )
主線程休眠

/*** 主線程休眠 */
	public static void test1() {
		for(int i=0;i<10;i++){
			System.out.println(i);
			//休眠【暫停】
			try {
				Thread.sleep(1000);//主線程休眠
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("AAAAAAAAAAAAAAAAAA");
	}

子線程休眠

/**
	 * 子線程休眠
	 */
	public static void test2() {
		//子線程休眠
		new Thread(){
			public void run() {
				for(int i=0;i<10;i++){
					System.out.println(Thread.currentThread() + " " + i);
					//休眠
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
		

		System.out.println("AAAAAAAAAAAAAAAA");
	}

3.2守護線程(瞭解)

 setDaemon(), 設置一個線程爲守護線程, 該線程不會單獨執行, 當其他非守護線程都執行結束後, 自動退出
 特點:男守護女,女的死,男的也不想活了

3.3加入線程(瞭解)

 join(), 當前線程暫停, 等待指定的線程執行結束後, 當前線程再繼續
 join(int), 可以等待指定的毫秒之後繼續

3.4線程讓出(瞭解)

 yield() 讓出cpu

3.5線程優先級

 setPriority()設置線程的優先級
 默認優先級是5,最小優先級1,最高優先級10
 可以設置2,3,4
 Thread裏面有靜態常量
 開發幾乎不用,瞭解
在這裏插入圖片描述

4.線程與同步(重點)

什麼是同步
 同步就是加鎖,不讓其它人訪問
 synchronized指的就是同步的意思
什麼情況下需要同步
 當多線程併發, 有多段代碼同時執行時, 我們希望某一段代碼執行的過程中CPU不要切換到其他線程工作. 這時就需要同步.
 如果兩段代碼是同步的, 那麼同一時間只能執行一段, 在一段代碼沒執行結束之前, 不會執行另外一段代碼.
同步代碼塊
 使用synchronized關鍵字加上一個鎖對象來定義一段代碼, 這就叫同步代碼塊
 多個同步代碼塊如果使用相同的鎖對象, 那麼他們就是同步的
在這裏插入圖片描述
同步方法
 使用synchronized關鍵字修飾一個方法, 該方法中所有的代碼都是同步的
 非靜態同步函數的鎖是:this
 靜態同步函數的鎖是:字節碼對象(xx.class)

5.鎖的總結

1.鎖問題:

同步中,鎖最好同一個對象,如果不是同一對象,還是會有線程安全問題
鎖:this,代表當前對象
鎖:如果 new 對象,就不是同一把鎖
鎖:字節碼對象 String.class,內存中,只有一個字節碼對象
開發中:一般都是this

2.在方法內部聲明synchronized的就是 “同步代碼塊”

3.在聲明方法的時候,添加 synchronized,就是同步方法
》如果是非靜態方法,鎖就是this
》如果是靜態方法,鎖就當前類的字節碼對象

//TicketTask.class
public static synchronized void test1(){}

4.同步使用的建議:

同步加鎖的時候,儘量讓鎖住的代碼範圍小一點,這樣可以讓其它線程等待時間少一點,性能高

6.死鎖

 死鎖就是大家都抱着鎖,不釋放

public class Demo01 {

	private static String s1 = "筷子左";
	private static String s2 = "筷子右";
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		//死鎖(瞭解)
		//多線程同步的時候, 如果同步代碼嵌套, 使用相同鎖, 就有可能出現死鎖
		
		new Thread(){
			public void run() {
				while(true){
					synchronized (s1) {
						System.out.println("線程A 拿到" + s1 + " 等待" + s2);
						synchronized (s2) {
							System.out.println("線程A 拿到" + s2 + " 開動喫飯...");
						}
					}
				}
				
			};
		}.start();
		
		new Thread(){
			public void run() {
				while(true){
					synchronized (s2) {
						System.out.println("線程B 拿到" + s2 + " 等待" + s1);
						synchronized (s1) {
							System.out.println("線程B 拿到" + s1 + " 開動喫飯。。");
						}
					}
				}
			};
		}.start();
	}

}

7.回顧線程安全的類

 Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
 Vector是線程安全的,ArrayList是線程不安全的
 StringBuffer是線程安全的,StringBuilder是線程不安全的
 Hashtable是線程安全的,HashMap是線程不安全的

8.單例設計模式(重點)

8.1什麼是單例

 保證類在內存中只有一個對象。
 對象是new出來的,因此也就是說在程序中只能new一次對象

8.2單例實現的基本步驟

1》聲明一個類,類中有一個靜態屬性,類型與類名相同
2》把空參構造方法聲明爲私有
3》在類中提供一個公共靜態訪問方法來返回該對象實例

8.3單例的多種寫法

寫法一 餓漢式

class Singleton{
	private static Singleton instance = new Singleton();
	private Singleton(){}
	public static Singleton getInstance(){
		return instance;
	}
}

寫法二 懶漢式

class Singleton{
	private static Singleton instance;
	
	private Singleton(){}
	
	public static Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		}
		return instance;
	}
}

寫法三 另一種簡單

class Singleton{
	public static final Singleton instance = new Singleton();
	private Singleton(){}
}

8.4餓漢式和懶漢式的區別

 餓漢式是空間換時間,懶漢式是時間換空間
 在多線程訪問時,餓漢式不會創建多個對象,而懶漢式有可能會創建多個對象
 如果考慮線程安全問題,用餓漢式
 如果不考慮線程安全問題,用懶漢式

8.5Runtime類的使用

 Runtime類是一個單例類
 每個 Java 應用程序都有一個 Runtime 類實例,使應用程序能夠與其運行的環境相連接。通過 getRuntime 方法獲取當前運行時。
 案例:自動關機
Runtime r = Runtime.getRuntime();
r.exec(“shutdown -s -t 300”);//300秒後關機
r.exec(“shutdown -a”); //取消關機

9. Timer定時器

 Timer一種工具,用於在後臺線程中執行的任務。可安排任務執行一次,或者定期重複執行。
 方法
public void schedule(TimerTask task, long delay)
public void schedule(TimerTask task, long delay, long period)
public void schedule(TimerTask task, Date firstTime, long period)

public class Demo01 {

	public static void main(String[] args) {
		//Timer(計時器,定時器)
/*		一種工具,用於在後臺線程中執行的任務。可安排任務執行一次,或者定期重複執行。
		方法
		public void schedule(TimerTask task, long delay)
		public void schedule(TimerTask task, long delay, long period) 
		指定時間執行任務
		public void schedule(TimerTask task, Date firstTime, long period)*/

		
		test3();

	}

	public static void test3() {
		/**定時器的細節
		 * 1.定時器在子線程中執行
		 * 2.timer.cancel(); 取消定時器
		 */
		
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
			int count = 5;
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("任務A:" + count +"..." + Thread.currentThread());
				count --;
				if(count == 0){
					//取消定時器
					timer.cancel();
				}
			}
		}, 1000,2000);
		
		//timer.cancel();//主線程
	}

	public static void test2() {
		//3秒後執行任務,每隔兩秒重複執行任務
		Timer timmer = new Timer();
		timmer.schedule(new TimerTask() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("任務A....." + new Date());
				
			}
		}, 3000, 2000);
	}

	//3秒後執行任務
	public static void test1() {
		//1.創建定時器
		Timer timmer = new Timer();
		
		//2.執行任務
		/**
		 * 1.3秒後執行任務
		 * 2.任務執行完後,程序沒有退出
		 */
		timmer.schedule(new TimerTask() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("任務A....");
				
			}
		}, 3000);
	}
}

10.線程間的通訊(重點)

10.1什麼時候需要通信

多個線程併發執行時, 在默認情況下CPU是隨機切換線程的,如果我們希望他們有規律的執行, 就可以使用通信, 例如每個線程執行一次打印

10.2線程怎麼通信

》如果希望線程等待, 就調用wait()
》如果希望喚醒等待的線程, 就調用notify();
notify是隨機喚醒一個線程
notifyAll是喚醒所有線程
》這兩個方法必須在同步代碼中執行, 並且使用同步鎖對象來調用
》如果方法中沒有同步鎖,會有異常IllegalMonitorStateException

10.3線程通訊的一些疑問

1.在同步代碼塊中,用哪個對象鎖,就用哪個對象調用wait方法
2.爲什麼wait方法和notify方法定義在Object這類中?
因爲鎖對象可以是任意對象,Object是所有的類的基類,所以wait方法和notify方法需要定義在Object這個類中
3.sleep方法和wait方法的區別?
》sleep方法必須傳入參數,參數就是時間,時間到了自動醒來
》wait方法可以傳入參數也可以不傳入參數,傳入參數就是在參數的時間結束後等待,不傳入參數就是直接等待
》sleep方法在同步函數或同步代碼塊中,不釋放鎖,睡着了也抱着鎖睡
》wait方法在同步函數或者同步代碼塊中,釋放鎖

11.JDK1.5新特性互斥鎖(重點)

11.1ReentrantLock介紹

 使用ReentrantLock類也可以實現同步加鎖
 ReentrantLock叫[互斥鎖],使用lock()和unlock()方法進行同步

11.2使用ReentrantLock類使用要點

 使用ReentrantLock類的newCondition()方法可以獲取Condition對象
 需要等待的時候使用Condition的await()方法, 喚醒的時候用signal()方法
 不同的線程使用不同的Condition, 這樣就能區分喚醒的時候找哪個線程了

12.線程組

12.1概述

1.Java中使用ThreadGroup來表示線程組,它可以對一批線程進行分類管理,Java允許程序直接對線程組進行控制。
2.默認情況下,所有的線程都屬於主線程組。
3.public final ThreadGroup getThreadGroup() 通過線程對象獲取他所屬於的組
4.public final String getName() 通過線程組對象獲取組的名字
5.我們也可以給線程設置分組ThreadGroup(String name) 創建線程組對象並給其賦值名字

12.2創建線程對象

Thread(ThreadGroup?group, Runnable?target, String?name)

12.3代碼演示


/**
 * 掌握:
 * 1.如何獲取一個線程所屬的線程組
 * 2.如果在創建一個子線程時,設置它所屬的線程組
 * @author gyf
 *
 */
public class Demo01 {
	public static void main(String[] args) {
		//主線程
		Thread mainThread = Thread.currentThread();
		/**
		 * [main,5,main]
		 * main:線程名稱
		 * 5:代先級
		 * main:當前線程所屬的組名
		 */
		System.out.println("線程:" + mainThread);
		
		//獲取線程的“線程組”對象
		ThreadGroup tg = mainThread.getThreadGroup();
		System.out.println("線程組:" + tg.getName());

		
		//創建子線程
		Thread t1 = new Thread(){
			@Override
			public void run() {
				System.out.println("線程A...");
			}
		};
		//t1.start();
		System.out.println("t1子線程的線程組:" + t1.getThreadGroup());
		
	   //創建一個線程組
		ThreadGroup abcGroup = new ThreadGroup("abc組");
		//創建子線程對象
		Thread t2 = new Thread(abcGroup, new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("線程B");
			}
		});
		System.out.println("t2子線程的線程組:" + t2.getThreadGroup());
	}
}


13.線程池

13.1線程池概述

程序啓動一個新線程成本是比較高的,因爲它涉及到要與操作系統進行交互。而使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,更應該考慮使用線程池。線程池裏的每一個線程代碼結束後,並不會死亡,而是再次回到線程池中成爲空閒狀態,等待下一個對象來使用。在JDK5之前,我們必須手動實現自己的線程池,從JDK5開始,Java內置支持線程池

13.2Java的內置線程池

1.JDK5新增了一個Executors工廠類來產生線程池,有如下幾個方法
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()

  1. 這些方法的返回值是ExecutorService對象,該對象表示一個線程池,
    可以執行Runnable對象或者Callable對象代表的線程。
    它提供瞭如下方法
    Future<?> submit(Runnable task)
    Future submit(Callable task)

3.使用步驟:
1.創建線程池對象
2.創建Runnable實例
3.提交Runnable實例
4.關閉線程池es.shutdown();

4.Runnable和Callable的區別
Runnable的run方法沒有返回值
Callable的call方法有返回值,一般返回值也沒用

13.3使用演示

public class Demo01 {
	public static void main(String[] args) {
		//案例:10個線程完成10個洗車任務
		/*for(int i = 0;i<10;i++){
			new Thread(){
				public void run() {
					System.out.println("洗車任務 " + Thread.currentThread());
				};
			}.start();
		}*/
		
		//案例:5個線程完成10個洗車的任務
		//1.創建線程池
		ExecutorService es = Executors.newFixedThreadPool(5);
		
		//2.添加任務-方式一
		/*for(int i=0;i<10;i++){
			es.submit(new Runnable() {
				@Override
				public void run() {
					System.out.println("洗車任務 " + Thread.currentThread());
				}
			});
		}*/
		
		//3.添加任務-方式二
		for(int i=0;i<10;i++){
			es.submit(new MyTask());
		}
		
	}
}

class MyTask implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		System.out.println("洗車任務 " + Thread.currentThread());
		return 110;
	}
	
}


14.線程的五種狀態

 新建,就緒,運行,阻塞,死亡
在這裏插入圖片描述

練習題

1.賣火車票

 需求,有A\B\C\D4個窗口同時買票,只有100張票可買
多線程會有安全問題熟記

public class Demo01 {

	public static void main(String[] args) {
		//同步代碼塊和同步方法
		
		//火車站賣票【問題】
		/**
		 * 湖南到廣州火車票:今天13:00 ,100張
		 * 火車站有4個窗口在同時賣票,要保證一張票只能被賣一次
		 * 
		 * 搞4個線程表示4個窗口
		 * 
		 * 通過加鎖可以解決被多次賣同一張票的問題
		 * 
		 * 使用同步代碼塊
		 */
		
		//創建賣票的任務
		TicketTask task = new TicketTask();
		
		//A窗口
		Thread t1 = new Thread(task);
		t1.setName("窗口A");
		//B窗口
		Thread t2 = new Thread(task);
		t2.setName("窗口B");
		//C窗口
		Thread t3 = new Thread(task);
		t3.setName("窗口C");
		//D窗口
		Thread t4 = new Thread(task);
		t4.setName("窗口D");
		
		//開啓線程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

class TicketTask implements Runnable{
	//只有100張票
	int ticket = 100;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		/**
		 * 同步代碼換括號裏參數可以傳任意對象
		 * this是一個鎖對象
		 * 不同的一把鎖,賣相同的票總是還是存在
		 */
		//賣票
		while(true){
			synchronized (this) {
				if(ticket <= 0){
					System.out.println("不好意思,票已經賣完了...");
					break;
				}else{
					System.out.println(Thread.currentThread() + "恭喜你賣到票,票號" + ticket);
					ticket --;
				}
			}
			
		}
	}
	
	/*@Override
	public void run() {
		// TODO Auto-generated method stub
		*//**
		 * 同步代碼換括號裏參數可以傳任意對象
		 *//*
		synchronized (this) {
			//賣票
			while(true){
				if(ticket <= 0){
					System.out.println("不好意思,票已經賣完了...");
					break;
				}else{
					System.out.println(Thread.currentThread() + "恭喜你賣到票,票號" + ticket);
					ticket --;
				}
			}
		}
	}*/
}

2.兩個線程間的通訊

public class Demo01 {
	public static void main(String[] args) {
		//1.創建任務對象
		MyTask task = new MyTask();
		
		//2.開啓兩個線程執行2個任務
		new Thread(){
			public void run() {
				while(true){
					try {
						task.task1();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
		
		new Thread(){
			public void run() {
				while(true){
					try {
						task.task2();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
	}
}

class MyTask{
	
	//標識 1:可以執行任務1,2:可以執行任務2
	int flag = 1;
	
	public synchronized void task1() throws InterruptedException{
		if(flag != 1){
			this.wait();//當前線程等待
		}
		
		System.out.println("1.銀行信用卡自動還款任務...");
		flag = 2;
		this.notify();//喚醒其它線程
		
	}
	
	public synchronized void task2() throws InterruptedException{
		
		if(flag != 2){
			this.wait();//線程等待
		}
		
		System.out.println("2.銀行儲蓄卡自動結算利息任務...");
		flag = 1;
		this.notify();//喚醒其它線程
	}
}


3.三個線程間的通訊(同步鎖實現有問題,少數沒按順序)

public class Demo01 {
	public static void main(String[] args) {
		//三個線程間的通訊
		MyTask task = new MyTask();
		new Thread(){
			public void run() {
				while(true){
					try {
						task.task1();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
		new Thread(){
			public void run() {
				while(true){
					try {
						task.task2();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
		new Thread(){
			public void run() {
				while(true){
					try {
						task.task3();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			};
		}.start();
	}
}

class MyTask{
	
	//標識 1:可以執行任務1,2:可以執行任務2, 3:可以執行任務3 
	int flag = 1;
	
	public synchronized void task1() throws InterruptedException{
		if(flag != 1){
			this.wait();//當前線程等待
			//this.wait(timeout);
		}
		
		System.out.println("1.銀行信用卡自動還款任務...");
		flag = 2;
		//this.notify();//喚醒隨機線程
		this.notifyAll();//喚醒所有等待線程
		
	}
	
	public synchronized void task2() throws InterruptedException{
		
		if(flag != 2){
			this.wait();//線程等待
		}
		
		System.out.println("2.銀行儲蓄卡自動結算利息任務...");
		flag = 3;
		//this.notify();//喚醒其它線程
		this.notifyAll();
		//Thread.sleep(millis);
	}
	
	public synchronized void task3() throws InterruptedException{
			if(flag != 3){
				this.wait();//線程等待
			}
			
			System.out.println("3.銀行短信提醒任務...");
			flag = 1;
			//this.notify();//喚醒其它線程
			this.notifyAll();
	}
}

4.三個線程間的通訊(使用互斥鎖,解決同步鎖少數沒按順序的問題)

/**
 * 互斥鎖的使用步驟
 * 1.創建互斥鎖對象
 * 2.創建3個Condition
 * 3.加鎖、解鎖
 * 4.線程等待-Condition的await方法
 * 5.線程喚醒-Condition的signal方法
 * @author gyf
 *
 */
class MyTask{
	//創建互斥鎖對象
	ReentrantLock rl = new ReentrantLock();
	//創建3個Condition
	Condition c1 = rl.newCondition();
	Condition c2 = rl.newCondition();
	Condition c3 = rl.newCondition();
	
	//標識 1:可以執行任務1,2:可以執行任務2, 3:可以執行任務3 
	int flag = 1;
	
	public void task1() throws InterruptedException{
	 rl.lock();//加鎖
			if(flag != 1){
				c1.await();//當前線程等待
			}
			
			System.out.println("1.銀行信用卡自動還款任務...");
			flag = 2;
			
			//指定喚醒線程2
			c2.signal();
	 rl.unlock();//解鎖
	}
	
	public void task2() throws InterruptedException{
	  rl.lock();	
			if(flag != 2){
				c2.await();//線程等待
			}
			
			System.out.println("2.銀行儲蓄卡自動結算利息任務...");
			flag = 3;
			
			//喚醒線程3
			c3.signal();
	  rl.unlock();
	}
	
	public void task3() throws InterruptedException{
	 rl.lock();
			if(flag != 3){
				c3.await();//線程等待
			}
			
			System.out.println("3.銀行短信提醒任務...");
			flag = 1;
			
			//喚醒線程1
			c1.signal();
	 rl.unlock();
	}
}

總結

通過線程一天的學習,會使用了兩種創建線程的方法,一種是繼承Thread類,另一種是實現Runnable接口(兩種方法都可以使用匿名內部類直接創建)。然後就是線程的其他方法,例如休眠、守護、加入等。掌握了同步代碼塊、同步方法關鍵字synchronized,在使用同步做案例:三個線程任務時,發現順序會出問題,於是學習了互斥鎖ReentrantLock,有效的實現了多線程調度的有序性。後面對線程池有了瞭解。記住,多線程會有安全問題。

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