執行緒與行程
行程:是指一個記憶體中運行的應用程式,每個行程都有一個獨立的記憶體空間,一個應用程式可以同時運行多個行程;行程也是程式的一次執行程序,是系統運行程式的基本單位;系統運行一個程式即是一個行程從創建、運行到消亡的程序,
執行緒:是行程中的一個執行單元,負責當前行程中程式的執行,一個行程中至少有一個執行緒,一個行程中是可以有多個執行緒的,這個應用程式也可以稱之為多執行緒程式,
行程與執行緒的區別
行程:有獨立的記憶體空間,行程中的資料存放空間(堆空間和堆疊空間)是獨立的,至少有一個執行緒,
執行緒:堆空間是共享的,堆疊空間是獨立的,執行緒消耗的資源比行程小的多,
- 因為一個行程中的多個執行緒是并發運行的,那么從微觀角度看也是有先后順序的,哪個執行緒執行完全取決于 CPU 的調度,程式員是干涉不了的,而這也就造成的多執行緒的隨機性,
- Java 程式的行程里面至少包含兩個執行緒,主行程也就是 main()方法執行緒,另外一個是垃圾回識訓制執行緒,每當使用 java 命令執行一個類時,實際上都會啟動一個 JVM,每一個 JVM 實際上就是在作業系統中啟動了一個執行緒,java 本身具備了垃圾的收集機制,所以在 Java 運行時至少會啟動兩個執行緒,
- 由于創建一個執行緒的開銷比創建一個行程的開銷小的多,那么我們在開發多任務運行的時候,通常考慮創建多執行緒,而不是創建多行程,
執行緒的創建
繼承Thread類
Java使用java.lang.Thread類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的實體,
Java中通過繼承Thread類來創建并啟動多執行緒的步驟如下:
- 定義一個類繼承Thread類
- 重寫run 方法(執行緒任務)
- 開啟執行緒
創建子類物件
呼叫thread類的start方法
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("sub... "+i);
}
}
}
public static void main(String[] args) {
// 創建自定義的執行緒物件
MyThread mt = new MyThread();
// 開啟執行緒
mt.start();
// 一個執行緒僅可以被啟動一次
// mt.start()
// 主執行緒執行
for (int i = 0; i < 50; i++) {
System.out.println("main... "+i);
}
}
執行緒名字的設定和獲取
-
Thread類的方法
String getName()可以獲取到執行緒的名字, -
Thread類的方法
setName(String name)設定執行緒的名字, -
通過Thread類的構造方法
Thread(String name)也可以設定執行緒的名字,
public class Demo {
public static void main(String[] args) {
MyThread mt = new MyThread();
//設定執行緒名字
mt.setName("旺財");
mt.start();
}
}
class MyThread extends Thread{
public void run(){
System.out.println("執行緒名字:"+super.getName());
}
}
執行緒是有默認名字的,如果不設定,JVM會賦予執行緒默認名字Thread-0,Thread-1,
獲取運行main方法執行緒的名字
Demo類不是Thread的子類,因此不能使用getName()方法獲取,
Thread類定義了靜態方法static Thread currentThread()獲取到當前正在執行的執行緒物件,
public static void main(String[] args){
Thread t = Thread.currentThread();
System.out.println(t.getName());
}
實作Runnable介面
java.lang.Runnable介面類
步驟如下:
- 定義Runnable介面的實作類,并重寫該介面的run()方法,該run()方法的方法體同樣是該執行緒的執行緒執行體,
- 創建Runnable實作類的實體,并以此實體作為Thread的target來創建Thread物件,該Thread物件才是真正的執行緒物件,
- 呼叫執行緒物件的start()方法來啟動執行緒,
public class Demo {
public static void main(String[] args) {
// 創建自定義類物件 執行緒任務物件
MyRunnable mr = new MyRunnable();
// 創建執行緒物件
Thread t = new Thread(mr);
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("main " + i);
}
}
}
public class MyRunnable implements Runnable{
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
通過實作Runnable介面,使得該類有了多執行緒類的特征,run()方法是多執行緒程式的一個執行目標,所有的多執行緒代碼都在run方法里面,Thread類實際上也是實作了Runnable介面的類,
在啟動的多執行緒的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出物件,然后呼叫Thread物件的start()方法來運行多執行緒代碼,
實際上所有的多執行緒代碼都是通過運行Thread的start()方法來運行的,
匿名內部類方式創建執行緒
使用執行緒的內匿名內部類方式,可以方便的實作每個執行緒執行不同的執行緒任務操作,
public static void main(String[] args) {
// 第一種方式 實際上需要的就是Thread類的子類物件
Thread t = new Thread(){
@Override
public void run() {
System.out.println("方式一 賦值!");
}
};
t.start();
new Thread() {
@Override
public void run() {
System.out.println("方式一 直接呼叫!");
}
}.start();
// 第二種方式 實際上需要的就是Runnable介面的實作類物件
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("方式二 賦值!");
}
};
new Thread(r).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("方式二 直接呼叫!");
}
}).start();
}
Thread和Runnable的區別
如果一個類繼承Thread,則不適合資源共享,但是如果實作了Runable介面的話,則很容易的實作資源共享,
總結:
實作Runnable介面比繼承Thread類所具有的優勢:
- 適合多個相同的程式代碼的執行緒去共享同一個資源,
- 可以避免java中的單繼承的局限性,
- 增加程式的健壯性,實作解耦操作,代碼可以被多個執行緒共享,代碼和執行緒獨立,
Thread類API
睡眠sleep
public static void sleep(long time) 讓當前執行緒進入到睡眠狀態,到毫秒后自動醒來繼續執行
public class Test{
public static void main(String[] args){
for(int i = 1;i<=5;i++){
Thread.sleep(1000);
System.out.println(i)
}
}
}
設定執行緒優先級
執行緒的切換是由執行緒調度控制的,我們無法通過代碼來干涉,但是我們通過提高執行緒的優先級最大程度的改善執行緒獲取時間片的幾率
執行緒的優先級被劃分為10級,值分別為1-10,其中1最低,10最高.Thread提供了3個常量來表示:
public static final int MIN_PRIORITY //1 最低優先級
public static final int NORM_PRIORITY //5 默認優先級
public static final int MAX_PRIORITY //10 最大優先級
// 更改執行緒的優先級
public final void setPriority(int newPriority)
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("t1: " + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("t2: " + i);
}
}
});
// 設定優先級
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
join
讓當前執行緒等待,呼叫方法的執行緒進行插隊先執行,執行完畢后,在讓當前執行緒執行.對其他執行緒沒有任何影響.
注意 此處的當前執行緒不是呼叫方法的執行緒 而是Thread.currentThread().
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("t1: " + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("t2: " + i);
}
}
});
t1.start();
// t1.join();
/* t1在此處插隊時
此時 Thread.currentThread() 為 main 執行緒, 所以 main 執行緒等待, t1 執行緒插隊
由于main 執行緒等待 t2執行緒還未開啟, 因此t1執行完畢, main和t2搶
*/
t2.start();
t1.join();
/* t1在此處插隊時
此時 Thread.currentThread() 依然為 main 執行緒, 所以 main 執行緒等待, t1 執行緒插隊
而在這之前 t2 執行緒已經開啟, 因此會出現兩種情況:
1. t2 先執行完 t1 執行完之后 主執行緒執行
2. t1 先執行完 t2 和 主執行緒搶
*/
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
面試題
三個執行緒同時開啟 保證執行緒一之后執行執行緒二 再之后執行執行緒三
class JoinMethodTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("t1: " + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 50; i++) {
System.out.println("t2: " + i);
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 50; i++) {
System.out.println("t3: " + i);
}
}
});
t1.start();
t2.start();
t3.start();
}
}
執行緒停止
public final void stop() 直接中斷執行緒 此方法已過時 不安全
public boolean isInterrupted() 獲取執行緒是否中斷的狀態 如果中斷回傳true 沒中斷回傳false
public void interrupt() 中斷執行緒 此時呼叫interrupted方法 會回傳true
代碼演示:
public class Test02 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <1000000 ; i++) {
System.out.println(i);
//是否有人要中斷執行緒 如果有回傳true 如果沒有回傳false
//讓執行緒中斷更為平滑 可以使用代碼來控制中斷
boolean b = Thread.currentThread().isInterrupted();
if(b){
break;
}
}
}
});
t1.start();
try {
Thread.sleep(2000);
// t1.stop(); //方法已經過時 不安全
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
用戶執行緒與守護執行緒
java分為兩種執行緒:用戶執行緒和守護執行緒
/*
用戶執行緒
我們正常寫的代碼都是用戶執行緒
守護執行緒
用戶執行緒存在 守護執行緒可以執行 用戶執行緒執行完成 守護執行緒即使沒有執行完 jvm也會退出
守護執行緒和用戶執行緒沒有本質的區別,唯一不同之處就在于虛擬機的退出:
如果用戶執行緒已經全部退出運行了,只剩下守護執行緒存在,虛擬機會直接退出.但是只要有用戶執行緒運行,虛擬機就不會退出.
設定守護執行緒:
public final void setDaemon(boolean on) on的值為true將執行緒設定為守護執行緒,需要在開啟執行緒之前設定
*/
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1: " + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2: " + i);
}
}
});
/*
由于t2每次休眠50ms, 而t1休眠200ms, 所以t2先執行完
而t1并不是守護執行緒, 所以t2執行完后, t1繼續執行
*/
// t1.start();
// t2.start();
//將t1設定為守護執行緒, t2執行完后, t1將不再執行, jvm直接退出
t1.setDaemon(true);
t1.start();
t2.start();
}
執行緒安全
多條執行緒操作同一資源時 可能出現資料錯誤 此時執行緒是不安全的
public class Demo {
public static void main(String[] args) {
SellTickets st = new SellTickets();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
Thread t3 = new Thread(st);
t1.start();
t2.start();
t3.start();
}
}
class SellTickets implements Runnable {
private int i = 20;
public void run() {
while (true) {
if (i > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 賣票 " + i--);
}
}
}
}
/*
Thread-2 賣票 19
Thread-0 賣票 20
Thread-1 賣票 18
Thread-1 賣票 17
Thread-2 賣票 15
Thread-0 賣票 16
Thread-2 賣票 14
Thread-0 賣票 13
Thread-1 賣票 12
Thread-1 賣票 10
Thread-0 賣票 9
Thread-2 賣票 11
Thread-2 賣票 8
Thread-0 賣票 8
Thread-1 賣票 6
Thread-0 賣票 5
Thread-2 賣票 4
Thread-1 賣票 3
Thread-2 賣票 2
Thread-1 賣票 1
Thread-0 賣票 0
Thread-2 賣票 -1
*/
發現程式出現了兩個問題:
- 相同的票數
- 不存在的票,比如0票與-1票,是不存在的,
執行緒同步
當我們使用多個執行緒訪問同一資源的時候,且多個執行緒中對資源有寫的操作,就容易出現執行緒安全問題,
要解決上述多執行緒并發訪問一個資源的安全性問題:也就是解決重復票與不存在票問題,Java中提供了同步機制(synchronized)來解決,
根據案例簡述:
視窗1執行緒進入操作的時候,視窗2和視窗3執行緒只能在外等著,視窗1操作結束,視窗1和視窗2和視窗3才有機會進入代碼去執行,
也就是說在某個執行緒修改共享資源的時候,其他執行緒不能修改該資源,等待修改完畢同步之后,才能去搶奪CPU資源,完成對應的操作,保證了資料的同步性,解決了執行緒不安全的現象,
為了保證每個執行緒都能正常執行原子操作,Java引入了執行緒同步機制,
同步代碼塊
同步代碼塊:執行緒操作的共享資料進行同步,synchronized關鍵字可以用于方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問,
// 格式
synchronized(同步鎖){
需要同步操作的代碼
}
同步鎖:
同步鎖又稱為物件監視器,同步鎖只是一個概念,可以想象為在物件上標記了一個鎖.
- 鎖物件 可以是任意型別,
- 多個執行緒物件 要使用同一把鎖,
注意:在任何時候,最多允許一個執行緒擁有同步鎖,誰拿到鎖就進入代碼塊,其他的執行緒只能在外等著(BLOCKED),
使用同步代碼塊解決代碼:
public class Ticket implements Runnable{
private int ticket = 100;
private Object lock = new Object();
/*
* 執行賣票操作
*/
@Override
public void run() {
//每個視窗賣票的操作
//視窗 永遠開啟
while(true){
synchronized (lock) {
if(ticket>0){//有票 可以賣
//出票操作
//使用sleep模擬一下出票時間
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//獲取當前執行緒物件的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket--);
}
}
}
}
}
注意:執行緒運行至同步代碼塊的時候,需要判斷鎖,獲取鎖,出去同步代碼塊后要釋放鎖,增加了很多操作,因此執行緒安全,程式的運行速度慢!
同步方法
同步方法:當一個方法中的所有代碼,全部是執行緒操作的共享資料的時候,可以將整個方法進行同步,使用synchronized修飾的方法,就叫做同步方法,保證A執行緒執行該方法的時候,其他執行緒只能在方法外等著,
// 格式
public synchronized void method(){
// 可能會產生執行緒安全問題的代碼
}
使用同步方法代碼如下:
public class Ticket implements Runnable{
private int ticket = 100;
/*
* 執行賣票操作
*/
@Override
public void run() {
//每個視窗賣票的操作
//視窗 永遠開啟
while(true){
sellTicket();
}
}
/*
* 鎖物件 是 誰呼叫這個方法 就是誰
* 隱含 鎖物件 就是 this
*
*/
public synchronized void sellTicket(){
if(ticket>0){//有票 可以賣
//出票操作
//使用sleep模擬一下出票時間
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//獲取當前執行緒物件的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket--);
}
}
}
同步鎖是誰?
-
對于非static方法,同步鎖就是this,
-
對于static方法,我們使用當前方法所在類的位元組碼物件(類名.class),
Lock鎖
java.util.concurrent.locks.Lock機制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向物件,
Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了,如下:
public void lock():加鎖,public void unlock():釋放鎖,
使用如下:
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 執行賣票操作
*/
@Override
public void run() {
//每個視窗賣票的操作
//視窗 永遠開啟
while(true){
lock.lock();
if(ticket>0){//有票 可以賣
//出票操作
//使用sleep模擬一下出票時間
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//獲取當前執行緒物件的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket--);
}
lock.unlock();
}
}
}
執行緒狀態
這里先列出各個執行緒狀態發生的條件,下面將會對每種狀態進行詳細決議,
| 執行緒狀態 | 導致狀態發生條件 |
|---|---|
| NEW(新建) | 執行緒剛被創建,但是并未啟動,還沒呼叫start方法, |
| Runnable(可運行) | 執行緒可以在java虛擬機中運行的狀態,可能正在運行自己代碼,也可能沒有,這取決于作業系統處理器, |
| Blocked(鎖阻塞) | 當一個執行緒試圖獲取一個物件鎖,而該物件鎖被其他的執行緒持有,則該執行緒進入Blocked狀態;當該執行緒持有鎖時,該執行緒將變成Runnable狀態, |
| Waiting(無限等待) | 一個執行緒在等待另一個執行緒執行一個(喚醒)動作時,該執行緒進入Waiting狀態,進入這個狀態后是不能自動喚醒的,必須等待另一個執行緒呼叫notify或者notifyAll方法才能夠喚醒, |
| Timed Waiting(計時等待) | 同waiting狀態,有幾個方法有超時引數,呼叫他們將進入Timed Waiting狀態,這一狀態將一直保持到超時期滿或者接收到喚醒通知,帶有超時引數的常用方法有Thread.sleep 、Object.wait, |
| Teminated(被終止) | 因為run方法正常退出而死亡,或者因為沒有捕獲的例外終止了run方法而死亡, |
等待和喚醒
Object類的方法
void wait()
// 在其他執行緒對用此物件的notify()方法或notifyAll方法前, 導致當前執行緒等待
void wait(long time)
// 當前執行緒等待 time 毫秒
void notify()
// 喚醒在此物件監視器上等待的單個執行緒
void notifyAll()
// 喚醒在此物件監視器上等待的所有執行緒
public class RelatedInObject {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (o) {
System.out.println("開始等待");
try {
o.wait();
// 等待3000毫秒后
// o.wait(3000);
System.out.println("已經被喚醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("end");
}
}).start();
Thread.sleep(5000);
synchronized (o) {
System.out.println("開始喚醒");
o.notify();
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/549973.html
標籤:其他
上一篇:13檔案操作
