多線程

1:多線程(理解)


1:要想了解多線程,必須先了解線程,而要想了解線程,必須先了解進程,因爲線程是依賴於進程而存在。


2:什麼是進程?

通過任務管理器我們就看到了進程的存在。

而通過觀察,我們發現只有運行的程序纔會出現進程。

進程:就是正在運行的程序。

進程是系統進行資源分配和調用的獨立單位。每一個進程都有它自己的內存空間和系統資源。

3:多進程有什麼意義呢?

單進程的計算機只能做一件事情,而我們現在的計算機都可以做多件事情。

舉例:一邊玩遊戲(遊戲進程),一邊聽音樂(音樂進程)。

也就是說現在的計算機都是支持多進程的,可以在一個時間段內執行多個任務。

並且呢,可以提高CPU的使用率。

問題:

一邊玩遊戲,一邊聽音樂是同時進行的嗎?

不是。因爲單CPU在某一個時間點上只能做一件事情。

而我們在玩遊戲,或者聽音樂的時候,是CPU在做着程序間的高效切換讓我們覺得是同時進行的。

4:什麼是線程呢?

在同一個進程內又可以執行多個任務,而這每一個任務我就可以看出是一個線程。

線程:是程序的執行單元,執行路徑。是程序使用CPU的最基本單位。

單線程:如果程序只有一條執行路徑。

多線程:如果程序有多條執行路徑。

5:多線程有什麼意義呢?

多線程的存在,不是提高程序的執行速度。其實是爲了提高應用程序的使用率。

程序的執行其實都是在搶CPU的資源,CPU的執行權。

多個進程是在搶這個資源,而其中的某一個進程如果執行路徑比較多,就會有更高的機率搶到CPU的執行權。

我們是不敢保證哪一個線程能夠在哪個時刻搶到,所以線程的執行有隨機性。



(1)多線程:一個應用程序有多條執行路徑

進程:正在執行的應用程序

線程:進程的執行單元,執行路徑

單線程:一個應用程序只有一條執行路徑

多線程:一個應用程序有多條執行路徑

多進程的意義?

提高CPU的使用率

多線程的意義?

提高應用程序的使用率


Demo:

/*
 *	進程:
 *		正在運行的程序,是系統進行資源分配和調用的獨立單位。
 *		每一個進程都有它自己的內存空間和系統資源。
 *	線程:
 *		是進程中的單個順序控制流,是一條執行路徑
 *		一個進程如果只有一條執行路徑,則稱爲單線程程序。
 *		一個進程如果有多條執行路徑,則稱爲多線程程序。
 *
 *  舉例:
 *  	掃雷程序,迅雷下載
 *  
 *  大家注意兩個詞彙的區別:並行和併發。
 *		前者是邏輯上同時發生,指在某一個時間內同時運行多個程序。
 *		後者是物理上同時發生,指在某一個時間點同時運行多個程序。
 *
 * Java程序的運行原理:
 * 		由java命令啓動JVM,JVM啓動就相當於啓動了一個進程。
 * 		接着有該進程創建了一個主線程去調用main方法。
 * 
 * 思考題:
 * 		jvm虛擬機的啓動是單線程的還是多線程的?
 * 			多線程的。
 * 			原因是垃圾回收線程也要先啓動,否則很容易會出現內存溢出。
 * 			現在的垃圾回收線程加上前面的主線程,最低啓動了兩個線程,所以,jvm的啓動其實是多線程的。
 */
public class MyThreadDemo {
	public static void main(String[] args) {
		System.out.println("hello");
		new Object();
		new Object();
		new Object();
		new Object();
		//...
		System.out.println("world");
	}
}



(2)Java程序的運行原理及JVM的啓動是多線程的嗎?

A:Java命令去啓動JVM,JVM會啓動一個進程,該進程會啓動一個主線程。

B:JVM的啓動是多線程的,因爲它最低有兩個線程啓動了,主線程和垃圾回收線程。

(3)多線程的實現方案(自己補齊步驟及代碼掌握)

A:繼承Thread類


Thread類:

/*
 * 該類要重寫run()方法,爲什麼呢?
 * 不是類中的所有代碼都需要被線程執行的。
 * 而這個時候,爲了區分哪些代碼能夠被線程執行,java提供了Thread類中的run()用來包含那些被線程執行的代碼。
 */
public class MyThread extends Thread {

	@Override
	public void run() {
		// 自己寫代碼
		// System.out.println("好好學習,天天向上");
		// 一般來說,被線程執行的代碼肯定是比較耗時的。所以我們用循環改進
		for (int x = 0; x < 200; x++) {
			System.out.println(x);
		}
	}

}


Demo:

package cn.itcast_02;

/*
 * 需求:我們要實現多線程的程序。
 * 如何實現呢?
 * 		由於線程是依賴進程而存在的,所以我們應該先創建一個進程出來。
 * 		而進程是由系統創建的,所以我們應該去調用系統功能創建一個進程。
 * 		Java是不能直接調用系統功能的,所以,我們沒有辦法直接實現多線程程序。
 * 		但是呢?Java可以去調用C/C++寫好的程序來實現多線程程序。
 * 		由C/C++去調用系統功能創建進程,然後由Java去調用這樣的東西,
 * 		然後提供一些類供我們使用。我們就可以實現多線程程序了。
 * 那麼Java提供的類是什麼呢?
 * 		Thread
 * 		通過查看API,我們知道了有2中方式實現多線程程序。
 * 
 * 方式1:繼承Thread類。
 * 步驟
 * 		A:自定義類MyThread繼承Thread類。
 * 		B:MyThread類裏面重寫run()?
 * 			爲什麼是run()方法呢?
 * 		C:創建對象
 * 		D:啓動線程
 */
public class MyThreadDemo {
	public static void main(String[] args) {
		// 創建線程對象
		// MyThread my = new MyThread();
		// // 啓動線程
		// my.run();
		// my.run();
		// 調用run()方法爲什麼是單線程的呢?
		// 因爲run()方法直接調用其實就相當於普通的方法調用,所以你看到的是單線程的效果
		// 要想看到多線程的效果,就必須說說另一個方法:start()
		// 面試題:run()和start()的區別?
		// run():僅僅是封裝被線程執行的代碼,直接調用是普通方法
		// start():首先啓動了線程,然後再由jvm去調用該線程的run()方法。
		// MyThread my = new MyThread();
		// my.start();
		// // IllegalThreadStateException:非法的線程狀態異常
		// // 爲什麼呢?因爲這個相當於是my線程被調用了兩次。而不是兩個線程啓動。
		// my.start();

		// 創建兩個線程對象
		MyThread my1 = new MyThread();
		MyThread my2 = new MyThread();

		my1.start();
		my2.start();
	}
}


獲取線程名稱:

MyThread類:

public class MyThread extends Thread {

	public MyThread() {
	}
	
	public MyThread(String name){
		super(name);
	}

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


Demo:

/*
 * 如何獲取線程對象的名稱呢?
 * public final String getName():獲取線程的名稱。
 * 如何設置線程對象的名稱呢?
 * public final void setName(String name):設置線程的名稱
 * 
 * 針對不是Thread類的子類中如何獲取線程對象名稱呢?
 * public static Thread currentThread():返回當前正在執行的線程對象
 * Thread.currentThread().getName()
 */
public class MyThreadDemo {
	public static void main(String[] args) {
		// 創建線程對象
		//無參構造+setXxx()
		// MyThread my1 = new MyThread();
		// MyThread my2 = new MyThread();
		// //調用方法設置名稱
		// my1.setName("林青霞");
		// my2.setName("劉意");
		// my1.start();
		// my2.start();
		
		//帶參構造方法給線程起名字
		// MyThread my1 = new MyThread("林青霞");
		// MyThread my2 = new MyThread("劉意");
		// my1.start();
		// my2.start();
		
		//我要獲取main方法所在的線程對象的名稱,該怎麼辦呢?
		//遇到這種情況,Thread類提供了一個很好玩的方法:
		//public static Thread currentThread():返回當前正在執行的線程對象
		System.out.println(Thread.currentThread().getName());
	}
}

/*
名稱爲什麼是:Thread-? 編號

class Thread {
	private char name[];

	public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }
    
     private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        //大部分代碼被省略了
        this.name = name.toCharArray();
    }
    
    public final void setName(String name) {
        this.name = name.toCharArray();
    }
    
    
    private static int threadInitNumber; //0,1,2
    private static synchronized int nextThreadNum() {
        return threadInitNumber++; //return 0,1
    }
    
    public final String getName() {
        return String.valueOf(name);
    }
}

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

*/


B:實現Runnable接口

Demo:

1

public class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			// 由於實現接口的方式就不能直接使用Thread類的方法了,但是可以間接的使用
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
	}

}

2

/*
 * 方式2:實現Runnable接口
 * 步驟:
 * 		A:自定義類MyRunnable實現Runnable接口
 * 		B:重寫run()方法
 * 		C:創建MyRunnable類的對象
 * 		D:創建Thread類的對象,並把C步驟的對象作爲構造參數傳遞
 */
public class MyRunnableDemo {
	public static void main(String[] args) {
		// 創建MyRunnable類的對象
		MyRunnable my = new MyRunnable();

		// 創建Thread類的對象,並把C步驟的對象作爲構造參數傳遞
		// Thread(Runnable target)
		// Thread t1 = new Thread(my);
		// Thread t2 = new Thread(my);
		// t1.setName("林青霞");
		// t2.setName("劉意");

		// Thread(Runnable target, String name)
		Thread t1 = new Thread(my, "林青霞");
		Thread t2 = new Thread(my, "劉意");

		t1.start();
		t2.start();
	}
}


(4)線程的調度和優先級問題

A:線程的調度

a:分時調度

b:搶佔式調度 (Java採用的是該調度方式)


B:獲取和設置線程優先級

a:默認是5

b:範圍是1-10


Demo:

/*
 * 我們的線程沒有設置優先級,肯定有默認優先級。
 * 那麼,默認優先級是多少呢?
 * 如何獲取線程對象的優先級?
 * 		public final int getPriority():返回線程對象的優先級
 * 如何設置線程對象的優先級呢?
 * 		public final void setPriority(int newPriority):更改線程的優先級。 
 * 
 * 注意:
 * 		線程默認優先級是5。
 * 		線程優先級的範圍是:1-10。
 * 		線程優先級高僅僅表示線程獲取的 CPU時間片的機率高,但是要在次數比較多,或者多次運行的時候才能看到比較好的效果。
 * 		
 * IllegalArgumentException:非法參數異常。
 * 拋出的異常表明向方法傳遞了一個不合法或不正確的參數。 
 * 
 */
public class ThreadPriorityDemo {
	public static void main(String[] args) {
		ThreadPriority tp1 = new ThreadPriority();
		ThreadPriority tp2 = new ThreadPriority();
		ThreadPriority tp3 = new ThreadPriority();

		tp1.setName("東方不敗");
		tp2.setName("嶽不羣");
		tp3.setName("林平之");

		// 獲取默認優先級
		// System.out.println(tp1.getPriority());
		// System.out.println(tp2.getPriority());
		// System.out.println(tp3.getPriority());

		// 設置線程優先級
		// tp1.setPriority(100000);
		
		//設置正確的線程優先級
		tp1.setPriority(10);
		tp2.setPriority(1);

		tp1.start();
		tp2.start();
		tp3.start();
	}
}



(5)線程的控制(常見方法)

A:休眠線程

Demo:

1

import java.util.Date;

public class ThreadSleep extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x + ",日期:" + new Date());
			// 睡眠
			// 困了,我稍微休息1秒鐘
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

2

/*
 * 線程休眠
 *		public static void sleep(long millis)
 */
public class ThreadSleepDemo {
	public static void main(String[] args) {
		ThreadSleep ts1 = new ThreadSleep();
		ThreadSleep ts2 = new ThreadSleep();
		ThreadSleep ts3 = new ThreadSleep();

		ts1.setName("林青霞");
		ts2.setName("林志玲");
		ts3.setName("林志穎");

		ts1.start();
		ts2.start();
		ts3.start();
	}
}


B:加入線程

Demo:

/*
 * public final void join():等待該線程終止。 
 */
public class ThreadJoinDemo {
	public static void main(String[] args) {
		ThreadJoin tj1 = new ThreadJoin();
		ThreadJoin tj2 = new ThreadJoin();
		ThreadJoin tj3 = new ThreadJoin();

		tj1.setName("李淵");
		tj2.setName("李世民");
		tj3.setName("李元霸");
                //tj1執行完才繼續執行下面的線程
		tj1.start();
		try {
			tj1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		tj2.start();
		tj3.start();
	}
}


C:禮讓線程

Demo:

1

public class ThreadYield extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x);
			Thread.yield();
		}
	}
}

2

/*
 * public static void yield():暫停當前正在執行的線程對象,並執行其他線程。 
 * 讓多個線程的執行更和諧,但是不能靠它保證一人一次。
 */
public class ThreadYieldDemo {
	public static void main(String[] args) {
		ThreadYield ty1 = new ThreadYield();
		ThreadYield ty2 = new ThreadYield();

		ty1.setName("林青霞");
		ty2.setName("劉意");

		ty1.start();
		ty2.start();
	}
}




D:後臺線程

Demo:

/*
 * public final void setDaemon(boolean on):將該線程標記爲守護線程或用戶線程。
 * 當正在運行的線程都是守護線程時,Java 虛擬機退出。 該方法必須在啓動線程前調用。 
 * 
 * 遊戲:坦克大戰。
 */
public class ThreadDaemonDemo {
	public static void main(String[] args) {
		ThreadDaemon td1 = new ThreadDaemon();
		ThreadDaemon td2 = new ThreadDaemon();

		td1.setName("關羽");
		td2.setName("張飛");

		// 設置守護線程
		td1.setDaemon(true);
		td2.setDaemon(true);

		td1.start();
		td2.start();

		Thread.currentThread().setName("劉備");
		for (int x = 0; x < 5; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
	}
}


E:終止線程(掌握)

Demo:

1

import java.util.Date;

public class ThreadStop extends Thread {
	@Override
	public void run() {
		System.out.println("開始執行:" + new Date());

		// 我要休息10秒鐘,親,不要打擾我哦
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// e.printStackTrace();
			System.out.println("線程被終止了");
		}

		System.out.println("結束執行:" + new Date());
	}
}

2

/*
 * public final void stop():讓線程停止,過時了,但是還可以使用。
 * public void interrupt():中斷線程。 把線程的狀態終止,並拋出一個InterruptedException。
 */
public class ThreadStopDemo {
	public static void main(String[] args) {
		ThreadStop ts = new ThreadStop();
		ts.start();

		// 你超過三秒不醒過來,我就乾死你
		try {
			Thread.sleep(3000);
			// ts.stop();
			ts.interrupt();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}


(6)線程的生命週期(參照線程生命週期圖解.bmp)

A:新建

B:就緒

C:運行

D:阻塞

E:死亡


(7)電影院賣票程序的實現

A:繼承Thread類

1:

public class SellTicket extends Thread {

	// 定義100張票
	// private int tickets = 100;
	// 爲了讓多個線程對象共享這100張票,我們其實應該用靜態修飾
	private static int tickets = 100;

	@Override
	public void run() {
		// 定義100張票
		// 每個線程進來都會走這裏,這樣的話,每個線程對象相當於買的是自己的那100張票,這不合理,所以應該定義到外面
		// int tickets = 100;

		// 是爲了模擬一直有票
		while (true) {
			if (tickets > 0) {
				System.out.println(getName() + "正在出售第" + (tickets--) + "張票");
			}
		}
	}
}

2:

/*
 * 某電影院目前正在上映賀歲大片(紅高粱,少林寺傳奇藏經閣),共有100張票,而它有3個售票窗口售票,請設計一個程序模擬該電影院售票。
 * 繼承Thread類來實現。
 */
public class SellTicketDemo {
	public static void main(String[] args) {
		// 創建三個線程對象
		SellTicket st1 = new SellTicket();
		SellTicket st2 = new SellTicket();
		SellTicket st3 = new SellTicket();

		// 給線程對象起名字
		st1.setName("窗口1");
		st2.setName("窗口2");
		st3.setName("窗口3");

		// 啓動線程
		st1.start();
		st2.start();
		st3.start();
	}
}


B:實現Runnable接口

1:

public class SellTicket implements Runnable {
	// 定義100張票
	private int tickets = 100;

	@Override
	public void run() {
		while (true) {
			if (tickets > 0) {
				System.out.println(Thread.currentThread().getName() + "正在出售第"
						+ (tickets--) + "張票");
			}
		}
	}
}

2:

/*
 * 實現Runnable接口的方式實現
 */
public class SellTicketDemo {
	public static void main(String[] args) {
		// 創建資源對象
		SellTicket st = new SellTicket();

		// 創建三個線程對象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 啓動線程
		t1.start();
		t2.start();
		t3.start();
	}
}


(8)電影院賣票程序出問題

A:爲了更符合真實的場景,加入了休眠100毫秒。


// 理想狀態:

// 窗口1正在出售第100張票

// 窗口2正在出售第99張票

// 但是呢?

// CPU的每一次執行必須是一個原子性(最簡單基本的)的操作。

// 先記錄以前的值

// 接着把ticket--

// 然後輸出以前的值(t2來了)

// ticket的值就變成了99

// 窗口1正在出售第100張票

// 窗口2正在出售第100張票


/*

 * 實現Runnable接口的方式實現

 * 

 * 通過加入延遲後,就產生了連個問題:

 * A:相同的票賣了多次

 * CPU的一次操作必須是原子性的

 * B:出現了負數票

 * 隨機性和延遲導致的

 */



B:賣票問題

a:同票多次

b:負數票


(9)多線程安全問題的原因(也是我們以後判斷一個程序是否有線程安全問題的依據)

A:是否有多線程環境

B:是否有共享數據

C:是否有多條語句操作共享數據


Demo:

1

public class SellTicket implements Runnable {
	// 定義100張票
	private int tickets = 100;
	//創建鎖對象
	private Object obj = new Object();

//	@Override
//	public void run() {
//		while (true) {
//			synchronized(new Object()){
//				if (tickets > 0) {
//					try {
//						Thread.sleep(100); 
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName() + "正在出售第"
//							+ (tickets--) + "張票");
//				}
//			}
//		}
//	}
	
	@Override
	public void run() {
		while (true) {
			synchronized (obj) {
				if (tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "正在出售第" + (tickets--) + "張票");
				}
			}
		}
	}
}

2

/*
 * 如何解決線程安全問題呢?
 * 
 * 要想解決問題,就要知道哪些原因會導致出問題:(而且這些原因也是以後我們判斷一個程序是否會有線程安全問題的標準)
 * A:是否是多線程環境
 * B:是否有共享數據
 * C:是否有多條語句操作共享數據
 * 
 * 我們來回想一下我們的程序有沒有上面的問題呢?
 * A:是否是多線程環境	是
 * B:是否有共享數據	是
 * C:是否有多條語句操作共享數據	是
 * 
 * 由此可見我們的程序出現問題是正常的,因爲它滿足出問題的條件。
 * 接下來纔是我們要想想如何解決問題呢?
 * A和B的問題我們改變不了,我們只能想辦法去把C改變一下。
 * 思想:
 * 		把多條語句操作共享數據的代碼給包成一個整體,讓某個線程在執行的時候,別人不能來執行。
 * 問題是我們不知道怎麼包啊?其實我也不知道,但是Java給我們提供了:同步機制。
 * 
 * 同步代碼塊:
 * 		synchronized(對象){
 * 			需要同步的代碼;
 * 		}
 * 
 * 		A:對象是什麼呢?
 * 			我們可以隨便創建一個對象試試。
 * 		B:需要同步的代碼是哪些呢?
 * 			把多條語句操作共享數據的代碼的部分給包起來
 * 
 * 		注意:
 * 			同步可以解決安全問題的根本原因就在那個對象上。該對象如同鎖的功能。
 * 			多個線程必須是同一把鎖。
 */
public class SellTicketDemo {
	public static void main(String[] args) {
		// 創建資源對象
		SellTicket st = new SellTicket();

		// 創建三個線程對象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 啓動線程
		t1.start();
		t2.start();
		t3.start();
	}
}


(10)同步解決線程安全問題

A:同步代碼塊

synchronized(對象) {

需要被同步的代碼;

}

這裏的鎖對象可以是任意對象。

Demo:

1

public class SellTicket implements Runnable {
	// 定義100張票
	private int tickets = 100;
	//創建鎖對象
	private Object obj = new Object();

//	@Override
//	public void run() {
//		while (true) {
//			synchronized(new Object()){
//				if (tickets > 0) {
//					try {
//						Thread.sleep(100); 
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName() + "正在出售第"
//							+ (tickets--) + "張票");
//				}
//			}
//		}
//	}
	
	@Override
	public void run() {
		while (true) {
			synchronized (obj) {
				if (tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "正在出售第" + (tickets--) + "張票");
				}
			}
		}
	}
}

2

public class SellTicketDemo {
	public static void main(String[] args) {
		// 創建資源對象
		SellTicket st = new SellTicket();

		// 創建三個線程對象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 啓動線程
		t1.start();
		t2.start();
		t3.start();
	}
}


B:同步方法

把同步加在方法上。

這裏的鎖對象是this

Demo:

1

public class SellTicket implements Runnable {

	// 定義100張票
	private int tickets = 100;

	// 定義同一把鎖
	private Object obj = new Object();

	@Override
	public void run() {
		while (true) {
			// t1,t2,t3都能走到這裏
			// 假設t1搶到CPU的執行權,t1就要進來
			// 假設t2搶到CPU的執行權,t2就要進來,發現門是關着的,進不去。所以就等着。
			// 門(開,關)
			synchronized (obj) { // 發現這裏的代碼將來是會被鎖上的,所以t1進來後,就鎖了。(關)
				if (tickets > 0) {
					try {
						Thread.sleep(100); // t1就睡眠了
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "正在出售第" + (tickets--) + "張票 ");
					//窗口1正在出售第100張票
				}
			} //t1就出來可,然後就開門。(開)
		}
	}
}

2

/*
 * 舉例:
 * 		火車上廁所。
 * 
 * 同步的特點:
 * 		前提:
 * 			多個線程
 *		解決問題的時候要注意:
 *			多個線程使用的是同一個鎖對象
 * 同步的好處 
 *		同步的出現解決了多線程的安全問題。
 * 同步的弊端
 *		當線程相當多時,因爲每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會降低程序的運行效率。
 */
public class SellTicketDemo {
	public static void main(String[] args) {
		// 創建資源對象
		SellTicket st = new SellTicket();

		// 創建三個線程對象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 啓動線程
		t1.start();
		t2.start();
		t3.start();
	}
}


C:靜態同步方法

把同步加在方法上。

這裏的鎖對象是當前類的字節碼文件對象(反射再講字節碼文件對象)

Demo:

1

public class SellTicket implements Runnable {

	// 定義100張票
	private static int tickets = 100;

	// 定義同一把鎖
	private Object obj = new Object();
	private Demo d = new Demo();

	private int x = 0;
	
	//同步代碼塊用obj做鎖
//	@Override
//	public void run() {
//		while (true) {
//			synchronized (obj) {
//				if (tickets > 0) {
//					try {
//						Thread.sleep(100);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName()
//							+ "正在出售第" + (tickets--) + "張票 ");
//				}
//			}
//		}
//	}
	
	//同步代碼塊用任意對象做鎖
//	@Override
//	public void run() {
//		while (true) {
//			synchronized (d) {
//				if (tickets > 0) {
//					try {
//						Thread.sleep(100);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName()
//							+ "正在出售第" + (tickets--) + "張票 ");
//				}
//			}
//		}
//	}
	
	@Override
	public void run() {
		while (true) {
			if(x%2==0){
				synchronized (SellTicket.class) {
					if (tickets > 0) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "正在出售第" + (tickets--) + "張票 ");
					}
				}
			}else {
//				synchronized (d) {
//					if (tickets > 0) {
//						try {
//							Thread.sleep(100);
//						} catch (InterruptedException e) {
//							e.printStackTrace();
//						}
//						System.out.println(Thread.currentThread().getName()
//								+ "正在出售第" + (tickets--) + "張票 ");
//					}
//				}
				
				sellTicket();
				
			}
			x++;
		}
	}

//	private void sellTicket() {
//		synchronized (d) {
//			if (tickets > 0) {
//			try {
//					Thread.sleep(100);
//			} catch (InterruptedException e) {
//					e.printStackTrace();
//			}
//			System.out.println(Thread.currentThread().getName()
//						+ "正在出售第" + (tickets--) + "張票 ");
//			}
//		}
//	}
	
	//如果一個方法一進去就看到了代碼被同步了,那麼我就再想能不能把這個同步加在方法上呢?
//	 private synchronized void sellTicket() {
//			if (tickets > 0) {
//			try {
//					Thread.sleep(100);
//			} catch (InterruptedException e) {
//					e.printStackTrace();
//			}
//			System.out.println(Thread.currentThread().getName()
//						+ "正在出售第" + (tickets--) + "張票 ");
//			}
//	}
	
	private static synchronized void sellTicket() {
		if (tickets > 0) {
		try {
				Thread.sleep(100);
		} catch (InterruptedException e) {
				e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()
					+ "正在出售第" + (tickets--) + "張票 ");
		}
}
}

class Demo {
}

2

/*
 * A:同步代碼塊的鎖對象是誰呢?
 * 		任意對象。
 * 
 * B:同步方法的格式及鎖對象問題?
 * 		把同步關鍵字加在方法上。
 * 
 * 		同步方法是誰呢?
 * 			this
 * 
 * C:靜態方法及鎖對象問題?
 * 		靜態方法的鎖對象是誰呢?
 * 			類的字節碼文件對象。(反射會講)
 */
public class SellTicketDemo {
	public static void main(String[] args) {
		// 創建資源對象
		SellTicket st = new SellTicket();

		// 創建三個線程對象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 啓動線程
		t1.start();
		t2.start();
		t3.start();
	}
}


(11)回顧以前的線程安全的類

A:StringBuffer

B:Vector

C:Hashtable

D:如何把一個線程不安全的集合類變成一個線程安全的集合類

用Collections工具類的方法即可。


Demo:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

public class ThreadDemo {
	public static void main(String[] args) {
		// 線程安全的類
		StringBuffer sb = new StringBuffer();
		Vector<String> v = new Vector<String>();
		Hashtable<String, String> h = new Hashtable<String, String>();

		// Vector是線程安全的時候纔去考慮使用的,但是我還說過即使要安全,我也不用你
		// 那麼到底用誰呢?
		// public static <T> List<T> synchronizedList(List<T> list)
		List<String> list1 = new ArrayList<String>();// 線程不安全
		List<String> list2 = Collections
				.synchronizedList(new ArrayList<String>()); // 線程安全
	}
}

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