1. 程式人生 > >棟哥帶你學Java執行緒和鎖

棟哥帶你學Java執行緒和鎖

執行緒

這裡寫圖片描述

在說執行緒之前我們瞭解下什麼是程序.
程序:一個正在執行的程式(獨立執行).
程序和執行緒之間的關係:
一個程序可以只有一個執行緒(單執行緒程式),一個執行緒中也可以有多個執行緒(多執行緒程式).
執行緒:一個執行緒,相當於cpu的執行路徑,一個獨立的單元.
執行緒的作用:大大提升了處理效率.
我們看上面的圖,一個防毒軟體是一個程序,這個程序有三個執行緒.這三個執行緒分別是防毒
軟體的防毒,清理和優化功能,其實每個功能也就是一段程式碼.如果我們同時開啟了這三個
功能,這時候cpu就會給他們對應的每個執行緒一個執行路徑.這裡拿單核的cpu舉例,單核的
cpu一次只能執行一個執行緒,所以當執行第一個執行緒的時候其他兩個執行緒都在等待,但是這
裡的執行執行緒不是一次性把這個執行緒的程式碼全部執行完,而是在三個執行緒之間來回切換執行
.但是每個執行緒又可以設定優先順序,優先順序高的可以讓cpu多執行那個執行緒一會,這也叫搶奪
資源.
標準的單執行緒例子:
public
static void main(String[] args) { /* * 標準的單執行緒程式 * 絕對安全 程式由上至下依次執行 但是 效率不高 * */ // 新增 add(); // 刪除 remove(); System.out.println("程式執行完畢"); } public static void add() { for (int i = 0; i < 100; i++) { System.out.println(i); } } public static void
remove() { System.out.println("刪除"); } 解析:程式碼從頭執行到尾,依次執行,就是標準的單執行緒. public static void main(String[] args) { // Exception in thread "main" 異常出現在 執行緒main中 System.out.println(10 / 0); add(); System.out.println("程式完成"); } public static void add() { for (int i = 0; i < 100; i++) { System.out
.println(i); } } 之前我們說算數異常的時候,都是隻看右邊by zero,現在我們看左邊. Exception in thread "main" java.lang.ArithmeticException: / by zero thread就是執行緒,我們的main函式也是主執行緒.那麼執行緒是如何開啟的呢? JVM呼叫main方法 ---> 找作業系統(cpu) ---> 開啟一個執行路徑 開啟一個叫main執行路徑,main就是一個執行緒,又叫主執行緒. 知道了什麼是執行緒後,我們如何去建立一個執行緒呢? 第一種方法: 寫一個類去繼承Thread類 public static void main(String[] args) { // 建立一個執行緒 SubThread subThread = new SubThread("哈哈"); // 如果你直接呼叫run方法 相當於 就調了一個普通成員方法 // subThread.run(); // 開啟執行緒 // 注意:直接呼叫run方法不能開啟執行緒 需要呼叫start方法開啟執行緒 subThread.start(); for (int i = 0; i < 50; i++) { System.out.println("--main--" + i); } // 獲取當前執行的執行緒物件方法 靜態方法 Thread currentThread = Thread.currentThread(); // 獲取主執行緒的名字 String name = currentThread.getName(); System.out.println("****" + name); } class SubThread extends Thread { // 重寫run方法 @Override public void run() { // Exception in thread "Thread-0" } } 注意:繼承Thread類後一定要重寫run方法,因為例項化物件的時候,開啟執行緒系統就會調 用自動幫我們run方法,如果不重新,那就沒什麼意義.千萬不要使用物件取調run方法,因 為那樣呼叫run方法只是一個普通的方法,切記系統會幫我們呼叫.開啟執行緒用物件.start 第二種方法: 寫一個類去實現Runnable介面,然後例項化一個執行緒物件,把實現類物件傳入執行緒的構造 方法裡. // 實現Runnable介面建立執行緒物件 // 介面中run方法是個抽象方法 public class Demo { public static void main(String[] args) { // 建立執行緒 Thread thread = new Thread(new RunnableImpl(), "哈哈"); // 開啟執行緒 thread.start(); } } class RunnableImpl implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " RunnableImpl"); } } 方法介紹: 當前執行的執行緒物件方法 靜態方法 Thread currentThread = Thread.currentThread(); 獲取當前執行緒的名字 String name = currentThread.getName(); 給執行緒設定名字 第一種:構造物件的時候把名字傳入構造方法裡 第二種:呼叫執行緒物件.setName(name); 注意:子類裡面最好不要定義name為成員變數,因為父類裡面的執行緒名就是用的name,而且 父類的get/set方法用final修飾了,如果子類還是要用name為成員變數的話就必須把子 類裡自動生成的get/set的標準寫法,換成一個新的方法名,不然編譯就會報錯.還有為什 麼主執行緒迴圈列印1-10並且主執行緒開啟了一個子執行緒(run方法裡也是列印1-10),結果看 起來是亂的,因為執行緒的執行順序就是cpu支線到誰就呼叫該執行緒的方法,所以主執行緒和字 執行緒就是隨機被執行的. 知識點: 1.建立一個執行緒 相當於cpu開闢了一個獨立的執行路徑 2.每個執行路徑都是一個獨立的空間 3.建立一個執行緒 該執行緒就會擁有一個獨立棧空間
執行緒的六種狀態

這裡寫圖片描述

執行緒的六種狀態:
1.新建狀態(例項化執行緒物件的時候)
2.執行狀態(呼叫start方法)
3.受阻塞狀態(等待cpu的執行資源)
4.休眠狀態(呼叫了sleep(時間)方法)
5.等待狀態(呼叫了wait()方法)
6.死亡狀態(run()方法執行完畢)
狀態圖沒必要死記,只要記住六種狀態然後就可以互相推理出來他們之間的關係.
建立執行緒的第三種方法:
使用匿名內部類方法,它的作用就相當於建立一個該子類的物件.
公式:
new 父類名或者介面名(){
    重寫父類的方法
};
這裡例項化出來的物件是子類的物件.
Thread t1 = new Thread() {
    @Override
    public void run() {
        System.out.println("我是建立執行緒方式1");
    }
};
t1.start();
Thread t2 = new Thread(new Runnable() {

    @Override
    public void run() {
        System.out.println("我是建立執行緒方式2");
    }
});
t2.start();
解析:如果只是new出來的話是沒有物件名的,而且只能呼叫一次方法,再繼續呼叫其他的方
法就得重新new一個出來,但是在類裡面使用匿名內部類的方式也是可以建立物件名出來的.
執行緒休眠問題:
讓執行緒休眠的方法是sleep(這裡填時間) 單位毫秒,sleep是過載方法,不一樣的引數時
間單位也不一樣.
class SleepThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            // 休眠一秒
            // 如果子執行緒中 出現異常 只能try...catch處理
            /*
             * Thread類 是Runnable介面的 實現類
             * 重寫介面中的 run方法
             * 該方法 沒有丟擲異常
             * 所以 所以的Runnable介面實現類 (包括Thread類)
             * 都不能在run方法中 丟擲異常 只能處理
             */
            try {
                super.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
注意:使用sleep方法會有異常,但是我們不能丟擲異常,是因為父類的run方法壓根就沒拋
出異常,所以子類的方法就更沒辦法拋了,那就只有使用try..catch方法.為什麼會有異
常呢,因為前說了執行緒有六種狀態,萬一執行睡眠前面的程式碼都正常,剛好執行到睡眠那一
句前面執行緒突然中斷或者阻塞之類的,那就沒辦法執行到睡眠.
一說到鎖就會提到一個經典問題,那就是賣票.
每個執行緒就像一個賣票的app,可以是去火車站買,可以用12306,可以用飛豬...
我們現在建立三個執行緒,去賣50張票.
class ThreadSell extends Thread {
    private int number = 100;
    @Override
    public void run() {
        while (number > 0) {
            System.out.println(Thread.currentThread().getName() + "剩餘" + --number + "張");
        }
    }
}
public static void main(String[] args) {
        ThreadSP t1 = new ThreadSP();
        ThreadSP t2 = new ThreadSP();
        ThreadSP t3 = new ThreadSP();
        t1.start();
        t2.start();
        t3.start();
}
然後看執行結果就會發現,多賣了一點票.這是為什麼呢?
前面也說了,每個執行緒都有自己獨立的棧記憶體,所以三個執行緒之間互不影響.但是資料只有
一份,比如說現在開始賣第一張票,因為三個執行緒互不干擾,有可能三個同時訪問到資料,然
後cpu開啟了第一個執行緒的執行路徑,其他兩個執行緒則在等待,第一個執行緒執行完再自減,但
是這時候其他兩個執行緒的num還是50,所以這就是出現問題的根源.那麼我們怎麼解決呢.
這裡我們需要用到鎖,鎖的關鍵字是synchronized,這裡我們要怎麼寫呢?
程式碼如下:
/*
 * 同步鎖(也叫同步程式碼塊)
 * 鎖可以是任意物件 要保證 鎖的唯一 三個執行緒都使用的時同一把鎖
 * synchronized(物件鎖){
 *  
 * }
 */
class Tickets implements Runnable {
    // 宣告50張票 保證票是共享資料 只New一次該類物件
    private int tickets = 50;
    // 建立了物件鎖 保證唯一
    private Object obj = new Object();
    // 賣票方法
    @Override
    public void run() {
        while (true) {
            // 鎖只要保證是物件和唯一就可以 填this可以
            synchronized (this) {
                // 操作的共享資料
                if (tickets > 0) {
                    // 列印之前讓執行緒休眠一會
                    try { 
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    // 有票就賣
                    System.out.println(Thread.currentThread().getName() 
                            + "--" + tickets);
                    // 減少一張
                    tickets--;
                }else {
                    // 沒票 結束迴圈
                    break;
                }
            }
            // 讓執行緒讓出cpu的資源
            Thread.yield();
        }
    }
}
public static void main(String[] args) {
    Tickets t = new Tickets();
    Thread t1 = new Thread(t);
    Thread t2 = new Thread(t);
    Thread t3 = new Thread(t);
    t1.start();
    t2.start();
    t3.start();
}
同步鎖規則:
1.執行緒 遇到鎖 就進程式碼塊(並且攜帶鎖)
2.當執行緒 執行完程式碼塊中的程式碼 把鎖返還
3.執行緒沒有遇到鎖 會在同步程式碼塊外等著 遇到鎖才能進
在這裡,鎖的物件只能是唯一的,上面就例項化出了一個物件,所以符合它的規定.
synchronized (obj)和synchronized (this)
因為在例項化物件的時候,Tickets類裡就例項化了一個Object物件,這個物件不會改變.
為什麼寫this也行呢?因為this表示本類物件,我們本來就只例項化了一次,所以只有一個
本類物件.
模擬死鎖的過程:
/*
 * 模擬執行緒死鎖
 */
public class Demo {
    public static void main(String[] args) {
        DieLockRunnable dieLockRunnable = new DieLockRunnable();
        Thread t1 = new Thread(dieLockRunnable);
        Thread t2 = new Thread(dieLockRunnable);
        t1.start();
        t2.start();
    }
}
// A鎖
class LockA {
    // 定義一個常量  作為鎖物件 不能修改(也不能建立)
    public static final LockA LOCK_A = new LockA();
    // 私有構造方法
    private LockA() {}
}
// B鎖
class LockB {
    // 定義一個常量  作為鎖物件 不能修改(也不能建立)
    public static final LockB LOCK_B = new LockB();
    // 私有構造方法
    private LockB() {}
}
// 執行緒
class DieLockRunnable implements Runnable{
    private boolean isTrue = true;
    @Override
    public void run() {
        while (true) {
            if (isTrue) {
                // A鎖到B鎖
                synchronized (LockA.LOCK_A) {
                    System.out.println("第一次先A");
                    synchronized (LockB.LOCK_B) {
                        System.out.println("第一次後B");
                    }
                }
            } else {
                // B鎖到A鎖
                synchronized (LockB.LOCK_B) {
                    System.out.println("第二次先B");
                    synchronized (LockA.LOCK_A) {
                        System.out.println("第二次後A");
                    }
                }
            }
            // 修改標記
            isTrue = !isTrue;
        }
    }
}
Lock鎖
我們看API裡,Lock是一個介面,我們要使用它,要麼匿名內部類去實現,要是就實現介面.
我們去實現它的子介面:
class Tickets3 implements Runnable {
    private int tickets = 50;
    // 宣告鎖物件
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            // 加鎖
            lock.lock();
            try {
                // 鎖住操作共享資料的程式碼
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + 
                                "---" + "剩餘" + tickets + "張票");
                    tickets--;
                }else {
                    break;
                }
            } finally {
                // 解鎖
                lock.unlock();
            }
            Thread.yield();
        }
    }   
}
為什麼要使用try...finally,博主也不知道,人家API裡面就是這麼給的示範寫法,我們
就只能照著人家寫的規則來.使用Lock加鎖就物件.lock(),解鎖就lock.unlock();

相關推薦

Java執行

執行緒 在說執行緒之前我們瞭解下什麼是程序. 程序:一個正在執行的程式(獨立執行). 程序和執行緒之間的關係: 一個程序可以只有一個執行緒(單執行緒程式),一個執行緒中也可以有多個執行緒(多執行緒程式). 執行緒:一個執行緒,相當於cpu的執行路徑,一個獨

Java執行中斷

執行緒中斷 (一)stop方法 在早期,執行緒中又一個stop方法,可以直接終止執行緒.但是現在已經不用了,而且api上也已經在方法下標明已過時.這時因為stop方法一旦呼叫,不管執行緒是什麼狀態的都直接會被終 止,假如正在執行執行緒,run方法中的程式

Java泛型

在說泛型之前先建三個類. 第一個、Person類 /* * 姓名 和 年齡 * 構造 set/get toString * 建立一個學生類(構造方法) 繼承人類 */ public class Person { private Stri

Java跟蹤行號流、裝飾者模式以及列印流

跟蹤行號流 跟蹤行號字元輸入流:LineNumberReader 這個流有什麼特點呢? 沒什麼特別的特點,就是可以獲取行號. public void fun() throws FileNotFound

Java抽獎小遊戲

使用者資訊類: public class User { static String username; // 註冊時存入的使用者名稱 static String password; //

Java static的用法

一、封裝 在說static關鍵詞的用法之前我們先說說Java面向物件的三大特徵之一的封裝. 什麼是封裝? 講類中的屬性或方法對外界隱藏,然後開放公共的訪問方式. 那麼怎麼隱藏呢? 之前我們寫mai

Java集合與迭代器

Java中的集合 為什麼會有集合? 因為陣列有弊端,所有才有了集合. 陣列的特點是什麼? 首先,陣列中只能新增相同型別的元素(基本資料型別和引用資料型別都能儲存),而且陣列的長度一旦確定就不能該改變, 如果要新增超出陣列長度個數的元素,就只能再宣告一個新的

Java之JDBC

JDBC 什麼是JDBC? JDBC(Java Data Base Connectivity,java資料庫連線)是一種用於執行SQL語句 的JavaAPI,可以為多種關係資料庫提供統一訪問,它由一組用Java語言編寫的類和介面組成.JDBC提供了一種基準

Java 併發程式設計系列之瞭解多執行

早期的計算機不包含作業系統,它們從頭到尾執行一個程式,這個程式可以訪問計算機中的所有資源。在這種情況下,每次都只能執行一個程式,對於昂貴的計算機資源來說是一種嚴重的浪費。 作業系統出現後,計算機可以執行多個程式,不同的程式在單獨的程序中執行。作業系統負責為各個獨

3分鐘沉迷於執行運動

1、執行緒安全問題出現的原因 -----                         多個執行緒訪問統一資源,有可能出現執行緒安全問題     &n

一位10年Java程式設計師總結進階中的懂多執行jvm優化嗎?

感謝朋友們的認可和指正。本文是有感而發,因為看過了太多坑人的部落格和書籍,感慨自己走過的彎路,不希望其他初學者被網上互相抄襲的部落格和東拼西湊的書籍浪費時間,想以一個相對巨集觀的視野來描述一個概念,力求通俗易懂,所以沒有深入太多細節,簡化了很多模型,給部分朋友造成了疑惑,說聲抱歉。也沒有配圖,都是抽

【凱子Framework】Activity介面顯示全解析

前幾天凱子哥寫的Framework層的解析文章《Activity啟動過程全解析》,反響還不錯,這說明“寫讓大家都能看懂的Framework解析文章”的思想是基本正確的。 我個人覺得,深入分析的文章必不可少,但是對於更多的Android開發者——即

【凱子Framework】Activity啟動過程全解析

It’s right time to learn Android’s Framework ! 前言 一個App是怎麼啟動起來的? App的程式入口到底是哪裡? Launcher到底是什麼神奇的東西? 聽說還有個AMS的東西,它是做什麼的?

新霸進入java的世界

新霸哥從近期大家的留言中注意到了大家對基礎知識比較重視,很多的朋友希望多講一些入門的知識,為了滿足廣大開發愛好者的需求,新霸哥決定從最基礎的做起,一點一點的幫助大家一起走進雲端計算的世界。下面新霸哥首先帶領大家入門,今天入門的第一站就是進入java的世界。   喜歡程式設計

走進多執行的世界(多執行實現方式)

做效能測試的同學使用最多的就是LoadRunner和Jemter工具了吧,能夠使用洪荒之力模擬多使用者同時請求伺服器,來觀察伺服器端的負載情況並定位效能瓶頸,聽上去挺高大上的。無論任何效能工具,核心原理都離不開多執行緒。如何實現多執行緒?如何定位異常狀態的執行緒找到效能瓶頸

python-[第一章-初識Python]

license div 高級 今天 做什麽 挖掘 運維 自然語言 什麽 Python是一種解釋型、面向對象、動態數據類型的高級程序設計語言。 Python由Guido van Rossum於1989年底發明,第一個公開發行版發行於1991年。 像Perl語言一樣, Py

P7架構師深入瞭解執行的發展歷史

  專題簡介 作為一個合格的Java程式設計師,必須要對併發程式設計有一個深層次的瞭解,在很多網際網路企業都會

java執行記憶體 java 重排序 java happens-before java 記憶體語義

執行緒之間的通訊機制有兩種:           共享記憶體:在共享記憶體的併發模型裡,執行緒之間共享程式的公共狀態,通過寫-讀記憶體中的公共狀態進行隱式通訊。      

java 執行——偏向&輕量級&重量級

偏向鎖 輕量級鎖 重量級鎖 執行緒阻塞的代價 java的執行緒是對映到作業系統原生執行緒之上的,如果要阻塞或喚醒一個執行緒就需要作業系統介入,需要在戶態與核心態之間切換,這種切換會消耗大量的系統資源。 我們所熟知的Synchronized 在競爭鎖失

java 執行 Lock 使用Condition實現執行的等待(await)與通知(signal)

轉自:小禾點點 一、Condition 類   在前面我們學習與synchronized鎖配合的執行緒等待(Object.wait)與執行緒通知(Object.notify),那麼對於JDK1.5 的 java.util.concurrent.locks.Reentran