day23-----------多線程(傳智視頻)

多線程概述
java

package cn.itcast_01;
/*
 *	進程:
 *		正在運行的程序,是系統進行資源分配和調用的獨立單位。
 *		每個進程都有它本身的內存空間和系統資源。
 *	線程:
 *		是進程中的單個順序控制流,是一條執行路徑
 *		一個進程若是隻有一條執行路徑,則稱爲單線程程序。
 *		一個進程若是有多條執行路徑,則稱爲多線程程序。
 *
 *  舉例:
 *  	掃雷程序,迅雷下載
 *  
 *  你們注意兩個詞彙的區別:並行和併發。
 *		前者是邏輯上同時發生,指在某一個時間內同時運行多個程序。
 *		後者是物理上同時發生,指在某一個時間點同時運行多個程序。
 *
 * 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");
	}
}

獲取和設置線程對象名稱
安全

package cn.itcast_03;

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);
		}
	}
}
package cn.itcast_03;

/*
 * 如何獲取線程對象的名稱呢?
 * 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();
	}
}

*/

線程調度及獲取和設置線程優先級
多線程

package cn.itcast_04;

/*
 * 咱們的線程沒有設置優先級,確定有默認優先級。
 * 那麼,默認優先級是多少呢?
 * 如何獲取線程對象的優先級?
 * 		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();
	}
}

線程控制之休眠線程
併發

package cn.itcast_04;

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

線程控制之加入線程
jvm

package cn.itcast_04;

/*
 * 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.start();
		try {
			tj1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		tj2.start();
		tj3.start();
	}
}

線程控制之禮讓線程
ide

package cn.itcast_04;

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

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

線程控制之守護線程
函數

package cn.itcast_04;

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

/*
 * 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);
		}
	}
}

線程控制之中斷線程
this

package cn.itcast_04;

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());
	}
}
package cn.itcast_04;

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

		// 你超過三秒不醒過來,我就乾死你
	        //主要還看主函數的,主函數要求他睡了3秒,3秒後,將其終止了,
	        //該線程3秒後已經死掉了,因此10秒後是不會醒來的
		try {
			Thread.sleep(3000);
			// ts.stop();
			ts.interrupt();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

多線程實現方式二:Ruannable
線程

package cn.itcast_05;

public class MyRunnable implements Runnable {

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

}
package cn.itcast_05;

/*
 * 方式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();
	}
}

繼承Thread類的方式賣電影票案例
設計

package cn.itcast_06;

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--) + "張票");
			}
		}
	}
}
package cn.itcast_06;

/*
 * 某電影院目前正在上映賀歲大片(紅高粱,少林寺傳奇藏經閣),共有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();
	}
}

實現Runnable接口的方式賣電影票案例

package cn.itcast_07;

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--) + "張票");
			}
		}
	}
}
package cn.itcast_07;

/*
 * 實現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();
	}
}

賣電影票出現了同票和負數票的緣由分析

package cn.itcast_08;

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

//	@Override
//	public void run() {
//		while (true) {
//			// t1,t2,t3三個線程
//			// 這一次的tickets = 100;
//			if (tickets > 0) {
//				// 爲了模擬更真實的場景,咱們稍做休息
//				try {
//					Thread.sleep(100); // t1就稍做休息,t2就稍做休息
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
//
//				System.out.println(Thread.currentThread().getName() + "正在出售第"
//						+ (tickets--) + "張票");
//				// 理想狀態:
//				// 窗口1正在出售第100張票
//				// 窗口2正在出售第99張票
//				// 可是呢?
//				// CPU的每一次執行必須是一個原子性(最簡單基本的)的操做。
//				// 先記錄之前的值
//				// 接着把ticket--
//				// 而後輸出之前的值(t2來了)
//				// ticket的值就變成了99
//				// 窗口1正在出售第100張票
//				// 窗口2正在出售第100張票
//
//			}
//		}
//	}
	
	@Override
	public void run() {
		while (true) {
			// t1,t2,t3三個線程
			// 這一次的tickets = 1;
			if (tickets > 0) {
				// 爲了模擬更真實的場景,咱們稍做休息
				try {
					Thread.sleep(100); //t1進來了並休息,t2進來了並休息,t3進來了並休息,
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				System.out.println(Thread.currentThread().getName() + "正在出售第"
						+ (tickets--) + "張票");
				//窗口1正在出售第1張票,tickets=0
				//窗口2正在出售第0張票,tickets=-1
				//窗口3正在出售第-1張票,tickets=-2
			}
		}
	}
}
package cn.itcast_08;

/*
 * 實現Runnable接口的方式實現
 * 
 * 經過加入延遲後,就產生了連個問題:
 * A:相同的票賣了屢次
 * 		CPU的一次操做必須是原子性的
 * 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();
	}
}

線程安全問題的產生緣由分析

package cn.itcast_09;

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--) + "張票");
				}
			}
		}
	}
}
package cn.itcast_09;

/*
 * 如何解決線程安全問題呢?
 * 
 * 要想解決問題,就要知道哪些緣由會致使出問題:(並且這些緣由也是之後咱們判斷一個程序是否會有線程安全問題的標準)
 * 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();
	}
}

同步代碼塊的方式解決線程安全問題

package cn.itcast_10;

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就出來可,而後就開門。(開)
		}
	}
}
package cn.itcast_10;

/*
 * 舉例:
 * 		火車上廁所。
 * 
 * 同步的特色:
 * 		前提:
 * 			多個線程
 *		解決問題的時候要注意:
 *			多個線程使用的是同一個鎖對象
 * 同步的好處 
 *		同步的出現解決了多線程的安全問題。
 * 同步的弊端
 *		當線程至關多時,由於每一個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會下降程序的運行效率。
 */
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();
	}
}

同步代碼塊的鎖及同步方法應用和鎖的問題

package cn.itcast_11;

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 {
}
package cn.itcast_11;

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

之前的線程安全的類回顧

package cn.itcast_12;

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>()); // 線程安全
	}
}
相關文章
相關標籤/搜索