在瞭解線程以前,首先明白什麼是進程。java
進程是指運行中的應用程序,每一個進程都會有本身獨立的地址空間(內存空間)好比:瀏覽器,編譯器,系統任務管理器等等。操做系統會給該進程分配獨立的地址空間,當用戶再次點擊瀏覽器時,就又啓動一個進程。用戶每啓動一個進程,操做系統就會給該進程分配一個獨立的內存空間。web
線程能夠理解成進程的寄生,一個進程能夠由多個線程。線程本身不擁有系統資源,只擁有一點在運行中必不可少的資源,但他可與同屬一個進程的其餘線程共享所擁有的所有資源,一個線程能夠建立和撤銷另外一個線程,線程能夠併發執行。瀏覽器
線程有三種基本狀態:就緒,阻塞,運行。緩存
一、線程是輕量級進程。安全
二、線程沒有獨立的地址空間服務器
三、線程由進程建立(寄生)網絡
四、一個進程能夠擁有多個線程------>多線程多線程
五、線程的生命週期:併發
新建狀態,就緒狀態,運行狀態,阻塞狀態,死亡狀態。框架
一、咱們熟悉的Tomcat服務器內部就是使用多線程:
上百個客戶訪問同一個web應用,Tomcat接入後都是把後續的處理扔給一個新的線程來處理,這個新的線程就是servlet程序,好比doGet和doPost
若是不採用多線程,上百個用戶同時訪問一個web應用的時候,只能使用串行處理,那樣就不實際。
二、異步處理:
異步處理也使用多線程,好比task a和task b要並行處理。可是CPU是多核的話,就可讓一個CPU執行有一個線程,若是隻有一個CPU的話,底層是按照分時複用的原則,各個線程按照時間獲取CPU資源。
三、javaweb應用中不多用到多線程,由於在開發過程當中,多線程被框架實現了,須要本身實現的不多。
四、用的比較多的時網絡應用程序。
當多個線程同時共享,同一個全局變量或靜態變量,作寫的操做時,可能會發生數據衝突問題,也就是線程安全問題。可是作讀操做是不會發生數據衝突問題。
經典案例就是搶火車票的案例啦:
class ThreadTrain1 implements Runnable { private int count = 100; private static Object oj = new Object();
@Override public void run() { while (count > 0) { try { Thread.sleep(50); } catch (Exception e) { // TODO: handle exception } sale(); } }
public void sale() { // 前提 多線程進行使用、多個線程只能拿到一把鎖。 // 保證只能讓一個線程 在執行 缺點效率下降 // synchronized (oj) { // if (count > 0) { System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票"); count--; // } // } } }
public class ThreadDemo { public static void main(String[] args) { ThreadTrain1 threadTrain1 = new ThreadTrain1(); Thread t1 = new Thread(threadTrain1, "①號窗口"); Thread t2 = new Thread(threadTrain1, "②號窗口"); t1.start(); t2.start(); } } |
運行結果:
能夠看到一號跟二號出現搶到同一張票,這顯然是不可用的
使用同步synchronized或者使用鎖(lock)
爲何使用多線程同步或者鎖就能解決線程安全問題?
答:由於多線程可能會發生數據衝突問題(線程不安全問題),只能讓當前一個線程執行,同步/鎖內的代碼執行完以後,才能讓其餘線程進來執行,一個一個執行,這樣就能夠解決線程不安全問題。多個線程共享一個資源,不會收到其餘線程的干擾。
多線程同步代碼塊案例:
private static Object oj = new Object(); public void sale() { // 前提 多線程進行使用、多個線程只能拿到一把鎖。 // 保證只能讓一個線程 在執行 缺點效率下降 synchronized (oj) { if (count > 0) { System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票"); count--; } } } |
也能夠這樣寫:
public synchronized void sale() { if (trainCount > 0) { try { Thread.sleep(40); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票."); trainCount--; } } |
注意:使用同步代碼塊的時候通常都是使用當前類。靜態方法時使用鎖時當前類的字節碼文件。
簡單理解:同步中嵌套同步,致使鎖沒法釋放。
class ThreadTrain6 implements Runnable { // 這是貨票總票數,多個線程會同時共享資源 private int trainCount = 100; public boolean flag = true; private Object mutex = new Object();
@Override public void run() { if (flag) { while (true) { synchronized (mutex) { // 鎖(同步代碼塊)在何時釋放? 代碼執行完, 自動釋放鎖. // 若是flag爲true 先拿到 obj鎖,在拿到this 鎖、 才能執行。 // 若是flag爲false先拿到this,在拿到obj鎖,才能執行。 // 死鎖解決辦法:不要在同步中嵌套同步。 sale(); } } } else { while (true) { sale(); } } } public synchronized void sale() { synchronized (mutex) { if (trainCount > 0) { try { Thread.sleep(40); } catch (Exception e) {
} System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票."); trainCount--; } } } }
public class DeadlockThread {
public static void main(String[] args) throws InterruptedException {
ThreadTrain6 threadTrain = new ThreadTrain6(); // 定義 一個實例 Thread thread1 = new Thread(threadTrain, "一號窗口"); Thread thread2 = new Thread(threadTrain, "二號窗口"); thread1.start(); Thread.sleep(40); threadTrain.flag = false; thread2.start(); }
} |
一、原子性
原子性是指不可分割,必須先後一致,好比去銀行存取錢,A帳戶往B帳戶裏轉1000塊錢,A帳戶裏減去1000,B帳戶增長1000,先後不能夠有分割。
二、可見性
多個線程訪問同一個變量時,一個線程修改了該變量的值,其餘線程可以當即看到修改的值
三、有序性
程序執行的順序按照代碼的前後順序執行。
共享內存模型指的就是Java內存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入時,能對另外一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(main memory)中,每一個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其餘的硬件和編譯器優化。