深入講解java線程與synchronized關(guān)鍵字
我們將會從以下的幾點理解java線程的一些概念:
- 線程的基本概念和優(yōu)劣之處
- 創(chuàng)建一個線程的兩種方式
- 線程的屬性
- 線程的狀態(tài)
- synchronized可修飾的方法
- synchronized的重要特性
一、線程的基本概念
在計算機中有進(jìn)程和線程這么兩個概念,進(jìn)程中可以有多個線程,它們是從屬關(guān)系,進(jìn)程往往更像是資源的占有者,線程才是程序的執(zhí)行者,多個線程之間共享著進(jìn)程中的資源。一個cpu同時只能運行一個線程,每個線程都有一個時間片,時間片用完了就會被阻塞并讓出CPU的控制權(quán),交給下一個線程使用。這樣在計算機中就可以實現(xiàn)多任務(wù)的假象,其實CPU在不斷的切換線程,好像多個任務(wù)在同時運行。
使用線程的優(yōu)勢毋庸置疑,可以增加CPU的執(zhí)行效率,一旦某個線程需要等待某種資源(例如:等待打印機),就可以將它阻塞釋放CPU讓CPU執(zhí)行別的線程,而不需要讓CPU和此線程一起等待某種資源從而提高系統(tǒng)效率,另外一點就是可以用這種假象增加用戶體驗度。但是,CPU在切換不同線程之間所要花費的代價也是不可忽視的,在較為復(fù)雜的程序中這種劣勢可能九流一毛,但是如果在簡單的程序中就會顯得尤為突出。
二、創(chuàng)建一個線程
接下來我們看看如何在java中創(chuàng)建一個線程來實現(xiàn)多個線程同時運行。第一種方式,java 中有一個類Thread,我們只要繼承這個類并重寫他的run方法,調(diào)用start方法就可以啟動一個新的線程了。(沒見過的同學(xué)可能不能理解以下代碼,下面我會解釋)
/*聲明自己的一個線程類*/ public class Test_thread extends Thread { //重寫Thread類中的run方法 public void run(){ System.out.println("i am the thread"); } } public class Test_Class { public static void main(String[] args){ Test_thread thread = new Test_thread(); thread.start(); } }
輸出結(jié)果:i am the thread
首先我們先了解一下,一個線程被創(chuàng)建之后,怎么才能啟動運行,我們調(diào)用thread.start();方法啟動一個線程,首先就會執(zhí)行我們重寫的run方法(如果沒有重寫就會調(diào)用Thread類的run方法,什么也不做,這也是我們重寫run方法的原因),也就是說run方法是一個線程的開始。有個疑問,為什么調(diào)用start方法,卻執(zhí)行了run方法了?其實調(diào)用start方法就是為線程的啟動做準(zhǔn)備操作,分配線程私有的堆棧資源,然后執(zhí)行run方法。
下面我們看創(chuàng)建一個線程的第二種方式,實現(xiàn)接口Runnable,并重寫其中的run方法。
public class Test_thread implements Runnable { public void run(){ System.out.println("i am the thread"); } } public class Test_Class { public static void main(String[] args){ Test_thread thread = new Test_thread(); thread.start();//編譯錯誤 } }
我們會發(fā)現(xiàn)雖然重寫了run方法,但是在調(diào)用start方法的時候卻編譯錯誤,我們進(jìn)入到Runnable接口的源代碼中可以看到,只有一個抽象方法run,所以沒有啟動線程的start方法,所以我們還是要借助Tread類。
public class Test_Class { public static void main(String[] args){ Thread thread = new Thread(new Test_thread()); thread.start(); } }
因為Thread類中有start方法,所以可以使用Thread的一個構(gòu)造函數(shù)傳入一個實現(xiàn)了Runnable接口的類型,構(gòu)建一個Thread類。
三、線程的屬性和狀態(tài)
在一個多線程的程序中我們使用線程的一些屬性來區(qū)別和辨認(rèn)它們:
private long tid; private volatile char name[]; public static native Thread currentThread() private boolean daemon = false; private volatile int threadStatus = 0; public final static int NORM_PRIORITY = 5;
每個線程會有一個tid指定此線程的在所有線程中的排序,這個值是遞增的,還有一個name指定該線程的名字,也可以使用setName設(shè)置一個優(yōu)雅的線程名字,Thread類還提供了一個方法返回當(dāng)前正在占用CPU的線程對象。每個線程在某個時刻都是有狀態(tài)的,屬性threadStatus記錄了當(dāng)前對象線程的狀態(tài)是什么,默認(rèn)情況下,所有的線程的優(yōu)先級都被置為5,如果有特殊需要可以修改線程的優(yōu)先級,使得某個線程可以優(yōu)先得到運行。下面介紹線程的幾種不同的狀態(tài)。
- New:線程剛剛被創(chuàng)建,還沒有被啟動。
- Runnable:線程處于可運行的狀態(tài),但是不一定處于正在運行的狀態(tài)(后面解釋區(qū)別)
- Blocked:線程處于被阻塞的狀態(tài),一般是請求某資源不成功于是讓出CPU阻塞自己(具體的區(qū)別后面說)
- Waiting:線程處于等待狀態(tài),一般是等待服務(wù)
- Terminated:線程終止,也就是線程被kill,釋放其占用的資源
下面具體的說說不同狀態(tài)下的線程的一些操作,首先看看new,線程從此被創(chuàng)建,只是離運行狀態(tài)還需要一些準(zhǔn)備。Runnable表示線程是可運行,至于是否已經(jīng)處于運行狀態(tài)還要看系統(tǒng)給的時間片是否用完,如果用完了就會將此線程放置在可運行線程隊列的尾部,等待下次分配時間片,如果時間片沒有用完,就是處于運行狀態(tài)的(這也是為什么叫做Runnable而不是Running的原因)。接下來的兩種狀態(tài)Blocked和waiting都表示線程阻塞,需要讓出CPU。只是導(dǎo)致它們處于這種狀態(tài)的原因不一樣,具體的在我們介紹完synchronized關(guān)鍵字之后就會不言而喻了。
四、關(guān)鍵字synchronized
先看一段代碼:
public class Test_thread extends Thread{ public static int count = 0; public void run(){ try { Thread.sleep((int) (Math.random() * 100)); }catch(InterruptedException e){ } count++; } } public class Test_Class { public static void main(String[] args){ Test_thread[] thread = new Test_thread[1000]; for(int a=0;a<1000;a++){ thread[a] = new Test_thread(); thread[a].start(); } for(int a=0;a<1000;a++){ try { thread[a].join(); }catch (InterruptedException e){ } } System.out.println(Test_thread.count); } }
按照直覺,創(chuàng)建1000個線程,每個線程隨機睡覺并將公共變量增一,main線程等待所有線程執(zhí)行結(jié)束之后,輸出公共變量的值。按照直覺答案應(yīng)該是1000,但是我們運行的結(jié)果每次都是不一樣的,接近1000但每次都不一樣。這是為什么呢?這其實就是簡單的模擬了高并發(fā),可能有幾個線程睡了相同的時間,同時醒來獲取的count值是相同的,這就導(dǎo)致這幾個線程對count的操作被覆蓋了。
public class Test_thread extends Thread{ public static int count = 0; public void run(){ try { Thread.sleep((int) (Math.random() * 100)); }catch(InterruptedException e){ } /*使用關(guān)鍵字*/ synchronized (Test_thread.class){ count++; } } }
一個簡單的關(guān)鍵字就可以輕松解決這樣的高并發(fā)的問題。至于為什么這么寫?在介紹完synchronized關(guān)鍵字之后,想必你就會知道了。
首先我們需要知道,每個對象都有一把內(nèi)部鎖。所以被synchronized關(guān)鍵字修飾的方法,其實是被加了內(nèi)部對象鎖。我們看代碼:
public class Counter{ private int count; /*為實例方法加此關(guān)鍵字*/ public synchronized int getCount(){ return count; } }
為實例方法添加關(guān)鍵字,實際上就是給當(dāng)前的對象加鎖;括號中就是要加鎖的對象。
public class Counter{ private int count; /*為實例方法加此關(guān)鍵字*/ synchronized(this){ return count; } }
對于靜態(tài)方法,實際上就是為這個類加上鎖;
public class Counter{ private static int count; public static synchronized int getCount(){ return count; } } /*實際上給這個類加上鎖*/ public class Counter{ private int count; synchronized(Counter.class){ return count; } }
五、深入理解synchronized的一些特性
第一個性質(zhì)是:可重入性。被synchronized修飾的方法中的所有操作都是原子操作,但是當(dāng)我們需要在其中訪問另外的一些需要鎖的代碼時候,可以直接獲取別的鎖。也就是當(dāng)前的對象是可以獲得多個鎖的。
第二個性質(zhì)是:內(nèi)存的可見性。在我們的計算機中,其實有很多的操作并不是很"干脆"的,比如你向數(shù)據(jù)庫中存數(shù)據(jù)時,其實很多時候一些待存入的數(shù)據(jù)積累在緩存中等到一定數(shù)據(jù)量的時候才會統(tǒng)一的存入數(shù)據(jù)庫,但是在我們看來這些數(shù)據(jù)其實應(yīng)該早就存入數(shù)據(jù)庫了。這就導(dǎo)致了一個內(nèi)存的不可見性問題。當(dāng)然我們也是可以使用關(guān)鍵字synchronized來保證每次取出的數(shù)據(jù)都是最新的。在釋放鎖的時候會立即將數(shù)據(jù)協(xié)會內(nèi)存,獲得鎖的時候會去讀取最新的內(nèi)容。
public class Counter{ private int count; public synchronized int getCount(){ return count; } } /*每次獲得該對象的鎖之后,去獲取最新的count數(shù)值*/
其實,如果僅僅是要保證內(nèi)存的不可見性,使用synchronized關(guān)鍵字可能代價有點高,在這種情況下,我們可以使用關(guān)鍵字volatile。
public class Counter{ /*使用關(guān)鍵字volatile*/ private volatile int count; public int getCount(){ return count; } } /*此關(guān)鍵字保證每次使用count的時候數(shù)據(jù)都是最新的*/
總結(jié)
以上就是這篇文章的全部內(nèi)容了,還是希望大家發(fā)現(xiàn)其中錯誤直接指出,方便我糾正錯誤,更新知識。java并發(fā)系列文章,希望大家多多關(guān)注。
- Java的Synchronized關(guān)鍵字學(xué)習(xí)指南(全面 & 詳細(xì))
- 深入分析JAVA Synchronized關(guān)鍵字
- Java synchronized關(guān)鍵字使用方式及特性解析
- Java synchronized關(guān)鍵字和Lock接口實現(xiàn)原理
- Java中synchronized關(guān)鍵字引出的多種鎖 問題
- Java多線程并發(fā)編程 Synchronized關(guān)鍵字
- 實例解析Java中的synchronized關(guān)鍵字與線程安全問題
- Java中synchronized關(guān)鍵字修飾方法同步的用法詳解
- Java多線程編程中synchronized關(guān)鍵字的基礎(chǔ)用法講解
- 詳解Java中synchronized關(guān)鍵字的死鎖和內(nèi)存占用問題
- 舉例講解Java中synchronized關(guān)鍵字的用法
- Java多線程之synchronized關(guān)鍵字的使用
相關(guān)文章
SpringCloud超詳細(xì)講解微服務(wù)網(wǎng)關(guān)Zuul
這篇文章主要介紹了SpringCloud Zuul微服務(wù)網(wǎng)關(guān),負(fù)載均衡,熔斷和限流,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07Mybatis-plus批量去重插入ON DUPLICATE key update使用方式
這篇文章主要介紹了Mybatis-plus批量去重插入ON DUPLICATE key update使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12Java?如何通過注解實現(xiàn)接口輸出時數(shù)據(jù)脫敏
這篇文章主要介紹了Java?如何通過注解實現(xiàn)接口輸出時數(shù)據(jù)脫敏,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12