java-多線程與併發 以下內容為本人的學習筆記,如需要轉載,請聲明原文鏈接 https://www.cnblogs.com/lyh1024/p/16786357.html 多線程 1.進程與線程 1.1 什麼是進程 程式是指令和數據的有序集合,其本身沒有任何運行的含義,是一個靜態的概念。而進程是程 ...
java-多線程與併發
以下內容為本人的學習筆記,如需要轉載,請聲明原文鏈接 https://www.cnblogs.com/lyh1024/p/16786357.html
多線程
1.1 什麼是進程
程式是指令和數據的有序集合,其本身沒有任何運行的含義,是一個靜態的概念。而進程是程式在處理機上的一次執行過程,它是一個動態的概念。
進程是一個具有一定獨立功能的程式,一個實體,每一個進程都有它自己的地址空間。
1.2 進程的狀態
進程執行時的間斷性,決定了進程可能有多種狀態。事實上,運行中的進程具有以下三種基本狀態。
-
就緒狀態(Ready)
-
運行狀態(Running)
-
阻塞狀態(Blocked)
cpu是有轉速的,轉速越快,性能越高。但CPU運行a程式時,要轉到h程式,需要時間,這時候進程就會進入阻塞狀態。
1.3 線程
線程實際上是在進程基礎上調度進一步劃分,一個線程啟動後,裡面的若幹程式又可以劃分成若幹個線程。
線程:是進程中的一個執行路徑,共用一個記憶體空間,程式之間可以自由切換,併發執行,一個進程最少有一個線程(單線程程式)
一個程式可以同時執行多個任務,來提高效率
例如:①同時下載多部電影
②看電影的同時吃零食
並行:就是兩個任務同時運行(多個CPU)
併發:是指兩個任務同時請求運行,而處理器一次只能接受一個任務,就會把兩個任務安排輪流執行,由於CPU時間片運行時間較短,就會感覺是兩個任務在同時執行
-
面試題:進程和線程的區別?
2.線程的基本使用
線程實現的三種方式
在java中如果要想實現多線程的操作,有兩種實現方法:
1)繼承Thread類(只能繼承一個)
2)實現Runnable介面(建議使用,可以實現多個)
3)實現Callable介面
public class ThreadDeom1{
public static void main(String[] args){
MyThread mt = new Thread();
MyRunnable mr = new MyRunnable();
Thread t2 = new Thread(mr);//將實現Runnable的當成一個任務放進線程Thread里
mt.start();//啟動線程,(實際是,start表準備就緒,告訴虛擬機可以啟動線程)
t2.start();//可簡寫,合併成:new Thread(mr).start
}
}
/**
創建線程方式一:
1.繼承Thread類
2.重寫run()方法
3.創建線程對象,調用start()開啟線程
註意:線程開啟不一定立即執行,有CPU調度執行
*/
class MyThread extends Thread{
public void run(){
for(int i = 0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
try{
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
/**
創建線程方式二:
1.實現Runnable介面
2.重寫run()方法
3.創建Runnable介面的實現類對象
4.創建線程對象(代理),丟入實現類對象
5.調用start()開啟線程
註意:線程開啟不一定立即執行,有CPU調度執行
*/
class MyRunnable implement Runnable{
public void run(){
for(int i = 0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
try{
/**
線程的休眠
在當前線程的執行中,暫停指定的毫秒數,釋放CPU的時間片
釋放CPU的時間片意思:CPU給一個進程P ,3秒執行時間,線程A和線程B互搶這個3秒時間片,設A搶到了,執行了3秒,此時會中斷,然後CPU執行下一個進程,CPU一直轉,又轉到進程P,然後A和B又開始互搶這3秒時間片,A又搶到,繼上次中斷的位置開始執行,執行完後,就讓時間片給其他線程。
*/
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
/**
創建線程方式三:
1.實現Callable介面,需要返回值類型
2.重寫call方法,需要拋出異常
3.創建目標對象
4.創建執行服務:ExecutorService ser = Exectors.newFixedThreadPool(1);
5.提交執行:Future<Boolean> result1 = ser.submit(t1);
6.獲取結果:boolean r1 = result1.get();
7.關閉服務:ser.shutdownNow();
*/
案例:
3.線程的休眠
public static void sleep(long millis) throws InterruptedException
使當前正在執行的線程以指定的毫秒數暫停(暫時停止執行),釋放CPU的時間片,具體取決於系統定時器和調度程式的精度和準確性。 線程不會丟失任何顯示器的所有權。
-
參數
millis
- 以毫秒為單位的睡眠時間長度 -
異常
IllegalArgumentException
- 如果millis
值為負數InterruptedException
- 如果任何線程中斷當前線程。 當拋出此異常時,當前線程的中斷狀態將被清除。 由線程本身中斷不會報錯。
-
線程的休眠,目的是讓出CPU執行的時間片,讓其他工作的線程可以執行,但不會釋放對象鎖
-
sleep可以模擬網路延遲,倒計時等
-
sleep時間達到後線程進入就緒狀態
public static void sleep(long millis,int nanos) throws InterruptedException//毫秒,納秒
public static Thread currentThread()//返回對當前正在執行的線程對象的引用,即獲取當前線程
4.join與中斷線程
public final void join() throws InterruptedException
等待這個線程死亡。
調用此方法的行為方式與調用join(0) 完全相同
-
異常
InterruptedException
- 如果任何線程中斷當前線程。 當拋出此異常時,當前線程的中斷狀態將被清除。
public void interrupt() | 中斷這個線程。 除非當前線程中斷自身,這是始終允許的 |
public static boolean interrupted() | 測試當前線程是否中斷。 該方法可以清除線程的中斷狀態 。 換句話說,如果這個方法被連續調用兩次,那麼第二個調用將返回false(除非當前線程再次中斷,在第一個調用已經清除其中斷狀態之後,在第二個調用之前已經檢查過)。 忽略線程中斷,因為線程在中斷時不存在將被該方法返回false所反映。 |
join方法的主要作用就是同步,它可以使得線程之間的並行執行變為串列執行
或者說 join合併線程,待此線程執行完畢後,回去再執行其他線程,其他線程阻塞,可以想象成插隊
public class ThreadDeom2{
public static void main(String[] args){
MyRunnable mr2 = new MyRunnable2();
Thread t1 = new Thread(mr2);
MyRunnable mr3 = new MyRunnable3();
Thread t2 = new Thread(mr3);
t1.start();
t2.start();
for(int i = 0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
try{
Thread.sleep(300);
}catch (InterruptedException e){
e.printStackTrace();
}
if(i==20){
/** try{
t1.join();//讓t線程執行完畢
}catch (InterruptedException e){
e.printStackTrace();
}*/
// t1.interrupt();//中斷線程(不會真的中斷線程),只是作了一個中斷標記
mr3.flag =false;
}
}
}
}
class MyRunable2 implements Runnable{
public void run(){
for(int i = 0;i<50;i++){
/**
中斷線程方式一:
1.使用interrupt()來中斷線程,設置一個中斷狀態(標記)
2.run方法里測試中斷狀態Thread.interrupted()
3.有sleep等拋出InterruptedException異常的,要重新打上中斷標記
中斷線程方式二(更加推薦使用):
1.自定義標記的方式,設置一個布爾值為ture,作為while的條件,要中斷時就將該布爾值設為false
*/
if(Thread.interrupted()){//測試中斷狀態,此方法會把中斷狀態清除
break;//不會真的中斷,因為sleep會拋出InterruptedException異常並把中斷標記清除,所以要重新打上中斷標記
}
System.out.println(Thread.currentThread().getName()+"-"+i);
try{
Thread.sleep(300);
}catch (InterruptedException e){
e.printStackTrace();
Thread.currentThread().interrupt();//重新打上中斷標記
}
}
}
}
class MyRunable3 implements Runnable{
public boolean flag = true;
public MyRunable3(){
flag = true;
}
public void run(){
int i = 0;
while(flag){
System.out.println(Thread.currentThread().getName()+"---"+i);
try{
Thread.sleep(300);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
5.守護線程與yield
線程分為用戶線程和守護線程
虛擬機必須確保用戶線程執行完畢
虛擬機不用等待守護線程執行完畢(用戶線程執行完畢後,JVM虛擬機自動退出,不用等待守護線程執行完畢)
如,後臺記錄操作日誌,監控記憶體,垃圾回收等待....
method | 說明 |
---|---|
public final void setDaemon(boolean on) | 將此線程標記為daemon線程或用戶線程。 當運行的唯一線程都是守護進程線程時,Java虛擬機將退出。 |
public final boolean isDaemon() | 測試這個線程是否是守護線程。 |
public static void yield() | 暫停當前正在執行的線程對象,並執行其他線程(瞭解) |
yield作用是暫停當前正在執行的線程對象(放棄當前CPU資源),並執行其他線程。
yield是讓當前運行線程回到可運行狀態,以允許具有相同優先順序的其他線程獲得運行機會
補充:當源碼由native修飾,表示為本地方法,由c或c++實現
for(int i = 0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
try{
Thread.sleep(300);
}catch (InterruptedException e){
e.printStackTrace();
}
if(i==5){
Thread.yield();//讓出本次CPU執行時間片,就讓一次,下一次還搶CPU時間片
}
}
6.其他方法與優先順序
Method or Fieids | 說明 |
---|---|
long getId() |
返回此線程的標識符。 |
String getName() |
返回此線程的名稱。 |
int getPriority() |
返回此線程的優先順序。 |
boolean isAlive() |
測試這個線程是否處於活動狀態,start以後就是活動狀態。 |
void setName(String name) |
將此線程的名稱更改為等於參數 name 。 |
void``setPriority(int newPriority) |
更改此線程的優先順序。 |
static int MAX_PRIORITY |
線程可以擁有的最大優先順序。 |
static int ``MIN_PRIORITY |
線程可以擁有的最小優先順序。 |
static int NORM_PRIORITY |
分配給線程的預設優先順序。 |
7.線程同步
7.1 多線程共用數據
在多線程的操作中,多個線程有可能同時處理同一個資源,這就是多線程中的共用數據
7.2線程同步
解決數據共用問題,必須使用同步,所謂同步就是指多個線程在同一個時間段內只有一個線程執行指定代碼,其他線程要等待此線程完成之後才可以繼續執行。
在多線程編程時,一些敏感數據不允許被多個線程同時訪問,此時就使用同步訪問技術,保證數據在任何時刻,最多有一個線程訪問,以保證數據的完整性
線程同步是確保多線程共用數據的安全性,同時也會犧牲性能,同步過多還可能產生死鎖,因此務必按需求使用同步
線程進行同步,有以下兩種方法:
1)同步代碼塊
synchronized(要同步的對象){
要同步的操作;
}
2)同步方法
public synchronized void method(){
要同步的操作;
}
3)Lock(ReentrantLock)
/**
1.多線程共用數據時,會發生線程不安全的情況(多個線程操作同一個數據)
2.多線程共用數據必須使用同步
*/
public class ThreadDeom4{
public static void main(String[] args){
MyRunnable5 = mr5 = new MyRunnable();//一個任務
Thread t1 = new Thread(mr5);//線程1
Thread t2 = new Thread(mr5);//線程2
t1.start();
t2.start();
}
}
class MyRunnable5 implements Runnable{
private int ticket;//售票
Object obj = new Object;
public void run(){
for(int i = 0;i<300;i++){
if(ticket>0){
//同步方法一:
synchronized(obj){
ticket--;
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("您購買的票已剩餘"+ticket--+"張");
}
//method();
}
}
}
//同步方法二,同步的對象是當前對象(this)
private synchronized void method(){
if(ticket>0){
ticket--;
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("您購買的票已剩餘"+ticket--+"張");
}
}
//同步方法三,Lock鎖
ReentrantLock lock = new ReentrantLock():
private synchronized void method(){
lock.lock();//上鎖
try{
if(ticket>0){
ticket--;
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("您購買的票已剩餘"+ticket--+"張");
}
}finally{//加try,catch確保一定能釋放鎖,避免死鎖
lock.unlock();//釋放鎖
}
}
}
解釋sleep不會釋放對象鎖:
sleep在synchronized同步代碼塊里,CPU分配3秒,任務執行1秒,sleep休眠1秒,剩下一秒sleep會釋放CPU時間片給其他線程,但是不會釋放對象鎖,也就是synchronized鎖還在,儘管有釋放CPU時間片,但是其他線程進不去這個同步鎖裡面,所以剩下1秒其他線程只能幹等。
7.3同步準則
當編寫synchronized塊時,有幾個簡單的準則可以遵循,這些準則在避免死鎖和性能危險的風險方面大有幫助:
-
使代碼塊保持簡潔。把不隨線程變化的預處理和後處理移出synchronized塊
-
不要阻塞。如InputStream.read()。
-
在持有鎖的時候,不要對其他對象調用其同步方法
8.死鎖
過多的同步有可能出現死鎖,死鎖的操作一般是在程式運行的時候才有可能出現
多線程中要進行資源的共用,就需要同步,但同步過多,就可能造成死鎖,要避免出現死鎖
線程死鎖:在一個同步方法中調用了另一個對象的同步方法,可能產生死鎖。
9.生產者和消費者問題
面試題;sleep與wait的區別?
-
sleep:讓線程進入休眠狀態,讓出CPU的時間片,不釋放對象鎖
-
wait:讓線程進入等待狀態,讓出CPU的時間片,並釋放對象鎖,等待其他線程通過notify方法來喚醒(同步方法里使用)
10.線程生命周期圖:
11、線程池
線程池是預先創建線程的一種技術。線程池在還沒有任務到來之前,創建一定數量的線程,放入空閑隊列中,然後對這些資源進行賦予。減少頻繁的創建和銷毀對象。
JDK1.5版本以上提供了現成的線程池
java裡面線程池的頂級介面是Executor,是一個執行線程的工具
線程池介面是ExecutorService
使用線程池的好處:
線程可以重覆利用,減少創建和銷毀線程所帶來的的系統資源的開銷,提升性能(節省線程創建的時間開銷,使程式響應更快)
java.util.concurrent包:併發編程中很常用的使用工具類(concurrent併發(工具類))
Executor介面(譯:執行器) :
執行已提交的Runnable任務的對象
ExecutorService介面:
Executor提供了管理終止的方法,以及可為跟蹤一個或多個非同步任務執行狀況而生成Future的方法
Executors類:
此包中所定義的Executor、ExecutorService等的工廠和使用方法
在Executors類裡面提供了一些靜態工廠,生成一些常用的線程池
newSingleThreadExecutor:
創建一個單線程的線程池。這個線程只有一個線程在工作,也就是相當於單線程串列執行所有任務。如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
newFixedThreadPool: (fixed 譯:固定的)
創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。
線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程
newCachedThreadPool(少用到):
創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。
newScheduledThreadPool:
創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求
public class ThreadDeom5{
public static void main(String[] args){
//創建線程池(4種)
//1.創建一個單線程的線程池,每個線程輪流走完才輪到下一個走
// ExecutorService es = Executors.newSingleThreadExecutor();
//2.創建固定大小的線程池,多個線程同時執行
// ExecutorService es = Executors.newFixedThreadPool(2);
//3.創建一個可緩存的線程池
// ExecutorService es = Executors.newCacheThreadPool();
//4.創建一個大小無限的線程池
// ExecutorService es = Executors.newScaheduleThreadPool(3)//要給初始容量
/** es.execute(new MyRunnable6());//一個execute()就為一個線程,MyRunnable6是一個任務
es.execute(new MyRunnable6());*/
es.schedule(new MYRunnable6(),3000,TimeUnit.MiLLISECONDS);//可以調度:延遲3秒
es.shutdown();
}
}
class MyRunnable6 implements Runnable{
public void run(){
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
try{
Thread.sleep(300);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
有待補充......
參考資料:
JDK1.8幫助文檔
2022-10-12 22:22:12