多線程這塊一直是面試的重點,也是開發當中的重點,因而下決定把這一塊的內容吃透它。整個系列的基礎文章最少就在60篇以上,由於這塊的知識看一兩篇確實感受沒什麼做用,須要有一個體系的纔好。這也是第一篇文章。點到爲止。java
1、認識線程面試
一、概念安全
什麼是線程呢?服務器
線程是進程劃分紅的更小的運行單位。就比如電腦QQ是一個進程,裏面還有各類子模塊,好比QQ空間,個性皮膚等子功能。微信
這裏出現了另一個名詞進程。多線程
進程是系統運行程序的基本單位。就比如是一個個應用程序QQ、微信等等。併發
看概念確實是一臉懵逼,舉個例子就明白了。咱們打開電腦的任務管理器,會發現上面就有進程,點擊這個進程咱們就能看到一個個應用程序,這就是進程。異步
那什麼是線程呢?不知道咱們注意到了沒有,每個進程最左邊都有一個小箭頭>,咱們打開來看看:ide
這些小的模塊就比如是一個個線程。有了這個印象咱們從新來認識一下線程和進程就容易多了。函數
線程:
線程是一個比進程小的執行單位,也被稱爲輕量級進程。一個進程能夠產生多個線程。多個線程共享同一塊內存和系統資源,CPU在這多個線程間來回切換去執行。
進程:
進程是系統運行程序的基本單位,也就是一個程序。系統運行一個程序便是一個進程從建立,運行到消亡的過程。
有了對線程的基本認識接下來咱們就能夠去理解一下,這一系列文章經常使用到的一些概念了。
二、並行與併發
其實他們倆區分很容易區分。
併發是多個任務交替使用CPU,同一時刻仍是隻有一個任務在跑,並行是多個任務同時跑。舉個例子就明白了。
桌子上有三個饅頭,每一時刻,小明只能咬一個饅頭。
桌子上有三個饅頭,每一時刻,小明、小紅、小華三我的同時咬三個饅頭。
明白了吧。
三、同步和異步
這兩個名詞咱們在學習的時候常常會遇到,舉個例子去理解,
對於同步:咱們去餐廳吃飯,只有一個客戶的時候商家比較容易處理,可是當有兩我的三我的的時候,這時候就須要排隊了,也就是擁塞了,這就是同步。換個例子來講,就是咱們請求服務器的時候,必需要等到服務器的反饋咱們纔可以去作其餘的事。
對於異步:就比如微信聊天,咱們只管把信息發送給對方,無論對方有沒有回覆咱們,咱們均可以去作其餘的事,也就是說執行完函數以後,沒必要等到反饋就能夠去作其餘的事。
四、死鎖
和操做系統裏面的死鎖意思同樣,也就是說多我的同時競爭一個資源,
例子一:比如多個男孩追求同一個女孩,這時候女孩就不知道該嫁給誰了。
例子二:咱們去圖書館借書,發現這本書被借走了,咱們只能等到那我的把書還到圖書館才能夠看。
五、原子變量與原子操做
所謂原子操做,就是「不可中斷的一個或一系列操做」。就比如你高考考試的時候,就算天塌下來也要把卷子作完。再急的事也不能搶奪他的優先權。
原子變量實際上是一個抽象的意思,由於本質上並無嚴格意義上的原子變量,可是在這裏,咱們能夠這樣理解,原子變量提供原子操做,就比如變量a,多個線程對其操做改變時,每一次只能有一個線程拿到他的執行權進行操做。
在這裏咱們基本上列舉了一些基本的概念,但其實還有不少,咱們在遇到的時候再去分析和理解會比較容易。咱們說了這麼久的線程,下面咱們來認識一下java中的線程。
2、基礎案例
咱們以一個生活中的案例來解釋說明,好比咱們敲代碼的同時還想聽音樂。
java中建立一個線程有兩種方式,繼承Thread類和實現runnable接口。咱們兩種都實現一下。
一、繼承Thread類
在這裏咱們定義兩個線程
public class ThreadTest01 extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++)
System.out.println("聽音樂");
}
}
複製代碼
還有一個線程能夠敲代碼
public class ThreadTest02 extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++)
System.out.println("敲代碼");
}
}
複製代碼
最後咱們就能夠一邊敲代碼一邊聽音樂了
public class ThreadTest {
public static void main(String[] args) {
ThreadTest01 thread1=new ThreadTest01();
ThreadTest02 thread2=new ThreadTest02();
thread1.start();
thread2.start();
}
}
複製代碼
咱們運行一邊會發現,敲代碼和聽音樂是交叉輸出的。這就體現了多線程的含義,由於要是平時輸出,確定就是誰在前先輸出誰,好比說先輸出十個聽音樂,在輸出十個敲代碼。不會交叉輸出。
固然咱們也可使用一個線程類去演示,在這裏,首先咱們建立了兩個類ThreadTest01和ThreadTest02,而且都繼承了Thread,而後再測試類中,咱們只須要調用相應的start方法便可。
使用一個線程類和使用多個線程類的區別你能夠這樣理解,一個是多個不一樣的線程分別完成本身的任務,一個是多個相同的線程共同完成一個任務。
二、實現Runnable接口
咱們一樣拿上面的例子進行說明
public class MyRunnable01 implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++)
System.out.println("聽音樂");
}
}
複製代碼
而後還能夠敲代碼
public class MyRunnable02 implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++)
System.out.println("敲代碼");
}
}
複製代碼
最後咱們能夠測試一下了
public class ThreadTest {
public static void main(String[] args) {
MyRunnable01 runnable01=new MyRunnable01();
MyRunnable02 runnable02=new MyRunnable02();
Thread thread1=new Thread(runnable01);
Thread thread2=new Thread(runnable02);
thread1.start();
thread2.start();
}
}
複製代碼
每次運行的時候,在控制檯你都會看到不同的效果。不過多運行幾回依然可以發現交叉運行的效果。
其實呢還有一種方式也能夠實現線程,那就是實現Callable接口,不過不多用到,這種方式在之後的文章中再進行詳細的介紹。畢竟這是第一篇文章。只是認識瞭解一下線程。
經過Runnable接口的方式,咱們依然發現,建立了兩個MyRunnable,而後直接賦給Thread便可,調用的時候一樣是使用start方法來啓動。這就是簡單的使用一下線程。
不知道咱們注意到沒有,java其實爲咱們已經提供了Thread,咱們能夠直接進行實例化。有時候咱們會常用匿名內部類的方式來建立一個線程,好比說下面這種
new Thread("th1") {
@Override
public void run() {
System.out.println( "匿名內部類");
}
}.start();
複製代碼
注意:上面的兩種建立線程的方式中,明明都是重寫的run方法,爲何要去調用start啓動線程呢?並且這兩種方式有什麼區別呢?在這裏先留一個懸念,下一篇文章將會介紹道。
3、分析多線程
一、使用線程有什麼好處呢?
好處你已經可以看到了,就是咱們能夠同時作好幾件事,在玩遊戲的時候能夠聽歌,還能夠看電影等等,多方便。
二、使用線程有什麼壞處嘛?
壞處其實也不少,好比說對於單核 CPU,CPU 在一個時刻只能運行一個線程,當在運行一個線程的過程當中轉去運行另一個線程,這個叫作線程上下文切換,上下文切換是一個複雜的過程,好比要記錄程序計數器、CPU寄存器的狀態等信息,耗時又耗空間。
爲了解決這個問題,纔有瞭如今的多核CPU。咱們常常會聽到手機或者是電腦是八核的,就是減小上下文切換帶來的時間空間損耗,提升程序運行的效率。
三、多線程帶來一個問題
從上面的例子其實咱們發現只是各幹各的事,相互之間互不干擾。還有一種狀況是一個資源被多個線程所用到了,這就帶來了線程安全問題。咱們使用例子來演示一下這個問題,
public class ThreadTest {
private int value=0;
public int getValue() {
return value++;
}
public static void main(String[] args) throws InterruptedException {
final ThreadTest test = new ThreadTest();
//咱們的本意多是th1執行後value變爲1
new Thread("th1") {
@Override
public void run() {
System.out.println( test.getValue()+" "+super.getName());
}
}.start();
//而後th2執行後value變爲2
new Thread("th2") {
@Override
public void run() {
System.out.println(test.getValue()+" "+super.getName());
}
}.start();
}
}
複製代碼
在上面,咱們直接建立了兩個線程,在第一個線程執行完以後value,在第二個線程執行完以後value變爲2。可是結果與咱們想的每每不同,我測試了N屢次,結果老是0 th2和1 th1。或者是反過來,再或者是00、11等等。這就是線程不安全的例子。如何去確保線程安全呢?方式其實有不少種,咱們一點一點深刻以後再去解決。
下一篇文章,咱們將對線程的生命週期,經常使用API、以及源碼(構造函數等)進行一個講解,正式開始咱們的多線程之旅。感謝支持。