1.1、進程和線程
進程:一個應用程序一般都是一個進程,正在進行的程序
每一個進程最少都有一個線程,都有一個執行順序,該順序是一個執行路徑或者一個控制單元
線程:進程中一個獨立的控制單元,線程控制著進程的執行。
windows中的任務管理器,可以查看進程,linux下通過ps命令
線程是進程的最小單位
線程依賴于進程
線程隨著進程的創建和創建,隨著進程的結束而消亡
如迅雷:可以同時開啟多個下載,就是多線程
多個程序同時執行,時CPU在很快的切換,看上去是同時執行,實際上是在CPU在切換執行。
多線程存在的意義:可以讓程序中的內容同時執行。
2.1、繼承Thread,重寫run方法
package com.pb.thread.demo1;/** * * @author Denny * 繼承Thread 類并重寫run方法來創建線程類 * */public class MyThread extends Thread { PRivate int count; /* * 重寫run 方法 * @see java.lang.Thread#run() */ @Override public void run(){ /* * 要運行的代碼塊或者方法,在這里調用,或者直接寫在這里 */ while(count<10){ count++; System.out.println("My thread run方法啟動了"+count); } } public static void main(String[] args) { //聲明線程類 MyThread mt=new MyThread(); //啟動線程,JVM調用run方法 mt.start(); //主線程 for(int x=0;x<10;x++){ System.out.println("main thread"+x); }} }
多個線程都在搶占CPU的執行權,誰搶到,誰就執行
在某一時刻,只能有一個程序在運行
CPU在做著快速切換,以達到看上去是同時運行的效果。
多線程一個特性:隨機性,誰搶到誰執行.
三、線程的運行和線程狀態
3.1、調用start()方法
使該線程開始執行,Java虛擬機調用該線程的run()方法
為什么要重寫run方法.
Thread類用于描述線程。
這個類定義一個功能,用于存儲線程要運行的代碼。該存儲功能就是run方法.
也就是說,Thread類中的run方法是用于存儲線程要運行的代碼
如果直接調用run()方法,就是調用對象的普通方法一樣.,僅僅是對象調用方法.線程創建了并沒有運行。
只有start()才可以運行線程。
3.2、線程狀態
新生:創建線程 new Thread()或者子類
運行:正在運行的線程,調用start()方法
凍結:已經創建的線程,但非運行狀態的線程。sleep(long 毫秒), wait()線程等待,直到notify()喚醒才可以繼續運行
臨時狀態阻塞:具備運行資格,但沒有執行權,就是還沒有被CPU切換到.
消亡:線程死亡stop(),或者線程運行完成。
4.1、獲取當前線程對象和名稱
繼承thread類實現線程,局部變量都有單獨的一份不能共享數據
package com.pb.thread.demo1;/** * * @author Denny * 繼承Thread 類并重寫run方法來創建線程類 *Thread.currentThread(),獲取當前線程對象 *繼承thread類實現線程,局部變量都有單獨的一份不能共享數據 */public class MyThread extends Thread { public MyThread(String name){ super(name); } /* * 重寫run 方法 * @see java.lang.Thread#run() */ @Override public void run(){ /* * 要運行的代碼塊或者方法,在這里調用,或者直接寫在這里 */ for(int x=0;x<10;x++){ System.out.println("My thread run的名字:"+Thread.currentThread().getName()+","+x); } } public static void main(String[] args) { //聲明線程類 MyThread mt1=new MyThread("mt1"); // mt1.setName("張三"); 設置線程名稱 MyThread mt2=new MyThread("mt2"); //啟動線程,JVM調用run方法 mt1.start(); mt2.start(); //主線程 for(int x=0;x<10;x++){ System.out.println(currentThread().getName()+","+x); }} }
示例:
5.1、實現Runnable接口
重寫run方法()
實現接口的方式最大的好處是可以共享數據
示例:多窗口同時售票,
package com.pb.thread.demo1;/** * * @author Administrator 多個窗口同時賣票 * */public class Ticket implements Runnable { private int tick = 100; public void run() { while (true) { if (tick > 0) { System.out.println(Thread.currentThread().getName()+"賣:" + tick--); }else{ break; } } } public static void main(String[] args) { //聲明線程類 Ticket ticket=new Ticket(); //創建線程對象,并將類做為參數 Thread t1=new Thread(ticket); t1.setName("一號窗口,"); Thread t2=new Thread(ticket); t2.setName("二號窗口,"); Thread t3=new Thread(ticket); t3.setName("三號窗口,"); Thread t4=new Thread(ticket); t4.setName("四號窗口,"); t1.start(); t2.start(); t3.start(); t4.start(); }}
結果:
1 一號窗口,賣:100 2 一號窗口,賣:98 3 一號窗口,賣:97 4 一號窗口,賣:96 5 一號窗口,賣:95 6 一號窗口,賣:94 7 二號窗口,賣:99 8 二號窗口,賣:92 9 一號窗口,賣:93 10 一號窗口,賣:88 11 一號窗口,賣:87 12 一號窗口,賣:86 13 二號窗口,賣:89 14 二號窗口,賣:84 15 二號窗口,賣:83 16 二號窗口,賣:82 17 二號窗口,賣:81 18 二號窗口,賣:80 19 二號窗口,賣:79 20 二號窗口,賣:78 21 二號窗口,賣:77 22 二號窗口,賣:76 23 二號窗口,賣:75 24 二號窗口,賣:74 25 二號窗口,賣:73 26 二號窗口,賣:72 27 二號窗口,賣:71 28 二號窗口,賣:70 29 二號窗口,賣:69 30 二號窗口,賣:68 31 二號窗口,賣:67 32 二號窗口,賣:66 33 二號窗口,賣:65 34 二號窗口,賣:64 35 二號窗口,賣:63 36 二號窗口,賣:62 37 二號窗口,賣:61 38 二號窗口,賣:60 39 二號窗口,賣:59 40 二號窗口,賣:58 41 二號窗口,賣:57 42 二號窗口,賣:56 43 二號窗口,賣:55 44 二號窗口,賣:54 45 二號窗口,賣:53 46 二號窗口,賣:52 47 二號窗口,賣:51 48 二號窗口,賣:50 49 二號窗口,賣:49 50 二號窗口,賣:48 51 二號窗口,賣:47 52 二號窗口,賣:46 53 二號窗口,賣:45 54 二號窗口,賣:44 55 二號窗口,賣:43 56 二號窗口,賣:42 57 二號窗口,賣:41 58 二號窗口,賣:40 59 二號窗口,賣:39 60 二號窗口,賣:38 61 二號窗口,賣:37 62 二號窗口,賣:36 63 二號窗口,賣:35 64 二號窗口,賣:34 65 二號窗口,賣:33 66 二號窗口,賣:32 67 二號窗口,賣:31 68 二號窗口,賣:30 69 二號窗口,賣:29 70 二號窗口,賣:28 71 二號窗口,賣:27 72 二號窗口,賣:26 73 二號窗口,賣:25 74 二號窗口,賣:24 75 二號窗口,賣:23 76 二號窗口,賣:22 77 二號窗口,賣:21 78 三號窗口,賣:90 79 四號窗口,賣:91 80 三號窗口,賣:19 81 二號窗口,賣:20 82 一號窗口,賣:85 83 二號窗口,賣:16 84 三號窗口,賣:17 85 四號窗口,賣:18 86 三號窗口,賣:13 87 三號窗口,賣:11 88 三號窗口,賣:10 89 三號窗口,賣:9 90 三號窗口,賣:8 91 三號窗口,賣:7 92 三號窗口,賣:6 93 三號窗口,賣:5 94 三號窗口,賣:4 95 三號窗口,賣:3 96 三號窗口,賣:2 97 三號窗口,賣:1 98 二號窗口,賣:14 99 一號窗口,賣:15100 四號窗口,賣:12
兩種創建線程方式:
優點 | 缺點 | |
繼承Thread類 | 1.編寫簡單 2.可以使用this關鍵字直接訪問當前線程 | 無法繼承其它類 無法實現數據共享 |
實現Runnable接口 | 1.可以繼承其它類 2.多個線程之間可以使用同一個Runnable對象 3.可以共享數據 | 編程方式稍微復雜,如需訪問當前線程,需要調用Thread類的currentThread()方法 |
6.1、還是上面的例子
加上sleep(1000)睡覺1秒
package com.day10.thread.demo1;public class Ticket implements Runnable { private int num=100; @Override public void run() { while(true){ if(num>0){ try { Thread.sleep(1000); //當前線程睡覺1秒,毫秒單位 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+",賣出"+num--+"號票"); } } } public static void main(String[] args) { Ticket t=new Ticket(); Thread t1=new Thread(t); Thread t2=new Thread(t); Thread t3=new Thread(t); Thread t4=new Thread(t); t1.start(); t2.start(); t3.start(); t4.start(); }}
結果:
Picked up JAVA_TOOL_OPTIONS: -javaagent:/usr/share/java/jayatanaag.jar Thread-0,賣出100號票Thread-2,賣出99號票Thread-1,賣出98號票Thread-3,賣出97號票Thread-0,賣出96號票Thread-2,賣出95號票Thread-1,賣出94號票Thread-3,賣出93號票Thread-0,賣出92號票Thread-2,賣出91號票Thread-1,賣出90號票Thread-3,賣出89號票Thread-0,賣出88號票Thread-2,賣出87號票Thread-1,賣出86號票Thread-3,賣出85號票Thread-0,賣出84號票Thread-2,賣出83號票Thread-1,賣出82號票Thread-3,賣出81號票Thread-0,賣出80號票Thread-2,賣出79號票Thread-1,賣出78號票Thread-3,賣出77號票Thread-0,賣出76號票Thread-2,賣出75號票Thread-1,賣出74號票Thread-3,賣出73號票Thread-0,賣出72號票Thread-2,賣出71號票Thread-1,賣出70號票Thread-3,賣出69號票Thread-0,賣出68號票Thread-2,賣出67號票Thread-1,賣出66號票Thread-3,賣出65號票Thread-0,賣出64號票Thread-2,賣出63號票Thread-1,賣出62號票Thread-3,賣出61號票Thread-0,賣出60號票Thread-2,賣出59號票Thread-1,賣出58號票Thread-3,賣出57號票Thread-0,賣出56號票Thread-2,賣出55號票Thread-1,賣出54號票Thread-3,賣出53號票Thread-0,賣出52號票Thread-2,賣出51號票Thread-1,賣出50號票Thread-3,賣出49號票Thread-0,賣出48號票Thread-2,賣出47號票Thread-1,賣出46號票Thread-3,賣出45號票Thread-0,賣出44號票Thread-2,賣出43號票Thread-1,賣出42號票Thread-3,賣出41號票Thread-0,賣出40號票Thread-2,賣出39號票Thread-1,賣出38號票Thread-3,賣出37號票Thread-0,賣出36號票Thread-2,賣出35號票Thread-1,賣出34號票Thread-3,賣出33號票Thread-0,賣出32號票Thread-2,賣出31號票Thread-1,賣出30號票Thread-3,賣出29號票Thread-0,賣出28號票Thread-2,賣出27號票Thread-1,賣出26號票Thread-3,賣出25號票Thread-0,賣出24號票Thread-2,賣出23號票Thread-1,賣出22號票Thread-3,賣出21號票Thread-0,賣出20號票Thread-2,賣出19號票Thread-1,賣出18號票Thread-3,賣出17號票Thread-0,賣出16號票Thread-2,賣出15號票Thread-1,賣出14號票Thread-3,賣出13號票Thread-0,賣出12號票Thread-2,賣出11號票Thread-1,賣出10號票Thread-3,賣出9號票Thread-0,賣出8號票Thread-2,賣出7號票Thread-1,賣出6號票Thread-3,賣出5號票Thread-0,賣出4號票Thread-2,賣出3號票Thread-1,賣出2號票Thread-3,賣出1號票Thread-0,賣出0號票Thread-2,賣出-1號票Thread-1,賣出-2號票
發現多賣票了,
為什么?因為賣票當前線程睡眠了1秒,其它線程就可以把這張票已經賣出了,
條件檢查1>0,如果4個人同時檢查,條件成立,就繼續賣票就出現了,負數
怎么解決?給線程加鎖
1. 多個線程在操作共享的數據。
2. 操作共享數據的線程代碼有多條。
當一個線程在執行操作共享數據的多條代碼過程中,其他線程參與了運算,就會導致線程安全問題的產生。
解決方式:就是將多條操作共享數據的線程代碼封裝起來,當有線程在執行這些代碼的時候,其他線程不可以參與運算。必須要當前線程把這些代碼都執行完畢后,其他線程才可以參與運算。
看下面的同步synchronized
7.1、synchronized
同步代碼塊的格式:
synchronized(對象){
需要被同步的代碼;
}
同步的好處:解決了線程的安全問題。
同步的弊端:當線程相當多時,因為每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會降低程序的運行效率。
同步的前提:必須有多個線程并使用同一個鎖。
修改上面的代碼
package com.day10.thread.demo1;public class Ticket implements Runnable { private int num=100; @Override public void run() { while(true){ synchronized(this){ if(num>0){ try { Thread.sleep(1000); //當前線程睡覺1秒,毫秒單位 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+",賣出"+num--+"號票"); }else{ break; } } } } public static void main(String[] args) { Ticket t=new Ticket(); Thread t1=new Thread(t); Thread t2=new Thread(t); Thread t3=new Thread(t); Thread t4=new Thread(t); t1.start(); t2.start(); t3.start(); t4.start(); }}
8.1、同步方法
package com.day10.thread.demo2;public class Account { //余額 private double blance=500; //取款 public void withDraw(int num){ blance=blance-num; } public double getBlance() { return blance; } public void setBlance(double blance) { this.blance = blance; } }package com.day10.thread.demo2;public class AccountThread implements Runnable { private Account account=new Account(); @Override public void run() { for (int i = 0; i < 5; i++) { if(account.getBlance()<0){ System.out.println("透支了!!"); } makWithDraw(100); //每次取100 } } /* * 取款的方法 */ public void makWithDraw(int num){ //判斷余額是不是大于要取款的數 if(account.getBlance()>=num){ System.out.println(Thread.currentThread().getName()+":準備取款!"); try { Thread.sleep(1000); //等待1秒 account.withDraw(num); System.out.println(Thread.currentThread().getName()+":取款完成!,當前余額為:"+account.getBlance()); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName()+" 余額不足以支付當前取款, 余額為: "+account.getBlance()); } }}package com.day10.thread.demo2;public class Demo { public static void main(String[] args) { AccountThread at=new AccountThread(); Thread t1=new Thread(at); t1.setName("張三"); Thread t2=new Thread(at); t2.setName("張三老婆"); t1.start(); t2.start(); }}
結果:
張三:準備取款!張三老婆:準備取款!張三:取款完成!,當前余額為:400.0張三:準備取款!張三老婆:取款完成!,當前余額為:300.0張三老婆:準備取款!張三:取款完成!,當前余額為:200.0張三:準備取款!張三老婆:取款完成!,當前余額為:100.0張三老婆:準備取款!張三:取款完成!,當前余額為:0.0張三 余額不足以支付當前取款, 余額為: 0.0張三 余額不足以支付當前取款, 余額為: 0.0張三老婆:取款完成!,當前余額為:-100.0透支了!!張三老婆 余額不足以支付當前取款, 余額為: -100.0透支了!!張三老婆 余額不足以支付當前取款, 余額為: -100.0
加上同步關鍵字
/* * 取款的方法 */ public synchronized void makWithDraw(int num){ //判斷余額是不是大于要取款的數 if(account.getBlance()>=num){ System.out.println(Thread.currentThread().getName()+":準備取款!"); try { Thread.sleep(1000); //等待1秒 account.withDraw(num); System.out.println(Thread.currentThread().getName()+":取款完成!,當前余額為:"+account.getBlance()); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName()+" 余額不足以支付當前取款, 余額為: "+account.getBlance()); } }
結果:
張三:準備取款!張三:取款完成!,當前余額為:400.0張三老婆:準備取款!張三老婆:取款完成!,當前余額為:300.0張三:準備取款!張三:取款完成!,當前余額為:200.0張三:準備取款!張三:取款完成!,當前余額為:100.0張三:準備取款!張三:取款完成!,當前余額為:0.0張三 余額不足以支付當前取款, 余額為: 0.0張三老婆 余額不足以支付當前取款, 余額為: 0.0張三老婆 余額不足以支付當前取款, 余額為: 0.0張三老婆 余額不足以支付當前取款, 余額為: 0.0張三老婆 余額不足以支付當前取款, 余額為: 0.0
沒有發生透支現象
同步代碼塊:
/* * 取款的方法 */ public void makWithDraw(int num){ synchronized(account){ //判斷余額是不是大于要取款的數 if(account.getBlance()>=num){ System.out.println(Thread.currentThread().getName()+":準備取款!"); try { Thread.sleep(1000); //等待1秒 account.withDraw(num); System.out.println(Thread.currentThread().getName()+":取款完成!,當前余額為:"+account.getBlance()); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName()+" 余額不足以支付當前取款, 余額為: "+account.getBlance()); } } }
結果同上,沒有發生透支現象
9.1、同鎖
同步方法同步對象是this,同步代碼塊也可以使用this來同步
10.1、靜態同步
不是this,因為靜態方法中也不可以定義this
靜態進內存,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象
靜態的同步函數使用的鎖是該函數所屬字節碼文件對象,可以用getClass方法獲取,也可以用當前類名.class表示。
synchronizid(類名.class){ 或者對象.getClass
}
11.1、懶漢式加鎖
package com.day10.thread.demo2;/** * * @author denny *單例模式 *餓漢式,不存在復生成對象的問題 *懶漢工,加synchronize */public class SingleDemo { private static SingleDemo singleDemo; private SingleDemo(){ } public static SingleDemo getNewIntance(){ if(singleDemo==null){//判斷是不是空,不是空就直接返回,是空就加鎖 synchronized(SingleDemo.class){ //加鎖 //this.getClass() 直接寫對象singleDemo.getClass()也行 if(singleDemo==null){ singleDemo=new SingleDemo(); } } } return singleDemo; }}
12.1、死鎖常見情景之一:同步的嵌套。
class MyLock{ static Object lockA=new Object(); static Object lockB=new Object();}public class Demo implements Runnable { private boolean flag; public Demo(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { while (true) { synchronized (MyLock.lockA) { System.out.println("if.........lockA"); synchronized (MyLock.lockB) { System.out.println("if.........lockB"); } } } } else { while(true){ synchronized (MyLock.lockB) { System.out.println("else.........lockB"); synchronized (MyLock.lockA) { System.out.println("else.........lockA"); } } } } } public static void main(String[] args) { Thread t1 = new Thread(new Demo(true)); Thread t2 = new Thread(new Demo(false)); t1.start(); t2.start(); }}
建議將要鎖的對象,聲明為static
新聞熱點
疑難解答