Java–多執行緒
1.對多執行緒的基礎理解
執行緒是行程執行的一個路線,在同一個行程中的,執行緒共享行程的記憶體空間,執行緒間可以自由切換,并發執行,在Java開發的程序中,設計的應用程式,往往都是多個執行緒同時作業的,這樣能夠提高程式的執行效率,但是也帶來了編程的復雜,需要解決多執行緒之間資源分配的問題,在本篇的博客中,主要記錄的是Java實作多執行緒的幾個方式,以及基礎的概念和常用的方法操作,
2.創建執行緒的方式
-
繼承Thread類,重寫run方法
-
實作Runnable介面,撰寫run方法
-
實作Callable介面,撰寫call方法
1) 繼承Threead的方式,參考例子如下:
// 定義MyThread繼承Thread型別,并重寫run方法,在run方法內撰寫創建的執行緒要執行的任務, public class MyThread extends Thread { // 執行緒要執行的方法 // 通過thread物件的start方法執行,是一條新的執行路徑 @Override public void run() { for (int i=0;i<100;i++){ System.out.println("窗前明月光"+i); } } }// 創建MyThread類,并呼叫start方法,然后執行緒會開始執行run方法 public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); for (int i=0;i<100;i++){ System.out.println("疑是地上霜"+i); } }通過上面運行的程式可以觀察到,myThread執行的任務和主執行緒列印任務是有交叉執行的,說明Java中執行緒之間的運行是搶占式的,執行緒搶到資源后,可以執行,沒有搶到足夠資源的執行緒需要等待下一次的搶占,
2)實作Runnable的方式,可以參考下面的例子
// 定義MyRunnable實作Runnable介面,并實作run方法,run方法內撰寫執行緒執行的任務, public class MyRunnable implements Runnable { @Override public void run() { for (int i=0;i<20;i++){ System.out.println("鋤禾日當午"+i); } } }// 將MyRunnable物件作為創建Thread物件的引數,然后呼叫start方法執行執行緒 public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); thread.start(); for (int i=0;i<20;i++){ System.out.println("汗滴禾下土"+i); } }特點:
1. 通過創建任務,給執行緒分配任務,更適合多個執行緒同時執行相同的任務; 2. 可以避免單繼承的局限性; 3. 任務和執行緒本身是分離,提高程式的健壯性 4. 執行緒池的操作,只支持實作Runnable介面的型別的任務,不接受繼承Thread的執行緒
3)實作Callable方法
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 創建MyCallable物件
MyCallable myCallable = new MyCallable();
// 2. 創建FutureTask物件,并將MyCallable物件作為創建物件的引數
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
// 3. 呼叫Thread物件的start方法執行執行緒
new Thread(futureTask).start(); // 執行執行緒
// 4. 可以通過get方法獲取執行緒執行完成時的回傳值
System.out.println(futureTask.get()); // 輸出任務回傳值
}
特點: 可以攜帶執行緒任務執行完成后回傳的引數
3. 執行緒的一些常用方法
-
設定和獲取執行緒的名字
// 設定當前執行緒的名字 Thread.currentThread().setName("執行緒1"); // 獲取當前執行緒的名字 Thread.currentThread().getName(); -
執行緒休眠函式
Thread.sleep(1000); // 單位是ms -
執行緒中斷
// 終端執行緒執行可以呼叫interrupt()函式,通過中斷可以終止執行的執行緒 public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new MyRunnable()); t1.start(); for (int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+":"+i); Thread.sleep(1000); } t1.interrupt(); // 中斷t1執行緒,通知他進行其他操作,可以是終止自身執行緒,或者是進行其他的操作 } static class MyRunnable implements Runnable{ @Override public void run() { for (int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+":"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { // e.printStackTrace(); // 接收到中斷信號時,終止任務/或者開始其他的任務 System.out.println("接收到中斷信號時,終止任務"); return; // 終止任務 } } } } -
設定守護執行緒
通過呼叫執行緒物件的setDaemon()方法,并傳入true可以設定執行緒為守護執行緒
Java中的執行緒可以分成用戶執行緒和守護執行緒,下面是這兩種執行緒的特點;
用戶執行緒,當行程中沒有一個用戶執行緒的時候,行程結束
守護執行緒,守護用戶執行緒,當最后一個用戶執行緒結束時,所有的守護執行緒死亡舉例如下:
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new MyRunnable()); t1.setDaemon(true); // 設定t1為守護執行緒,如果所有的用戶執行緒結束了,t1守護執行緒也會停止執行 t1.start(); for (int i=0;i<5;i++){ // 比較快結束 System.out.println(Thread.currentThread().getName()+":"+i); Thread.sleep(1000); } } static class MyRunnable implements Runnable{ @Override public void run() { for (int i=0;i<10;i++){ // 在所有用戶執行緒結束時,作為守護執行緒的它也會結束 System.out.println(Thread.currentThread().getName()+":"+i); } } }
4.執行緒同步
? 在多執行緒的環境下,執行緒的執行往往是多樣的,當這多個執行緒在同時對一個資料進行更新操作的時候,可能會因為同時操作的原因,導致資料出現錯誤,不滿足實際的情況,在這種情況下,如果需要保證資料的安全可靠,就要是每個對資料進行更新的執行緒排隊執行,這樣才能保證資料不會超出合理的范圍,保證每次操作的合理性,這種排隊的行為是執行緒同步的一種具體解釋.
Java中為了保證執行緒安全,使執行緒同步執行的方式有以下三種:
-
同步代碼塊
-
同步方法
-
顯示鎖Lock
下面詳細介紹這三種的使用方法,并給出一些例子:
首先給出一個會出現執行緒不安全的情況,這個例子演示的是多個執行緒同時進行買票,最后會出現剩余票數是負數的情況,這是不合理的現象,示例如下:
// 模擬執行緒不安全的情景:買票執行緒
public static void main(String[] args) {
Ticekt ticekt = new Ticekt(); // 一個物件
new Thread(ticekt).start(); // 三個買票執行緒
new Thread(ticekt).start();
new Thread(ticekt).start();
}
static class Ticekt implements Runnable{
private int num = 10; // 余票數量
@Override
public void run() {
while (num>0){ // 票數>0,繼續賣
System.out.println("準備買出一張票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("出票成功!");
num--;
System.out.println("當前余票數:"+num);
}
}
}
1.同步代碼塊
同步代碼塊使用關鍵字synchronize,引數是一個鎖物件,然后將同步執行的代碼塊用{}包圍,當遇到同一個鎖物件的時候,這些執行緒會同步執行,如果使不同的鎖物件的時候沒有任何影響,因此,如果希望同步執行的話,要注意鎖物件要是同一個物件,
修改上面不安全的買票程式,做出下面的例子:
// synchronize,利用鎖物件,
// 當要進入同一個鎖的代碼塊的時候會先檢查這個鎖有沒有被占用,
// 被占用則等待,否則占用鎖進入代碼塊
public static void main(String[] args) {
Ticekt ticekt = new Ticekt();
new Thread(ticekt).start();
new Thread(ticekt).start();
new Thread(ticekt).start();
}
static class Ticekt implements Runnable{
private int num = 10;
private Object o =new Object(); // 鎖物件
@Override
public void run() {
while (true){
synchronized (o){ // 同步代碼塊開始
if (num>0){
System.out.println("準備買出一張票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("出票成功!");
num--;
System.out.println(Thread.currentThread().getName()+":當前余票數:"+num);
}else
break;
} // 同步代碼塊結束
}
}
}
-
同步方法
同步方法的實作和上面的同步代碼塊類似,也使用synchronized,但是它修飾的物件變成了方法,當修飾的是普通方法的時候,鎖物件是當前物件本身,即this;如果修飾的是靜態方法的時候,鎖物件則是這個類的位元組碼檔案物件,
下面只做修飾普通方法的舉例,修飾靜態方法的情景類似:
public static void main(String[] args) { Ticekt ticekt = new Ticekt(); new Thread(ticekt).start(); new Thread(ticekt).start(); new Thread(ticekt).start(); } static class Ticekt implements Runnable{ private int num = 10; @Override public void run() { while (true){ boolean flag = sale(); if (!flag) break; } } // 買票方法,同步執行, // 不是靜態方法,鎖物件為this,即呼叫的物件 // 是靜態方法,鎖物件為Ticke.class,位元組碼檔案物件 // 同一個類的物件會共用this這個鎖,可以會影響其他同步方法的執行 public synchronized boolean sale(){ if (num>0){ System.out.println(Thread.currentThread().getName()+":準備買出一張票"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":出票成功!"); num--; System.out.println(Thread.currentThread().getName()+":當前余票數:"+num); return true; } return false; } }-
使用顯示鎖Lock
同步代碼塊和同步方法實作都是基于隱式鎖,
使用實作介面Lock的類ReentrantLock的方式是基于顯示鎖,
顯示鎖比較適合自定義加鎖和解鎖,帶來個性化的同時也可能會帶來加鎖后忘解鎖的情況,在使用的程序中,需要充分考慮到加鎖后在哪個地方再解鎖,防止程式進入死鎖,無法釋放資源,下面是使用案例,也是基于買票程式的:
public static void main(String[] args) { Ticekt ticekt = new Ticekt(); new Thread(ticekt).start(); new Thread(ticekt).start(); new Thread(ticekt).start(); } static class Ticekt implements Runnable{ private int num = 10; // 用實作介面Lock的類ReentrantLock創建物件 private Lock l= new ReentrantLock(); @Override public void run() { while (true){ l.lock(); // 上鎖資源訪問 if (num>0){ System.out.println(Thread.currentThread().getName()+":準備買出一張票"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":出票成功!"); num--; System.out.println(Thread.currentThread().getName()+":當前余票數:"+num); l.unlock(); // 操作成功,解鎖 }else{ l.unlock(); // 沒有買票,break前需要釋放鎖,防止后面的執行緒被鎖住不能釋放 break; } } } }
-
5.總結
以上的介紹,總結了Java中使用多執行緒的基本內容,包括執行緒的創建的方式,執行緒常用的方法,實作執行緒同步的方法,在我們實際開發中,情景往往都是復雜的,可能會有大量的請求會同時發出,這個時候就需要合理的處理號這些請求,利用多執行緒的思想去解決這些問題,認識并使用多執行緒后,能夠使自己開發的程式更加符合實際開發的需求,提高程式的執行效率,Java中的多執行緒的知識還很多,比如執行緒池,上面的介紹只是基礎入門,想要深入學習的話,可以繼續查找資料,或者找些專案實踐,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/282258.html
標籤:其他
上一篇:十分鐘教你搭建個人博客
下一篇:爬蟲學習日記2021-5-1
