Java基礎——線程及并發機制

前言


? ? ? ?在Java中,線程是一個很關鍵的名詞,也是很高頻使用的一種資源。那么它的概念是什么呢,是如何定義的,用法又有哪些呢?為何說Android里只有一個主線程呢,什么是工作線程呢。線程又存在并發,并發機制的原理是什么。這些內容有些了解,有些又不是很清楚,所以有必要通過一篇文章的梳理,弄清其中的來龍去脈,為了之后的開發過程中提供更好的支持。


目錄

一、線程定義

二、Java線程生命周期

三、線程用法

四、Android中的線程

五、工作線程

六、使用AsyncTask

七、什么是并發

八、并發機制原理

九、并發具體怎么用


一、線程定義


? ? ? ?說到線程,就離不開談到進程了,比如在Android中,一個應用程序基本有一個進程,但是一個進程可以有多個線程組成。在應用程序中,線程和進程是兩個基本執行單元,都是可以處理比較復雜的操作,比如網絡請求、I/O讀寫等等,在Java中我們大部分操作的是線程(Thread),當然進程也是很重要的。

? ? ? ?進程通常有獨立執行環境,有完整的可設置為私有基本運行資源,比如,每個進程會有自己的內存空間。而線程呢,去官網的查了下,原話如下:

Threads are sometimes called "lightweight processes". Both processes and threads provide an execution environment, 
but creating a new thread requires fewer resources than creating a new process.
? ? ? ?意思就是:線程相比進程所創建的資源要少很多,都是在執行環境下的執行單元。同時,每個線程有個優先級,高的優先級比低的優先級優先執行。線程是操作系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。


二、Java線程生命周期


  1. 新建狀態(New):當線程對象創建后,即進入了新建狀態。僅僅由java虛擬機分配內存,并初始化。如:Thread t = new MyThread();
  2. 就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處于就緒狀態的線程,java虛擬機創建方法調用棧和程序計數器,只是說明此線程已經做好了準備,隨時等待CPU調度執行,此線程并 沒有執行。
  3. 運行狀態(Running):當CPU開始調度處于就緒狀態的線程時,執行run()方法,此時線程才得以真正執行,即進入到運行狀態。注:緒狀態是進入到運行狀態的唯一入口,也就是說,線程要想進入運行狀態執行,首先必須處于就緒狀態中;
  4. 阻塞狀態(Blocked):處于運行狀態中的線程由于某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種:等待阻塞 – 運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態,JVM會把該線程放入等待池中;同步阻塞 – 線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),它會進入同步阻塞狀態;其他阻塞 – 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
  5. 死亡狀態(Dead):線程run()方法執行完了或者因異常退出了run()方法,該線程結束生命周期。?當主線程結束時,其他線程不受任何影響。


三、線程用法


那該如何創建線程呢,有兩種方式。

  • 使用Runnable
  • 繼承Thread類,定義子類

使用Runnable:

?? ? ??Runnable接口有個run方法,我們可以定義一個類實現Runnable接口,Thread類有個構造函數,參數是Runnable,我們定義好的類可以當參數傳遞進去。

public class HelloRunnable implements Runnable {public void run() {System.out.println("Hello from a thread!");}public static void main(String args[]) {(new Thread(new HelloRunnable())).start();}}

繼承Thread類:

?? ? ??Thread類它自身就包含了Runnable接口,我們可以定義一個子類來繼承Thread類,進而在Run方法中執行相關代碼。

public class HelloThread extends Thread {public void run() {System.out.println("Hello from a thread!");}public static void main(String args[]) {(new HelloThread()).start();}}
? ? ? ?從兩個使用方式上看,定義好 Thread 后,都需要執行 start() 方法,線程才算開始執行。

四、Android中的線程


? ? ? ?當某個應用組件啟動且該應用沒有運行其他任何組件時,Android 系統會使用單個執行線程為應用啟動新的 Linux 進程。默認情況下,同一應用的所有組件在相同的進程和線程(稱為“主”線程)中運行。

? ? ? ?應用啟動時,系統會為應用創建一個名為“主線程”的執行線程。 此線程非常重要,因為它負責將事件分派給相應的用戶界面小工具,其中包括繪圖事件。 此外,它也是應用與 Android UI 工具包組件(來自?android.widget?和?android.view?軟件包的組件)進行交互的線程。因此,主線程有時也稱為 UI 線程。

? ? ? ?系統絕對不會為每個組件實例創建單獨的線程。運行于同一進程的所有組件均在 UI 線程中實例化,并且對每個組件的系統調用均由該線程進行分派。因此,響應系統回調的方法,例如,報告用戶操作的?onKeyDown()?或生命周期回調方法)始終在進程的 UI 線程中運行。例如,當用戶觸摸屏幕上的按鈕時,應用的 UI 線程會將觸摸事件分派給小工具,而小工具反過來又設置其按下狀態,并將無效請求發布到事件隊列中。UI 線程從隊列中取消該請求并通知小工具應該重繪自身。

? ? ? ?在應用執行繁重的任務以響應用戶交互時,除非正確實施應用,否則這種單線程模式可能會導致性能低下。 特別地,如果 UI 線程需要處理所有任務,則執行耗時很長的操作(例如,網絡訪問或數據庫查詢)將會阻塞整個 UI。一旦線程被阻塞,將無法分派任何事件,包括繪圖事件。從用戶的角度來看,應用顯示為掛起。 更糟糕的是,如果 UI 線程被阻塞超過幾秒鐘時間(目前大約是 5 秒鐘),用戶就會看到一個讓人厭煩的“應用無響應”(ANR) 對話框。

此外,Android UI 工具包并非線程安全工具包。因此,您不得通過工作線程操縱 UI,而只能通過 UI 線程操縱用戶界面。因此,Android 的單線程模式必須遵守兩條規則:

  1. 不要阻塞 UI 線程
  2. 不要在 UI 線程之外訪問 Android UI 工具包

? ? ? ?那為何Andorid是主線程模式呢,就不能多線程嗎?在Java中默認情況下一個進程只有一個線程,這個線程就是主線。主線程主要處理界面交互相關的邏輯,因為用戶隨時會和界面發生交互,因此主線程在任何時候都必須有比較高的響應速度,否則就會產生一種界面卡頓的感覺。同樣Android也是沿用了Java的線程模型,Android是基于事件驅動機制運行,如果沒有一個主線程進行調度分配,那么線程間的事件傳遞就會顯得雜亂無章,使用起來也冗余,還有線程的安全性因素也是一個值得考慮的一個點。


五、工作線程


? ? ? ?既然了解主線程模式,除了UI線程,其他都是叫工作線程。根據單線程模式,要保證應用 UI 的響應能力,關鍵是不能阻塞 UI 線程。如果執行的操作不能很快完成,則應確保它們在單獨的線程(“后臺”或“工作”線程)中運行。例如以下代碼表示一個點擊監聽從單獨的線程下載圖像并將其顯示在ImageView中:

public void onClick(View v) {new Thread(new Runnable() {public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png");mImageView.setImageBitmap(b);} }).start();
} 

? ? ? ?咋看起來貌似沒什么問題,它創建了一個線程來處理網絡操作, 但是呢,它卻是在UI線程中執行,但是,它違反了單線程模式的第二條規則:不要在 UI 線程之外訪問 Android UI 工具包。

? ? ? ?那么你會問個問題了,為什么子線程中不能更新UI。因為UI訪問是沒有加鎖的,在多個線程中訪問UI是不安全的,如果有多個子線程都去更新UI,會導致界面不斷改變而混亂不堪。所以最好的解決辦法就是只有一個線程有更新UI的權限。

當然,Android 提供了幾種途徑來從其他線程訪問 UI 線程。以下列出了幾種有用的方法:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)

例如,您可以通過使用?View.post(Runnable)?方法修復上述代碼:

public void onClick(View v) {new Thread(new Runnable() {public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");mImageView.post(new Runnable() {public void run() { mImageView.setImageBitmap(bitmap);} }); } }).start();
} 

? ? ? ?現在,上述實現屬于線程安全型:在單獨的線程中完成網絡操作,而在 UI 線程中操縱?ImageView

? ? ? ?但是,隨著操作日趨復雜,這類代碼也會變得復雜且難以維護。 要通過工作線程處理更復雜的交互,可以考慮在工作線程中使用?Handler?處理來自 UI 線程的消息。當然,最好的解決方案或許是擴展?AsyncTask?類,此類簡化了與 UI 進行交互所需執行的工作線程任務。


六、使用AsyncTask


? ? ? ?AsyncTask?允許對用戶界面執行異步操作。它會先阻塞工作線程中的操作,然后在 UI 線程中發布結果,而無需你親自處理線程和/或處理程序。

? ? ? ?要使用它,必須創建?AsyncTask?子類并實現?doInBackground()?回調方法,該方法將在后臺線程池中運行。要更新 UI,必須實現?onPostExecute()?以傳遞doInBackground()?返回的結果并在 UI 線程中運行,這樣,即可安全更新 UI。稍后,您可以通過從 UI 線程調用?execute()?來運行任務。

例如,可以通過以下方式使用?AsyncTask?來實現上述示例:

public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png"); 
} private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {/** The system calls this to perform work in a worker thread and * delivers it the parameters given to AsyncTask.execute() */ protected Bitmap doInBackground(String... urls) {return loadImageFromNetwork(urls[0]);} /** The system calls this to perform work in the UI thread and delivers * the result from doInBackground() */ protected void onPostExecute(Bitmap result) {mImageView.setImageBitmap(result);} 
} 

? ? ? ?現在 UI 是安全的,代碼也得到簡化,因為任務分解成了兩部分:一部分應在工作線程內完成,另一部分應在 UI 線程內完成。

下面簡要概述了 AsyncTask 的工作方法,但要全面了解如何使用此類,您應閱讀?AsyncTask?參考文檔:

  • 可以使用泛型指定參數類型、進度值和任務最終值
  • 方法?doInBackground()?會在工作線程上自動執行
  • onPreExecute()onPostExecute()?和?onProgressUpdate()?均在 UI 線程中調用
  • doInBackground()?返回的值將發送到?onPostExecute()
  • 您可以隨時在?doInBackground()?中調用publishProgress(),以在 UI 線程中執行?onProgressUpdate()
  • 您可以隨時取消任何線程中的任務


七、什么是并發


說到并發,首先需要區別并發和并行這兩個名詞的區別。

并發性和并行性
并發是指在同一時間點只能有一條指令執行,但多個進程指令被快速輪換執行,使得在宏觀上具有多個進程同時執行的效果。
并行指在同一時間點,有多條指令在多個處理器上同時執行。

那么我們為什么需要并發呢?通常是為了提高程序的運行速度或者改善程序的設計。


八、并發機制原理


? ? ? ?Java對并發編程提供了語言級別的支持。Java通過線程來實現并發編程。一個線程通常完成某個特定的任務,一個進程可以擁有多個線程,當這些線程一起執行的時候,就實現了并發。與操作系統中的進程相似,每個線程看起來好像擁有自己的CPU,但是其底層是通過切分CPU時間來實現的。與進程不同的是,線程并不是相互獨立的,它們通常要相互合作來完成一些任務。


九、并發具體怎么用


休眠

? ? ? ?我們可以讓一個線程暫時休息一會兒。Thread類有一個sleep靜態方法,你可以將一個long類型的數據當做參數傳進去,單位是毫秒,表示線程將會休眠的時間。


讓步

? ? ? ?Thread類還有一個名為yield()的靜態方法。這個方法的作用是為了建議當前正在運行的線程做個讓步,讓出CPU時間給別的線程來運行。程序中可能會有一個線程在某個時刻已經完成了一大部分的任務,并且這個時候讓別的線程來運行比較合理。這樣的情況下,就可以調用yield()方法進行讓步。不過,調用這個方法并不能保證一定會起作用,畢竟它只是建議性的。所以,不應該用這個方法來控制程序的執行流程。


串入(join)

? ? ? ?當一個線程t1在另一個線程t2上調用t1.join()方法的時候,線程t2將等待線程t1運行結束之后再開始運行。正如下面這個例子:

public class ThreadTest {public static void main(String[] args) {SimpleThread simpleThread = new SimpleThread();Thread t = new Thread(simpleThread);t.start();}
}
public class SimpleThread implements Runnable{@Overridepublic void run() {Thread tempThread = new Thread() {@Overridepublic void run() {for(int i = 10; i < 15 ;i++) {System.out.println(i);}}};tempThread.start();try {tempThread.join();        //tempThread串入} catch (InterruptedException e) {e.printStackTrace();}for(int i = 0; i < 5; i++) {System.out.println(i);}}
}
輸出結果:

10
11
12
13
14
0
1
2
3
4


優先級

? ? ? ?我們可以給一個線程設定一個優先級。線程調度器在做調度工作的時候,優先級越高的線程越可能得到先運行的機會。Thread類的setPriority方法和getPriority方法分別用來設置線程的優先級和獲取線程的優先級。由于線程調度器根據優先級的大小來調度線程的效果在各種不同的JVM上差別很大,所以在絕大多數情況下,我們不應該依靠設定優先級來完成我們的工作,保持默認的優先級是一條很好的建議。



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446974.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446974.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446974.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

密碼機 密鑰管理項目安裝配置 從零開始

安裝gcc 更新sudo apt-get update下載gcc sudo apt-get install gcc參考鏈接 不推薦 安裝g 下載g sudo apt-get install g 安裝make sudo apt -get install make參考鏈接 安裝cmake 下載地址參考鏈接 安裝ssh sudo apt-get install ssh 安裝git和配置 sudo apt-get inst…

Androud 如何有效減少重復代碼

前言 重復的代碼一直都是可維護性的大敵&#xff0c;重構的重要任務之一也就是要去除掉重復的代碼&#xff0c;有效的減少重復代碼&#xff0c;可以大大提高軟件的擴展性。 在Android開發中&#xff0c;很容易產生重復的代碼。因為Android是組件&#xff0c;模板式開發&#xf…

解決在sample文件夾里面寫代碼,在測試的時候因為virtual原因,make編譯報錯

代碼的結構 錯誤顯示 解決辦法 添加一句話&#xff0c;具體的cpp依據情況而定set_source_files_properties(${PROJECT_SOURCE_DIR}/src/sample_storage_test.cpp COMPILE_FLAGS "-Wno-unused-parameter")

Android SharedPreferences總結及優化

一、SharedPreferences簡介 Android 中的 SharedPreferences&#xff08;后續簡稱SP&#xff09;是輕量級的數據存儲方式&#xff0c;能夠保存簡單的數據類型&#xff0c;比如 String、int、boolean 值等。應用場合主要是數據比較少的配置信息。其內部是以 XML 結構保存在 /dat…

Java基礎——深入理解ReentrantLock

一、簡介在Java中通常實現鎖有兩種方式&#xff0c;一種是synchronized關鍵字&#xff0c;另一種是Lock。二者其實并沒有什么必然聯系&#xff0c;但是各有各的特點&#xff0c;在使用中可以進行取舍的使用。二、ReentrantLock與synchronized的比較相同點&#xff1a; &#xf…

使用開源的openssl的md5頭文件,實現對于文件的md5代碼

需要安裝openssl的庫 sudo apt-get install opensslsudo apt-get install libssl-dev參考鏈接 代碼 #include "openssl/md5.h" #include <iostream> #include <fstream> #include <iomanip>//#define MAX_DATA_BUFF 1024; //#define MD5_LENGTH…

Android 多進程開發

前言正常情況下&#xff0c;一個apk啟動后只會運行在一個進程中&#xff0c;其進程名為AndroidManifest.xml文件中指定的應用包名&#xff0c;所有的基本組件都會在這個進程中運行。但是如果需要將某些組件&#xff08;如Service、Activity等&#xff09;運行在單獨的進程中&am…

clion中鏈接openssl庫

錯誤顯示 前提條件 apt-get install opensslapt-get install openssl-dev 解決辦法 在CMakeLists.txt文件中加入如下命令link_libraries(crypto) 參考鏈接 無法將openssl庫鏈接到CLion C 程序c - 無法將openssl庫鏈接到CLion C程序

Java中String、StringBuffer、StringBuilder三者的區別

一、簡介String、StringBuffer、StringBuilder三個類之間的區別主要是在兩個方面&#xff1a;運行速度和線程安全。二、區別1、運行速度&#xff0c;或者說是執行速度在這方面運行速度快慢為&#xff1a;StringBuilder > StringBuffer > String StringString為字符串常量…

Ubuntu環境下,使用clion編譯器,使用開源opensll的對稱AES算法對于文件進行加密,C++代碼

前提準備條件 需要安裝openssl需要安裝openssl-dev需要配置CMakeLists.txt文件集體內容可以參考我提供的相關參考鏈接 AES_file.h #include <openssl/aes.h> #include <iostream> #include <fstream> #include <cstring>#define RELEASE_ARRAY(P) if…

Java提高篇 —— Java關鍵字之static的四種用法

一、前言 在java的關鍵字中&#xff0c;static和final是兩個我們必須掌握的關鍵字。不同于其他關鍵字&#xff0c;他們都有多種用法&#xff0c;而且在一定環境下使用&#xff0c;可以提高程序的運行性能&#xff0c;優化程序的結構。下面我們先來了解一下static關鍵字及其用法…

C++ 使用move來刪除用戶指定的文件

代碼 #include <iostream>bool remove_file(std::string path){if (remove(path.c_str())0){std::cout << "success!" << std::endl;}else{std::cout << "False!" << std::endl;} } int main() {std::string path "/…

Java提高篇 —— Java關鍵字之final的幾種用法

一、前言 在java的關鍵字中&#xff0c;static和final是兩個我們必須掌握的關鍵字。不同于其他關鍵字&#xff0c;他們都有多種用法&#xff0c;而且在一定環境下使用&#xff0c;可以提高程序的運行性能&#xff0c;優化程序的結構。下面我們來了解一下final關鍵字及其用法。 …

使用C++的方式實現AES算法

aes_file.h #include <iostream> #include <fstream> #include <bitset> #include <string> using namespace std; typedef bitset<8> byte; typedef bitset<32> word;const int Nr 10; // AES-128需要 10 輪加密 const int Nk 4; /…

Java提高篇 —— Java三大特性之封裝

一、封裝 封裝從字面上來理解就是包裝的意思&#xff0c;專業點就是信息隱藏&#xff0c;是指利用抽象數據類型將數據和基于數據的操作封裝在一起&#xff0c;使其構成一個不可分割的獨立實體&#xff0c;數據被保護在抽象數據類型的內部&#xff0c;盡可能地隱藏內部的細節&am…

sqlite3的backup和restore函數的使用

參考代碼 第一段這個親測可以使用 #include <sqlite3.h> #include <iostream> /* ** Perform an online backup of database pDb to the database file named ** by zFilename. This function copies 5 database pages from pDb to ** zFilename, then unlocks pD…

Java提高篇 —— Java三大特性之繼承

一、前言 在《Think in java》中有這樣一句話&#xff1a;復用代碼是Java眾多引人注目的功能之一。但要想成為極具革命性的語言&#xff0c;僅僅能夠復制代碼并對加以改變是不夠的&#xff0c;它還必須能夠做更多的事情。在這句話中最引人注目的是“復用代碼”,盡可能的復用代碼…

Java提高篇 —— Java三大特性之多態

一、前言 面向對象編程有三大特性&#xff1a;封裝、繼承、多態。 封裝&#xff1a;隱藏了類的內部實現機制&#xff0c;可以在不影響使用的情況下改變類的內部結構&#xff0c;同時也保護了數據。對外界而已它的內部細節是隱藏的&#xff0c;暴露給外界的只是它的訪問方法。 繼…

光盤刻錄制作Ubuntu等操作系統的啟動盤

前提條件 軟媒刻錄 空白光盤&#xff08;至少4.7G&#xff09;電腦&#xff08;最好使用外置的光驅&#xff09;系統鏡像&#xff08;ISO格式&#xff09; 具體操作 打開軟媒魔方選擇光盤刻錄按照標紅的進行選擇選擇鏡像->選擇或者拖拽都可以選擇刻錄機->如果使用外部刻…

Java提高篇 —— 抽象類與接口

一、前言 接口和內部類為我們提供了一種將接口與實現分離的更加結構化的方法。 抽象類與接口是java語言中對抽象概念進行定義的兩種機制&#xff0c;正是由于他們的存在才賦予java強大的面向對象的能力。他們兩者之間對抽象概念的支持有很大的相似&#xff0c;甚至可以互換&…