本文目錄一覽:
Java開發中線程的安全問題以及產生的原因?
Java如何保證原子性常用的保證Java操作原子性的工具是鎖和同步方法(或者同步代碼塊)。使用鎖,可以保證同一時間只有一個線程能拿到鎖,也就保證了同一時間只有一個線程能執行申請鎖和釋放鎖之間的代碼。
與鎖類似的是同步方法或者同步代碼塊。使用非靜態同步方法時,鎖住的是當前實例;使用靜態同步方法時,鎖住的是該類的Class對象;使用靜態代碼塊時,鎖住的是synchronized關鍵字後面括號內的對象。
在Java 中多線程的實現方法有哪些,如何使用
Java多線程的創建及啟動
Java中線程的創建常見有如三種基本形式
1.繼承Thread類,重寫該類的run()方法。
複製代碼
1 class MyThread extends Thread {
2
3 private int i = 0;
4
5 @Override
6 public void run() {
7 for (i = 0; i 100; i++) {
8 System.out.println(Thread.currentThread().getName() + ” ” + i);
9 }
10 }
11 }
複製代碼
複製代碼
1 public class ThreadTest {
2
3 public static void main(String[] args) {
4 for (int i = 0; i 100; i++) {
5 System.out.println(Thread.currentThread().getName() + ” ” + i);
6 if (i == 30) {
7 Thread myThread1 = new MyThread(); // 創建一個新的線程 myThread1 此線程進入新建狀態
8 Thread myThread2 = new MyThread(); // 創建一個新的線程 myThread2 此線程進入新建狀態
9 myThread1.start(); // 調用start()方法使得線程進入就緒狀態
10 myThread2.start(); // 調用start()方法使得線程進入就緒狀態
11 }
12 }
13 }
14 }
複製代碼
如上所示,繼承Thread類,通過重寫run()方法定義了一個新的線程類MyThread,其中run()方法的方法體代表了線程需要完成的任務,稱之為線程執行體。當創建此線程類對象時一個新的線程得以創建,並進入到線程新建狀態。通過調用線程對象引用的start()方法,使得該線程進入到就緒狀態,此時此線程並不一定會馬上得以執行,這取決於CPU調度時機。
2.實現Runnable接口,並重寫該接口的run()方法,該run()方法同樣是線程執行體,創建Runnable實現類的實例,並以此實例作為Thread類的target來創建Thread對象,該Thread對象才是真正的線程對象。
複製代碼
1 class MyRunnable implements Runnable {
2 private int i = 0;
3
4 @Override
5 public void run() {
6 for (i = 0; i 100; i++) {
7 System.out.println(Thread.currentThread().getName() + ” ” + i);
8 }
9 }
10 }
複製代碼
複製代碼
1 public class ThreadTest {
2
3 public static void main(String[] args) {
4 for (int i = 0; i 100; i++) {
5 System.out.println(Thread.currentThread().getName() + ” ” + i);
6 if (i == 30) {
7 Runnable myRunnable = new MyRunnable(); // 創建一個Runnable實現類的對象
8 Thread thread1 = new Thread(myRunnable); // 將myRunnable作為Thread target創建新的線程
9 Thread thread2 = new Thread(myRunnable);
10 thread1.start(); // 調用start()方法使得線程進入就緒狀態
11 thread2.start();
12 }
13 }
14 }
15 }
複製代碼
相信以上兩種創建新線程的方式大家都很熟悉了,那麼Thread和Runnable之間到底是什麼關係呢?我們首先來看一下下面這個例子。
複製代碼
1 public class ThreadTest {
2
3 public static void main(String[] args) {
4 for (int i = 0; i 100; i++) {
5 System.out.println(Thread.currentThread().getName() + ” ” + i);
6 if (i == 30) {
7 Runnable myRunnable = new MyRunnable();
8 Thread thread = new MyThread(myRunnable);
9 thread.start();
10 }
11 }
12 }
13 }
14
15 class MyRunnable implements Runnable {
16 private int i = 0;
17
18 @Override
19 public void run() {
20 System.out.println(“in MyRunnable run”);
21 for (i = 0; i 100; i++) {
22 System.out.println(Thread.currentThread().getName() + ” ” + i);
23 }
24 }
25 }
26
27 class MyThread extends Thread {
28
29 private int i = 0;
30
31 public MyThread(Runnable runnable){
32 super(runnable);
33 }
34
35 @Override
36 public void run() {
37 System.out.println(“in MyThread run”);
38 for (i = 0; i 100; i++) {
39 System.out.println(Thread.currentThread().getName() + ” ” + i);
40 }
41 }
42 }
複製代碼
同樣的,與實現Runnable接口創建線程方式相似,不同的地方在於
1 Thread thread = new MyThread(myRunnable);
那麼這種方式可以順利創建出一個新的線程么?答案是肯定的。至於此時的線程執行體到底是MyRunnable接口中的run()方法還是MyThread類中的run()方法呢?通過輸出我們知道線程執行體是MyThread類中的run()方法。其實原因很簡單,因為Thread類本身也是實現了Runnable接口,而run()方法最先是在Runnable接口中定義的方法。
1 public interface Runnable {
2
3 public abstract void run();
4
5 }
我們看一下Thread類中對Runnable接口中run()方法的實現:
複製代碼
@Override
public void run() {
if (target != null) {
target.run();
}
}
複製代碼
也就是說,當執行到Thread類中的run()方法時,會首先判斷target是否存在,存在則執行target中的run()方法,也就是實現了Runnable接口並重寫了run()方法的類中的run()方法。但是上述給到的列子中,由於多態的存在,根本就沒有執行到Thread類中的run()方法,而是直接先執行了運行時類型即MyThread類中的run()方法。
3.使用Callable和Future接口創建線程。具體是創建Callable接口的實現類,並實現clall()方法。並使用FutureTask類來包裝Callable實現類的對象,且以此FutureTask對象作為Thread對象的target來創建線程。
看着好像有點複雜,直接來看一個例子就清晰了。
複製代碼
1 public class ThreadTest {
2
3 public static void main(String[] args) {
4
5 CallableInteger myCallable = new MyCallable(); // 創建MyCallable對象
6 FutureTaskInteger ft = new FutureTaskInteger(myCallable); //使用FutureTask來包裝MyCallable對象
7
8 for (int i = 0; i 100; i++) {
9 System.out.println(Thread.currentThread().getName() + ” ” + i);
10 if (i == 30) {
11 Thread thread = new Thread(ft); //FutureTask對象作為Thread對象的target創建新的線程
12 thread.start(); //線程進入到就緒狀態
13 }
14 }
15
16 System.out.println(“主線程for循環執行完畢..”);
17
18 try {
19 int sum = ft.get(); //取得新創建的新線程中的call()方法返回的結果
20 System.out.println(“sum = ” + sum);
21 } catch (InterruptedException e) {
22 e.printStackTrace();
23 } catch (ExecutionException e) {
24 e.printStackTrace();
25 }
26
27 }
28 }
29
30
31 class MyCallable implements CallableInteger {
32 private int i = 0;
33
34 // 與run()方法不同的是,call()方法具有返回值
35 @Override
36 public Integer call() {
37 int sum = 0;
38 for (; i 100; i++) {
39 System.out.println(Thread.currentThread().getName() + ” ” + i);
40 sum += i;
41 }
42 return sum;
43 }
44
45 }
複製代碼
首先,我們發現,在實現Callable接口中,此時不再是run()方法了,而是call()方法,此call()方法作為線程執行體,同時還具有返回值!在創建新的線程時,是通過FutureTask來包裝MyCallable對象,同時作為了Thread對象的target。那麼看下FutureTask類的定義:
1 public class FutureTaskV implements RunnableFutureV {
2
3 //….
4
5 }
1 public interface RunnableFutureV extends Runnable, FutureV {
2
3 void run();
4
5 }
於是,我們發現FutureTask類實際上是同時實現了Runnable和Future接口,由此才使得其具有Future和Runnable雙重特性。通過Runnable特性,可以作為Thread對象的target,而Future特性,使得其可以取得新創建線程中的call()方法的返回值。
執行下此程序,我們發現sum = 4950永遠都是最後輸出的。而「主線程for循環執行完畢..」則很可能是在子線程循環中間輸出。由CPU的線程調度機制,我們知道,「主線程for循環執行完畢..」的輸出時機是沒有任何問題的,那麼為什麼sum =4950會永遠最後輸出呢?
原因在於通過ft.get()方法獲取子線程call()方法的返回值時,當子線程此方法還未執行完畢,ft.get()方法會一直阻塞,直到call()方法執行完畢才能取到返回值。
上述主要講解了三種常見的線程創建方式,對於線程的啟動而言,都是調用線程對象的start()方法,需要特別注意的是:不能對同一線程對象兩次調用start()方法。
你好,本題已解答,如果滿意
請點右下角「採納答案」。
如何避免Java線程死鎖
Java線程死鎖需要如何解決,這個問題一直在我們不斷的使用中需要只有不斷的關鍵。不幸的是,使用上鎖會帶來其他問題。讓我們來看一些常見問題以及相應的解決方法: Java線程死鎖 Java線程死鎖是一個經典的多線程問題,因為不同的線程都在等待那些根本不可能被釋放的鎖,從而導致所有的工作都無法完成。假設有兩個線程,分別代表兩個飢餓的人,他們必須共享刀叉並輪流吃飯。他們都需要獲得兩個鎖:共享刀和共享叉的鎖。 假如線程 「A」獲得了刀,而線程「B」獲得了叉。線程「A」就會進入阻塞狀態來等待獲得叉,而線程「B」則阻塞來等待「A」所擁有的刀。這只是人為設計的例子,但儘管在運行時很難探測到,這類情況卻時常發生。雖然要探測或推敲各種情況是非常困難的,但只要按照下面幾條規則去設計系統,就能夠避免Java線程死鎖問題: 讓所有的線程按照同樣的順序獲得一組鎖。這種方法消除了 X 和 Y 的擁有者分別等待對方的資源的問題。 將多個鎖組成一組並放到同一個鎖下。前面Java線程死鎖的例子中,可以創建一個銀器對象的鎖。於是在獲得刀或叉之前都必須獲得這個銀器的鎖。 將那些不會阻塞的可獲得資源用變量標誌出來。當某個線程獲得銀器對象的鎖時,就可以通過檢查變量來判斷是否整個銀器集合中的對象鎖都可獲得。如果是,它就可以獲得相關的鎖,否則,就要釋放掉銀器這個鎖並稍後再嘗試。 最重要的是,在編寫代碼前認真仔細地設計整個系統。多線程是困難的,在開始編程之前詳細設計系統能夠幫助你避免難以發現Java線程死鎖的問題。 Volatile 變量,volatile 關鍵字是 Java 語言為優化編譯器設計的。以下面的代碼為例: 1.class VolatileTest {
2.public void foo() {
3.boolean flag = false;
4.if(flag) {
5.//this could happen
6.}
7.}
8.} 一個優化的編譯器可能會判斷出if部分的語句永遠不會被執行,就根本不會編譯這部分的代碼。如果這個類被多線程訪問, flag被前面某個線程設置之後,在它被if語句測試之前,可以被其他線程重新設置。用volatile關鍵字來聲明變量,就可以告訴編譯器在編譯的時候,不需要通過預測變量值來優化這部分的代碼。 無法訪問的Java線程死鎖有時候雖然獲取對象鎖沒有問題,線程依然有可能進入阻塞狀態。在 Java 編程中IO就是這類問題最好的例子。當線程因為對象內的IO調用而阻塞時,此對象應當仍能被其他線程訪問。該對象通常有責任取消這個阻塞的IO操作。造成阻塞調用的線程常常會令同步任務失敗。如果該對象的其他方法也是同步的,當線程被阻塞時,此對象也就相當於被冷凍住了。 其他的線程由於不能獲得對象的Java線程死鎖,就不能給此對象發消息(例如,取消 IO 操作)。必須確保不在同步代碼中包含那些阻塞調用,或確認在一個用同步阻塞代碼的對象中存在非同步方法。儘管這種方法需要花費一些注意力來保證結果代碼安全運行,但它允許在擁有對象的線程發生阻塞後,該對象仍能夠響應其他線程。 編輯推薦: 1. Java多線程優化之偏向鎖原理分析 2. Java多線程實現異步調用的方法 3. 使用Java多線程機制實現下載的方法介紹
JAVA中,線程死鎖是什麼意思
一. 什麼是線程
在談到線程死鎖的時候,我們首先必須了解什麼是Java線程。一個程序的進程會包含多個線程,一個線程就是運行在一個進程中的一個邏輯流。多線程允許在程序中並發執行多個指令流,每個指令流都稱為一個線程,彼此間互相獨立。
線程又稱為輕量級進程,它和進程一樣擁有獨立的執行控制,由操作系統負責調度,區別在於線程沒有獨立的存儲空間,而是和所屬進程中的其它線程共享一個存儲空間,這使得線程間的通信較進程簡單。筆者的經驗是編寫多線程序,必須注意每個線程是否干擾了其他線程的工作。每個進程開始生命周期時都是單一線程,稱為「主線程」,在某一時刻主線程會創建一個對等線程。如果主線程停滯則系統就會切換到其對等線程。和一個進程相關的線程此時會組成一個對等線程池,一個線程可以殺死其任意對等線程。
因為每個線程都能讀寫相同的共享數據。這樣就帶來了新的麻煩:由於數據共享會帶來同步問題,進而會導致死鎖的產生。
二. 死鎖的機制
由多線程帶來的性能改善是以可靠性為代價的,主要是因為有可能產生線程死鎖。死鎖是這樣一種情形:多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由於線程被無限期地阻塞,因此程序不能正常運行。簡單的說就是:線程死鎖時,第一個線程等待第二個線程釋放資源,而同時第二個線程又在等待第一個線程釋放資源。這裡舉一個通俗的例子:如在人行道上兩個人迎面相遇,為了給對方讓道,兩人同時向一側邁出一步,雙方無法通過,又同時向另一側邁出一步,這樣還是無法通過。假設這種情況一直持續下去,這樣就會發生死鎖現象。
導致死鎖的根源在於不適當地運用「synchronized」關鍵詞來管理線程對特定對象的訪問。「synchronized」關鍵詞的作用是,確保在某個時刻只有一個線程被允許執行特定的代碼塊,因此,被允許執行的線程首先必須擁有對變量或對象的排他性訪問權。當線程訪問對象時,線程會給對象加鎖,而這個鎖導致其它也想訪問同一對象的線程被阻塞,直至第一個線程釋放它加在對象上的鎖。
Java中每個對象都有一把鎖與之對應。但Java不提供單獨的lock和unlock操作。下面筆者分析死鎖的兩個過程「上鎖」和「鎖死」 。
(1) 上鎖
許多線程在執行中必須考慮與其他線程之間共享數據或協調執行狀態,就需要同步機制。因此大多數應用程序要求線程互相通信來同步它們的動作,在 Java 程序中最簡單實現同步的方法就是上鎖。在 Java 編程中,所有的對象都有鎖。線程可以使用 synchronized 關鍵字來獲得鎖。在任一時刻對於給定的類的實例,方法或同步的代碼塊只能被一個線程執行。這是因為代碼在執行之前要求獲得對象的鎖。
為了防止同時訪問共享資源,線程在使用資源的前後可以給該資源上鎖和開鎖。給共享變量上鎖就使得 Java 線程能夠快速方便地通信和同步。某個線程若給一個對象上了鎖,就可以知道沒有其他線程能夠訪問該對象。即使在搶佔式模型中,其他線程也不能夠訪問此對象,直到上鎖的線程被喚醒、完成工作並開鎖。那些試圖訪問一個上鎖對象的線程通常會進入睡眠狀態,直到上鎖的線程開鎖。一旦鎖被打開,這些睡眠進程就會被喚醒並移到準備就緒隊列中。
(2)鎖死
如果程序中有幾個競爭資源的並發線程,那麼保證均衡是很重要的。系統均衡是指每個線程在執行過程中都能充分訪問有限的資源,系統中沒有餓死和死鎖的線程。當多個並發的線程分別試圖同時佔有兩個鎖時,會出現加鎖衝突的情形。如果一個線程佔有了另一個線程必需的鎖,互相等待時被阻塞就有可能出現死鎖。
在編寫多線程代碼時,筆者認為死鎖是最難處理的問題之一。因為死鎖可能在最意想不到的地方發生,所以查找和修正它既費時又費力。例如,常見的例子如下面這段程序。
public int sumArrays(int[] a1, int[] a2) …{ int value = 0; int size = a1.length; if (size == a2.length) …{ synchronized(a1) …{ //1 synchronized(a2) …{ //2 for (int i=0; isize; i++) value += a1[i] + a2[i]; } } } return value; }
這段代碼在求和操作中訪問兩個數組對象之前鎖定了這兩個數組對象。它形式簡短,編寫也適合所要執行的任務;但不幸的是,它有一個潛在的問題。這個問題就是它埋下了死鎖的種子。
沒有完結,請樓主看下面的網址。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/243917.html