線程
概念
1.程序:未解決某種問題,使用計算機語言編寫的一些列指令(代碼)的集合
2.進程:正在運行的程序(被加載到內存中),是操作系統進行資源分配的最小單位
3.線程:進程可以進一步細化為線程(比進程更小)且線程是隸屬于進程的,是操作系統執行的最小的執行單元 也是cpu
進行任務調度的最小單位
- 如 :運行的
QQ
也是一個進程,操作系統就會為這個進程分配資源 一個聊天窗口就是一個線程,線程隸屬于進程
tips:早期是沒有線程的,是以進程為單位執行的,進程的單位比較大,當一個進程運行時,其他進程就不能執行了,所以后來,將進程中的多個任務細化為線程,cpu
的執行單位,也是從進程進化為更小的線程
總結
- 一個進程可以包含多個線程
- 一個線程只能隸屬于一個進程,線程不能脫離進程單獨獨立運行
- 一個進程中至少有一個線程,即主線程,
java
中mian方法
就是用來啟動主線程 - 在主線程中可以創建并啟動其他線程,所有線程都共享進程的內存資源
- 所有線程都共享進程資源
Thread類
在Java
中Thread
表示線程,提供了許多的方法,來對線程的控制,可以通過繼承Thread
類來實現線程
Thread常用方法
Thread.currentThread();
- 獲取當前運行的線程
run();
- 線程要執行的任務
start();
- 啟動
Java
線程
- 啟動
setName(String name);
- 設置線程名字
(String)getName();
- 獲取線程名字
getPriority();
- 獲取線程優先級
setPriority();
- 設置線程優先級 1~10一般線程默認優先 5
sleep(long ms);
- 設置線程休眠
join();
- 讓其他線程等待這個線程結束后,其他線程再執行
Thread構造方法
new Thread(Runnable runnable);
- 接受一個任務對象
new Thread(Runnable runnable,String name);
- 接受一個對象 并為對象設置名字
使用上面方法進行實例
線程1
public class MyThread extends Thread{//重寫run方法,在run中執行我們要執行的方法@Overridepublic void run() {for(int i=0;i<10000;i++){System.out.println("循環main"+i);}}}
線程2
public class MyThread2 extends Thread {@Overridepublic void run() {for(int i=0;i<10000;i++){System.out.println("循環main2"+i);}}
}
public class 線程 {public static void main(String[] args) {for(int i=0;i<10000;i++){System.out.println("循環1 "+i);}for(int i=0;i<10000;i++){System.out.println("循環2 "+i);}for(int i=0;i<10000;i++){System.out.println("循環3 "+i);}for(int i=0;i<10000;i++){System.out.println("循環4 "+i);}/*這樣不管怎么執行都是單線程,從上到下依次執行,所以如果想在java程序中有幾件不想管的事件同時執行可以在java中創建線程,把一些需要執行的任務放在線程中執行,這樣就擁有讓cup執行的權利*/MyThread myThread = new MyThread();//調用start方法讓程序多線程進行,重寫run方法但不能調用run,不然只是普通方法的調用,依然是單線程myThread.start();MyThread2 myThread2 = new MyThread2();myThread2.start();}}
觀察結果 可知 MyThread
與MyThread2
是同時進行的
Runnable接口
Java中我們也可以實現Runnable接口來進行對線程的實現
思路.
-
創建一個類 實現
Runnable接口
重寫run方法 -
在main函數使用
Thread構造方法
傳入Runnable對象
-
public class MyTask implements Runnable{@Overridepublic void run() {for(int i=0;i<10;i++){System.out.println("啊");}} }
-
MyTask myTask = new MyTask();//創建一個線程Thread thread = new Thread(myTask);
Callable接口
Java中也可以實現Callable接口 重寫 call()方法 來對線程的實現
思路.
- 重寫
call()
方法call()
方法有返回值 可以拋出異常,還可以處理不同類型 - 依然使用
Thread
構造方法來創建一個線程
關于線程的其他概念
線程生命周期
從線程到銷毀期間經歷五個狀態
守護線程(也是線程的一種)
setDaemon();
如果一個**線程
是守護線程**,那么它會等待java
中其他線程任務結束后,自動終止- 守護線程是為其他線程提供服務的,eg:
jvm
中的垃圾回收機制,就是一個守護線程
多線程
在一個應用程序中,存在多個線程,不同線程可以并行的執行任務
*操作系統線程任務調度算法
- 先來先服務調度算法
- 短作業優先調度算法
- 優先級調度算法
- 高響應比優先調度算法
- 時間片輪轉調度算法
- 多級反饋隊列調度算法(集合前幾種算法的優點)
對線程進行加鎖
分析
- 多線程的優點
- 提高程序的處理能力
- 提高CPU的 利用率
- 缺點
- 線程也是需要占用內存資源和CPU資源
- 多個線程對同一個共享資源進行訪問,會出現線程安全問題
對于內存資源與CPU資源我們可以通過不斷增加內存來解決
對于線程安全問題 我們則需要對 線程進行加鎖
synchronized
synchronized 修飾代碼塊需要加一個同步對象 synchronized(同步對象)
同步對象要求
- 多線程用到的對象必須是同一個對象
- 可以是
java
類中任何類對象. - 作用:用來記錄有沒有線程進入到同步代碼塊中
修飾方法
- 鎖不需要我們提供,會默認提供鎖對象
- synchronized如果修飾的是非靜態方法,鎖的對象是this
- 如果是靜態方法,鎖的對象是Class的對象 (一個類只能有一個對象)
修飾代碼塊例子
package day17;public class PrintNums implements Runnable{//實現線程的交替打印int num=100;Object object = new Object();@Overridepublic void run() {while(true){synchronized (object){object.notify();//喚醒線程if(num==0){break;}System.out.println(Thread.currentThread().getName()+"."+num+".");num--;try {object.wait();//線程等待} catch (InterruptedException e) {e.printStackTrace();}}}}
}
package day17;public class TestPrintNums {public static void main(String[] args) {PrintNums printNums = new PrintNums();Thread t1 = new Thread(printNums,"1");Thread t2 = new Thread(printNums,"2");Thread t3 = new Thread(printNums,"3");t1.start();t2.start();t3.start();}
}
于是我們實現了交替打印 每次只能進入一個線程
修飾方法(同步對象會有默認的)
- 鎖不需要我們提供,會默認提供對象鎖
synchronized
如果修飾的是非靜態方法,鎖的對象是this
? synchronized
如果修飾的是靜態方法,鎖的對象是類的Class對象
,一個類只有 一個對象
-
如果是非靜態方法,那么同步對象是this(如果是非靜態對象,我們可以使用Runnable創建線程方法)
-
如果是靜態方法,所得對象是當前類的Class對象
-
當前類的Class對象
:一個類加載到內存后會為這個類創建唯一的Class類的對象 -
Class類實例表示正在運行的Java應用程序中的類和接口
-
修飾靜態方法 public class SellTicket extends Thread {static int counts = 10;//10張票static Object object = new Object();@Overridepublic void run() {while (counts > 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}sellTicket();}}public static synchronized void sellTicket() {if(counts>0){System.out.println(Thread.currentThread().getName()+"買了"+counts+"張票");--counts;}} } public class Test {public static void main(String[] args) {SellTicket t1 = new SellTicket();t1.setName("窗口1");SellTicket t2 = new SellTicket();t2.setName("窗口2");t1.start();t2.start();} }
-
修飾非靜態方法 public class SellTickets2 implements Runnable{int t=20;//票數@Overridepublic void run() {while(t>0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}sell2();}}public synchronized void sell2(){if(t>0){System.out.println(Thread.currentThread().getName()+"買了"+t+"張票");--t;}} }public class Test2 {public static void main(String[] args) {SellTickets2 sellTickets2 = new SellTickets2();Thread t1 = new Thread(sellTickets2,"窗口1");Thread t2 = new Thread(sellTickets2,"窗口2");t1.start();t2.start();} }
ReentrantLock類加鎖
- 只能對某一代碼塊進行加鎖,不能對整個方法加鎖
- 加鎖方法如下
ReentrantLock r = new ReentrantLock();//創建 ReentrantLock對象
r.lock();<---------|//加鎖|............. |------被鎖部分(一次只進去一個線程)|
r.unlock();<-------|//解鎖
tips:r.unlock();
最好寫在finally{};代碼塊中,保證發生異常時,也能夠解鎖;
synchronized與ReentrantLock的區別
-
synchronized
是一個關鍵字,ReentrantLock
是一個類 -
synchronized
修飾代碼塊和方法,ReentrantLock
只能修飾代碼塊 -
synchronized
可以隱式的加鎖和釋放鎖,運行出現異常可以自動釋放鎖ReentrantLock
需要手動加鎖和釋放鎖,建議在finally
代碼中釋放鎖
常用的三個方法 wait ``notify
notifyAll
-
wait()
;方法使當前線程進入等待狀態,直到另一個線程調用該對象的notify()
或notifyAll()
方法來喚醒它 -
notify();
方法喚醒在該對象上調用wait()
方法進入等待狀態的一個線程,如果有多個線程在等待,則只會喚醒其中一個線程。 -
notifyAll();
方法喚醒在該對象上調用wait()
方法進入等待狀態的所有線程。? tips:1.都是Object類中定義的方法
? 2.三個方法必須在同步代碼塊中使用
? 3.這三個方法必須通過為鎖的對象調用