1:多線程
(1)多線程:一個應用程序有多條執行路徑
進程:正在執行的應用程序。
是系統進行資源分配和調用的獨立單元。每一個進程都有他自己的內存空間和系統資源
線程:進程的執行單元,執行路徑。
在同一個進程內又可以執行多個任務,而這每一個任務就可以視為一個線程。
單線程:一個應用程序只有一條執行路徑
多線程:一個應用程序有多條執行路徑
多進程的意義?
提高CPU的使用率
多線程的意義?
提高應用程序的使用率
(2)Java程序的運行原理及JVM的啟動是多線程的嗎?
A:Java命令去啟動JVM,JVM會啟動一個進程,該進程會啟動一個主線程。
B:JVM的啟動是多線程的,因為它最低有兩個線程啟動了,主線程和垃圾回收線程。
(3)多線程的實現方案
A:繼承Thread類
1 /* 2 * java提供兩兩種方式實現多線程程序 3 * 4 * 方式一:繼承Thred類 5 * 步驟:自定義MyThread類 繼承Thred類 6 * MyThread類里面重寫run()? 7 * 這里要明白為什么是run方法 8 * 創建對象 9 * 啟動線程 10 * 11 */ 12 public class MyThreadDemo { 13 public static void main(String[] args) { 14 //創建線程對象 15 //MyThread my = new MyThread(); 16 //啟動線程 17 //my.run(); 18 //my.run(); 19 /* 20 * 調用run()方法為什么是單線程的? 21 * 由於run()方法直接調用其實就相當於普通的方法調用,所有是單線程的效果 22 * 可以通過start()方法來實現多線程 23 * 24 * 這里要明白run()和start()的區別: 25 * run():僅是封裝被線程執行的代碼,直接調用屬於普通方法 26 * start():先啟動線程,然后再通過jvm去調用該線程的run()方法 27 */ 28 29 //創建兩個線程 30 MyThread my1 = new MyThread(); 31 MyThread my2 = new MyThread(); 32 my1.start(); 33 my2.start(); 34 } 35 36 }
1 /* 2 * 這個類要重寫run()方法,是因為,不是說類中的所有代碼都需要被線程執行。 3 * 這時為了區分哪些代碼能夠被線程執行,Java就提供了Thread類中的run()方法 4 * 來包含哪些需要被線程執行的代碼 5 */ 6 public class MyThread extends Thread{ 7 @Override 8 public void run(){ 9 for(int x = 0; x < 100; x++){ 10 System.out.println(x); 11 } 12 } 13 14 }
獲取線程名稱
1 /* 2 * 獲取線程對象名稱 3 */ 4 public class MyThredDemo1 { 5 public static void main(String[] args) { 6 //帶參構造方法給線程起名字 7 MyThread1 my1 = new MyThread1("a"); 8 MyThread1 my2 = new MyThread1("b"); 9 10 //調用方法設置名稱 11 //my1.setName("a"); 12 //my2.setName("b"); 13 14 my1.start(); 15 my2.start(); 16 17 //獲取main方法所在線程名稱 18 System.out.println(Thread.currentThread().getName()); 19 } 20 21 }
1 public class MyThread1 extends Thread{ 2 3 public MyThread1() { 4 5 } 6 7 public MyThread1(String name) { 8 9 } 10 11 @Override 12 public void run(){ 13 for(int x = 0; x < 100; x++){ 14 System.out.println(getName() + ":" + x); 15 } 16 } 17 }
B:實現Runnable接口(一般都會采用方式二)
1 /* 2 * 多線程實現方式2:實現Runnable接口 3 * 步驟:1.自定義類MyRunnable實現Runnable接口 4 * 2.重寫run()方法 5 * 3.創建MyRunnable類的對象 6 * 4.創建Thread類的對象,並把3步驟的對象作為構造參數傳遞 7 */ 8 public class MyRunnableDemo { 9 public static void main(String[] args) { 10 //創建MyRunnable類的對象 11 MyRunnable my = new MyRunnable(); 12 13 //創建Thread類的對象,並把3步驟的對象作為構造參數傳遞 14 //Thread(Runnale target) 15 //Thread t1 = new Thread(my); 16 //Thread t2 = new Thread(my); 17 //t1.setName("aa"); 18 //t2.setName("bb"); 19 20 //Thread(Runnable target, String name) 另一種方法命名 21 Thread t1 = new Thread(my, "aa"); 22 Thread t2 = new Thread(my, "bb"); 23 24 t1.start(); 25 t2.start(); 26 } 27 }
1 public class MyRunnable implements Runnable { 2 /* 3 * 1.自定義類MyRunnable實現Runnable接口 4 * 2.重寫run()方法 5 * 6 */ 7 @Override 8 public void run() { 9 for(int x = 0; x < 100; x++){ 10 //由於實現接口的方法不能直接Thread類的方法,這時需要間接使用 11 System.out.println(Thread.currentThread().getName() + ":" + x); 12 } 13 14 } 15 16 }
小結:
實現多線程的方式:兩種
方式一:繼承Thread類
步驟:1.自定義類MyThread繼承Thread類
2.在MyThread類中重寫run()方法
3.創建MyThread類的對象
4.啟動線程對象
方式二:實現Runnable接口
步驟:1.自定義類MyRunnble實現Runnable接口
2.MyRunnble里面重寫run()方法
3.創建MyRunnble類的對象
4.創建Thread類的對象,並把3步驟的對象作為構造參數傳遞
問題:有了方式一了,為什么還要方式二呢?
1.可以避免由於Java單繼承帶來的局限性
2.適合多個相同程序的代碼去處理同一個資源的情況,將線程和程序的代碼、數據分離,充分體現了面向對象的設計思想。
(4)線程的調度和優先級問題
A:線程的調度
a:分時調度
b:搶占式調度 (Java采用的是該調度方式)
B:獲取和設置線程優先級
a:默認是5
b:范圍是1-10
1 /* 2 * 獲取線程的優先級 3 * public final int getPriority():返回線程的優先級 4 * 5 * 設置線程對象的優先級 6 * public final void setPriority():設置線程優先級 7 * 8 * 注意:線程默認的優先級是5 9 * 線程優先級范圍是:1-10 10 * 優先級高僅表示線程獲取CPU時間片的幾率高,這要求次數比較多或者多次運行才能看到好的效果 11 * 12 */ 13 public class ThreadPriorityDemo { 14 15 public static void main(String[] args) { 16 ThreadPriority tp1 = new ThreadPriority(); 17 ThreadPriority tp2 = new ThreadPriority(); 18 ThreadPriority tp3 = new ThreadPriority(); 19 20 tp1.setName("aa"); 21 tp1.setName("bb"); 22 tp1.setName("cc"); 23 24 //獲取默認優先級 25 System.out.println(tp1.getPriority()); 26 System.out.println(tp2.getPriority()); 27 System.out.println(tp3.getPriority()); 28 29 //設置優先級 30 tp1.setPriority(10); 31 tp2.setPriority(1); 32 33 tp1.start(); 34 tp2.start(); 35 tp3.start(); 36 } 37 }
1 public class ThreadPriority extends Thread{ 2 @Override 3 public void run() { 4 for(int x = 0; x < 100; x++){ 5 System.out.println(getName() + ":" + x); 6 } 7 8 } 9 10 }
(5)線程的控制(常見方法)
A:休眠線程
1 //線程睡眠 public static void sleep(long millis) 2 public class ThreadSleepDemo { 3 4 public static void main(String[] args) { 5 ThreadSleep ts1 = new ThreadSleep(); 6 ThreadSleep ts2 = new ThreadSleep(); 7 ThreadSleep ts3 = new ThreadSleep(); 8 9 //設置線程名稱 10 ts1.setName("a"); 11 ts2.setName("b"); 12 ts3.setName("c"); 13 14 //啟動線程 15 ts1.start(); 16 ts2.start(); 17 ts3.start(); 18 19 } 20 21 }
1 public class ThreadSleep extends Thread { 2 3 @Override 4 public void run() { 5 for(int x = 0; x < 100; x++){ 6 System.out.println(getName() + ":" + x + ",時間:" + new Date()); 7 //睡眠 8 try { 9 Thread.sleep(1000);//睡眠 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 14 } 15 16 } 17 }
B:加入線程
1 //public final void join():等待線程終止 2 public class ThreadJoinDemo { 3 4 public static void main(String[] args) { 5 ThreadJoin tj1 = new ThreadJoin(); 6 ThreadJoin tj2 = new ThreadJoin(); 7 ThreadJoin tj3 = new ThreadJoin(); 8 9 tj1.setName("aa"); 10 tj2.setName("bb"); 11 tj3.setName("cc"); 12 13 tj1.start(); 14 //等待tj1執行完 15 try { 16 tj1.join(); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 21 tj2.start(); 22 tj3.start(); 23 24 } 25 }
1 public class ThreadJoin extends Thread { 2 @Override 3 public void run() { 4 for(int x = 0; x < 100; x++){ 5 System.out.println(getName() + ":" + x); 6 7 } 8 } 9 }
C:禮讓線程
1 /* 2 * public static void yield():暫停當前正在執行的線程,並去執行其他線程 3 * 可以讓多個線程更加和諧,但是還不能保證完全均勻 4 */ 5 public class ThreadYieldDemo { 6 public static void main(String[] args) { 7 ThreadYield ty1 = new ThreadYield(); 8 ThreadYield ty2 = new ThreadYield(); 9 10 ty1.setName("a"); 11 ty2.setName("b"); 12 13 ty1.start(); 14 ty2.start(); 15 } 16 17 }
1 public class ThreadYield extends Thread{ 2 @Override 3 public void run() { 4 for(int x = 0; x < 100; x++){ 5 System.out.println(getName() + ":" + x); 6 7 Thread.yield(); 8 } 9 } 10 11 }
D:后台線程(守護線程)
1 /* 2 * public final void setDaemon(boolean on) 3 * 將該線程標記為守護線程或者用戶線程 4 * 當正在運行的線程都是守護線程時,java虛擬機退出 5 * 該方法必須在啟動前調用 6 */ 7 public class ThreadDaemonDemo { 8 9 public static void main(String[] args) { 10 ThreadDaemon td1 = new ThreadDaemon(); 11 ThreadDaemon td2 = new ThreadDaemon(); 12 13 td1.setName("aa"); 14 td2.setName("bb"); 15 16 //設置守護線程 17 td1.setDaemon(true); 18 td2.setDaemon(true); 19 20 td1.start(); 21 td2.start(); 22 23 //設置主類線程名稱 24 Thread.currentThread().setName("dd"); 25 for(int x = 0; x < 5; x++){ 26 System.out.println(Thread.currentThread().getName() + ":" + x); 27 } 28 } 29 }
1 public class ThreadDaemon extends Thread{ 2 @Override 3 public void run() { 4 for(int x = 0; x < 100; x++){ 5 System.out.println(getName() + ":" + x); 6 } 7 } 8 9 }
E:終止線程(掌握)
1 public class ThreadStopDemo { 2 3 public static void main(String[] args) { 4 ThreadStop ts = new ThreadStop(); 5 6 ts.start(); 7 8 try { 9 Thread.sleep(3000); 10 ts.interrupt(); 11 } catch (InterruptedException e) { 12 13 e.printStackTrace(); 14 } 15 } 16 }
1 public class ThreadStop extends Thread{ 2 @Override 3 public void run() { 4 System.out.println("開始時間:" + new Date()); 5 try { 6 Thread.sleep(10000); 7 } catch (InterruptedException e) { 8 9 //e.printStackTrace(); 10 System.out.println("線程被中斷"); 11 } 12 13 System.out.println("結束時間:" + new Date()); 14 } 15 16 }
(6)線程的生命周期
A:新建
B:就緒
C:運行
D:阻塞
E:死亡
(7)電影院賣票程序的實現
A:繼承Thread類
1 //繼承Thread類來實現 2 public class SellTicketDemo { 3 4 public static void main(String[] args) { 5 //三個窗口 創建三個線程對象 6 SellTicket st1 = new SellTicket(); 7 SellTicket st2 = new SellTicket(); 8 SellTicket st3 = new SellTicket(); 9 10 //設置線程對象名稱 11 st1.setName("窗口1"); 12 st2.setName("窗口2"); 13 st3.setName("窗口3"); 14 15 //啟動線程 16 st1.start(); 17 st2.start(); 18 st3.start(); 19 } 20 }
1 public class SellTicket extends Thread { 2 3 //定義100張票 4 //private int tickets = 100; 5 //為了讓多個線程對象共享這100張票,將其用靜態修飾 6 private static int tickets = 100; 7 @Override 8 public void run() { 9 //定義100張票 10 //這里每個線程進來都會走這里,相當於每個線程賣自己的100張票,將票數定義到外面 11 //int tickets = 100; 12 13 //表示電影院一直有票 14 while(true){ 15 if(tickets > 0){ 16 System.out.println(getName() + "正在出售第:" + (tickets--) + "張票"); 17 } 18 } 19 } 20 }
B:實現Runnable接口(多線程問題一般采用這種方法)
1 //實現Runnable接口的方法 2 public class SellTicketDemo { 3 4 public static void main(String[] args) { 5 //創建資源對象 6 SellTicket st = new SellTicket(); 7 8 //創建三個窗口(線程對象) 9 Thread t1 = new Thread(st, "窗口1"); 10 Thread t2 = new Thread(st, "窗口2"); 11 Thread t3 = new Thread(st, "窗口3"); 12 13 //啟動線程 14 t1.start(); 15 t2.start(); 16 t3.start(); 17 } 18 }
1 public class SellTicket implements Runnable { 2 //定義100張票 3 private int tickets = 100; 4 5 @Override 6 public void run() { 7 while(true){ 8 if(tickets > 0){ 9 System.out.println(Thread.currentThread() 10 .getName()+ "正在出售第:" + (tickets--) + "張票" ); 11 } 12 } 13 } 14 15 }
(8)電影院賣票程序出問題
A:為了更符合真實的場景,加入了休眠100毫秒。
B:加入休眠后賣票問題
a:同票多次賣
因為CPU的一次操作必須是原子性的
b:負數票
隨機性和延遲性導致
(9)多線程安全問題的原因
(也是以后判斷一個程序是否有線程安全問題的依據)
A:是否有多線程環境
B:是否有共享數據
C:是否有多條語句操作共享數據
(10)同步解決線程安全問題
A:同步代碼塊
synchronized(對象) {
需要被同步的代碼;
}
注意:同步可以解決安全問題的根本原因在於這個對象上面
該對象如同鎖的功能。多個線程必須是同一把鎖。
1 //實現Runnable接口的方法 2 public class SellTicketDemo { 3 4 public static void main(String[] args) { 5 //創建資源對象 6 SellTicket st = new SellTicket(); 7 8 //創建三個窗口(線程對象) 9 Thread t1 = new Thread(st, "窗口1"); 10 Thread t2 = new Thread(st, "窗口2"); 11 Thread t3 = new Thread(st, "窗口3"); 12 13 //啟動線程 14 t1.start(); 15 t2.start(); 16 t3.start(); 17 } 18 }
1 public class SellTicket implements Runnable { 2 //定義100張票 3 private int tickets = 100; 4 5 //創建鎖對象 同一把鎖 6 private Object obj = new Object(); 7 8 @Override 9 public void run() { 10 while(true){ 11 synchronized (obj) { 12 if(tickets > 0){ 13 try { 14 Thread.sleep(100); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 System.out.println(Thread.currentThread() 19 .getName()+ "正在出售第:" + (tickets--) + "張票" ); 20 } 21 } 22 } 23 } 24 }
這里的鎖對象可以是任意對象。
1 //實現Runnable接口的方法 2 public class SellTicketDemo { 3 4 public static void main(String[] args) { 5 //創建資源對象 6 SellTicket st = new SellTicket(); 7 8 //創建三個窗口(線程對象) 9 Thread t1 = new Thread(st, "窗口1"); 10 Thread t2 = new Thread(st, "窗口2"); 11 Thread t3 = new Thread(st, "窗口3"); 12 13 //啟動線程 14 t1.start(); 15 t2.start(); 16 t3.start(); 17 } 18 }
1 public class SellTicket implements Runnable { 2 //定義100張票 3 private int tickets = 100; 4 5 //創建鎖對象 同一把鎖 6 //private Object obj = new Object(); 7 //任意對象 8 private Demo d = new Demo(); 9 @Override 10 public void run() { 11 while(true){ 12 synchronized (d) { 13 if(tickets > 0){ 14 try { 15 Thread.sleep(100); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 System.out.println(Thread.currentThread() 20 .getName()+ "正在出售第:" + (tickets--) + "張票" ); 21 } 22 } 23 } 24 } 25 } 26 27 class Demo{ 28 29 }
B:同步方法
把同步加在方法上。
這里的鎖對象是this
1 //實現Runnable接口的方法 2 public class SellTicketDemo { 3 4 public static void main(String[] args) { 5 //創建資源對象 6 SellTicket st = new SellTicket(); 7 8 //創建三個窗口(線程對象) 9 Thread t1 = new Thread(st, "窗口1"); 10 Thread t2 = new Thread(st, "窗口2"); 11 Thread t3 = new Thread(st, "窗口3"); 12 13 //啟動線程 14 t1.start(); 15 t2.start(); 16 t3.start(); 17 } 18 }
1 public class SellTicket implements Runnable { 2 //定義100張票 3 private int tickets = 100; 4 5 //創建鎖對象 同一把鎖 6 //private Object obj = new Object(); 7 8 //任意對象 9 private Demo d = new Demo(); 10 11 private int x = 0; 12 @Override 13 public void run() { 14 while(true){ 15 if(x%2 == 0){ 16 synchronized (this) { 17 if(tickets > 0){ 18 try { 19 Thread.sleep(100); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 System.out.println(Thread.currentThread() 24 .getName()+ "正在出售第:" + (tickets--) + "張票" ); 25 } 26 } 27 28 }else{ 29 sellTicket(); 30 } 31 x++; 32 33 } 34 } 35 36 private synchronized void sellTicket() { 37 38 if(tickets > 0){ 39 try { 40 Thread.sleep(100); 41 } catch (InterruptedException e) { 42 e.printStackTrace(); 43 } 44 System.out.println(Thread.currentThread() 45 .getName()+ "正在出售第:" + (tickets--) + "張票" ); 46 } 47 } 48 49 } 50 51 class Demo{ 52 }
C:靜態同步方法
把同步加在方法上。
這里的鎖對象是當前類的字節碼文件對象(反射再講字節碼文件對象)
1 //實現Runnable接口的方法 2 public class SellTicketDemo { 3 4 public static void main(String[] args) { 5 //創建資源對象 6 SellTicket st = new SellTicket(); 7 8 //創建三個窗口(線程對象) 9 Thread t1 = new Thread(st, "窗口1"); 10 Thread t2 = new Thread(st, "窗口2"); 11 Thread t3 = new Thread(st, "窗口3"); 12 13 //啟動線程 14 t1.start(); 15 t2.start(); 16 t3.start(); 17 } 18 }
1 public class SellTicket implements Runnable { 2 //定義100張票 3 private static int tickets = 100; 4 5 //創建鎖對象 同一把鎖 6 //private Object obj = new Object(); 7 8 //任意對象 9 private Demo d = new Demo(); 10 11 private int x = 0; 12 @Override 13 public void run() { 14 while(true){ 15 if(x%2 == 0){ 16 synchronized (SellTicket.class) { 17 if(tickets > 0){ 18 try { 19 Thread.sleep(100); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 System.out.println(Thread.currentThread() 24 .getName()+ "正在出售第:" + (tickets--) + "張票" ); 25 } 26 } 27 28 }else{ 29 sellTicket(); 30 } 31 x++; 32 33 } 34 } 35 36 private static synchronized void sellTicket() { 37 38 if(tickets > 0){ 39 try { 40 Thread.sleep(100); 41 } catch (InterruptedException e) { 42 e.printStackTrace(); 43 } 44 System.out.println(Thread.currentThread() 45 .getName()+ "正在出售第:" + (tickets--) + "張票" ); 46 } 47 } 48 49 } 50 51 class Demo{ 52 }
(11)回顧以前的線程安全的類
A:StringBuffer
B:Vector
C:Hashtable
D:如何把一個線程不安全的集合類變成一個線程安全的集合類
用Collections工具類的方法即可。
1 public class ThreadDemo3 { 2 public static void main(String[] args) { 3 //線程安全的類 4 StringBuffer sb = new StringBuffer(); 5 Vector<String> v = new Vector<String>(); 6 Hashtable<String, String> hsah = new Hashtable<String, String>(); 7 8 //Vector是線程安全的時候考慮使用。但即使安全也不會考慮使用 9 //用下面這種安全的 10 List<String> list1 = new ArrayList<String>();//線程不安全 11 //線程安全 12 List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); 13 } 14 }