1. 程式人生 > >Java -- 執行緒( Thread 類 和 Runnable介面 ) , 執行緒安全 .

Java -- 執行緒( Thread 類 和 Runnable介面 ) , 執行緒安全 .

一、執行緒

執行緒: 程序內部一個獨立執行單元(通向CPU的一條路徑.)

1. Thread類:

常用方法:
Thread.currentThread().getName(); 獲取當前執行緒的名稱.
Thread.sleep(long millis): 讓當前執行緒睡眠多少毫秒,之後繼續執行執行緒。

使用繼承類的方式建立執行緒:
a.建立一個Thread的子類 , 繼承Thread類.
b.重寫Thread類的run方法 , 設定執行緒要執行的任務.
c.建立Thread的子類物件.
d.呼叫Start方法 , - - - - > 開啟新執行緒 .

// 1. 建立一個子類 , 繼承Thread類 .
public class MyThread extends Thread{
    // 2.重寫run方法
    @Override
    public void run() {
        // 設定執行緒任務 -- 獲取當前正在執行的執行緒的名稱
        System.out.println(Thread.currentThread().getName());
    }
}

// 測試類
public class Demo01GetThreadName {
    public static void main(String[] args) {
        System.out.println("這裡是main執行緒."
); // 3. 建立子類物件 MyThread MyThread mt = new MyThread(); // 4. 呼叫start方法, 開啟執行緒 mt.start(); // 獲取主執行緒的名稱. String name = MyThread.currentThread().getName(); System.out.println(name); } }

2. Runnable介面(重點)

使用Runnable介面的好處:
a.避免了實現類單繼承的侷限性(還可以繼承其他類, 實現其他介面.)
b.降低了程式的耦合性,提高了程式的擴充套件性.

實現方式:
1.建立一個類實現Runnable介面.
2.重寫介面中的run方法 , 設定執行緒任務.
3.建立介面的實現類物件 .
4.建立Thread類物件 , 在構造方法中傳遞Runnable介面的實現類物件
5.呼叫Threa類中start開啟新執行緒.

// 1. 建立一個子類 , 實現 Runnable 介面 .
public class RunnableImpl implements Runnable {
    // 2. 重寫run方法 , 設定執行緒任務.
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}


// 測試類 
public class Demo01Runnable{
    public static void main(String[] args) {
        // 3.建立介面的實現類物件 .
        RunnableImpl r = new RunnableImpl();

        // 4.建立Thread類物件 . 介面的實現類物件作為引數
        Thread t = new Thread(r);

        // 5.開啟新執行緒
        t.start();

        // 3 , 4 , 5的簡寫步驟 .
        new Thread(new RunnableImpl2()).start();

        // 主執行緒開啟完新的執行緒,會繼續執行
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

3. 匿名內部類:

匿名內部類方式開啟多執行緒:
匿名內部類作用: 簡化程式碼
格式:
   new 父類/介面( ){
        重寫父類/介面中的方法
   };

// 匿名內部類的寫法:
public class Demo01Thread {
    public static void main(String[] args) {
        // Runnable介面的匿名內部類
        new Thread(new Runnable(){
            @Override
            public void run() { // 重寫run方法 , 設定執行緒任務.
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"----"+i);
                }
            }
        }).start(); // 開啟執行緒. 

        // 主執行緒 
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}

二、執行緒安全

出現執行緒安全的問題:
                      多執行緒訪問了同一個共享的資料。
使用執行緒安全:
                       a.增加了程式的安全性.
                       b.降低了效率.

1. 同步程式碼塊

synchronized(鎖物件){
              可能出現安全問題的程式碼
              (訪問了共享資料的程式碼)
}
注意:
1.鎖物件,可以是任意的物件
2.必須保證多個執行緒使用的是同一個鎖物件

/*
    賣票案例出現了執行緒安全問題:出現了重複的票和不存在的票
    解決執行緒安全問題的第一種方式:使用同步程式碼塊
 */

// 建立子類 , 實現Runnnabl介面
public class RunnableImpl implements Runnable {
    // 定義一個共享的票
    private int ticket = 100;

    // 重寫run方法 .
    @Override
    public void run() {
        // 使用同步程式碼塊 . 保證執行緒的安全 . 
        synchronized (this){
            while (true) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在賣第" + ticket + "張票!");
                    ticket--;
                }

            }
        }
    }
}

2.同步方法

同步鎖是誰?
同步方法中 : 同步鎖就是 t h i s(誰呼叫 , 誰就是this)
靜態同步方法中: 當前方法所在類的位元組碼物件(類名.class)。

// 1 . 建立一個類 , 實現runnable介面
public class RunnableImpl2 implements Runnable {
    // 建立一個共享的票
    private static int ticket = 100;

    // 重寫run方法 ,
    @Override
    public void run() {
        // 執行緒任務 - 賣票
        while (true){
            getTicket();
        }
    }


    /*
       定義一個賣票的方法
       1.定義一個方法,新增一個同步synchronized修飾符

       同步方法的鎖物件使用的是this
       哪個物件呼叫的方法,方法中的this就是哪個物件,本類的物件
       this就是Runnable的實現類物件RunnableImpl
    */
    public  synchronized void getTicket() {
        //2.把訪問了共享資料的程式碼,放到同步方法中
        // synchronized (this) {
            if (ticket > 0) {
                //為了提高安全問題出現的機率,讓程式睡眠10毫秒
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在賣第" + ticket + "張票!");
                ticket--;
            }
        //}
    }
}

3.Lock鎖

介面中的方法:
              void lock() 獲取鎖。
              void unlock() 釋放鎖。
同步鎖使用的弊端:
當執行緒任務中出現了多個同步(多個鎖)時,如果同步中嵌套了其他的同步。這時容易引發一種現象:程式出現無限等待,這種現象我們稱為死鎖。

public class RunnableImpl3 implements Runnable {
    private int ticket= 100;

    // 1. 在成員位置建立一個Lock介面的實現類物件ReentrantLock
    Lock r = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            r.lock();  // 2.在可能會出現執行緒安全問題的程式碼前呼叫lock方法獲取鎖物件 
            if (ticket > 0) {
                //為了提高安全問題出現的機率,讓程式睡眠10毫秒
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在賣第" + ticket + "張票!");
                ticket--;
            }
            r.unlock(); // 3.在可能會出現執行緒安全問題的程式碼後呼叫unlock方法釋放鎖物件
        }
    }
}

4.等待(wait)與喚醒(notify)

a. 必須使用鎖物件呼叫 .
b. wait和notify方法一般使用在同步中