Java多線程3 線程的同步

/*
Java多線程: 線程的生命週期與線程安全

一,線程的分類
Java中的線程分爲兩類:一種是守護線程,一種是用戶線程。 它們在幾乎每個方面都是相同的,唯一的區別是判斷JVM何時離開。
守護線程是用來服務用戶線程的,通過在start()方法前調用thread.setDaemon(true)可以把一個用戶線程變成一個守護線程。
Java垃圾回收和異常處理就是典型的守護線程,main方法時用戶線程,若JVM中都是守護線程,當前JVM將退出。

二,線程的狀態
JDK中用Thread.State類(Thread中的內部枚舉類)定義了線程的幾種狀態
要想實現多線程,必須在主線程中創建新的線程對象。Java語言使用Thread類及其子類的對象來表示線程,
在它的一個完整的生命週期中通常要經歷如下的五種狀態:
1新建(NEW):當一個Thread類或其子類的對象被聲明並創建時,新生的線程對象處於新建狀態
2就緒(RUNNABLE):處於新建狀態的線程被start()後,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件,只是沒分配到CPU資源
3運行(RUNNABLE):當就緒的線程被調度並獲得CPU資源時,便進入運行狀態, run()方法定義了線程的操作和功能
4阻塞(BLOCKED WAITING TIMED_WAITING):在某種特殊情況下,被人爲掛起或執行輸入輸出操作時,讓出 CPU 並臨時中止自己的執行,進入阻塞狀態
5死亡(TERMINATED):線程完成了它的全部工作或線程被提前強制性地中止或出現異常導致結束

三,線程狀態轉換涉及到的方法
1,使用new 構造器()新建一個Thread類或者其子類的實例對象,
2,新建狀態到就緒狀態:start()方法。
3,就緒狀態到運行狀態:cpu分配資源。
4,運行狀態回到就緒狀態:失去cpu資源或主動使用yield()方法。
5,運行狀態到死亡狀態:1run()方法執行完畢,2程序出現error或未處理的Exception,3主動調用stop()方法。
6,運行狀態到阻塞狀態:1,其他線程調用join()參與此線程,2調用sleep()方法,3調用wait()方法,4等待同步鎖時,5調用suspend()方法
7,阻塞狀態到就緒狀態:2,調用join()方法的線程執行結束,2sleep()到時,3notify()或notifyAll(),4獲得同步鎖,5resume()方法

四,線程安全問題
出現原因:當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行。導致共享數據的錯誤。
解決辦法:對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行,即使當前線程出現阻塞,共享數據也不能被改變

Java對於多線程的安全問題提供瞭解決方式:同步機制,使用synchronized關鍵字聲明方法或代碼塊

  1. 同步代碼塊:
    synchronized (同步監視器){
    // 需要被同步的代碼;
    操作共享數據的代碼既是需要被同步的代碼,
    多個線程共同操作的數據就是共享數據。
    同步監視器就是鎖,任何類的對象都可以是鎖,但是要求多線程共用一把鎖。
    實現Runnable接口的類中可以使用this來當鎖,但繼承Thread類的子類中
    慎重使用this來當鎖,因爲鎖不唯一。
    }
  2. synchronized還可以放在方法聲明中,表示整個方法爲同步方法。
    如果操作共享數據的語句都在一個方法體中,可以選擇將此方法聲明爲synchronized。
    public synchronized void show (String name){
    // 需要被同步的代碼;
    }

五 同步鎖機制:
在《Thinking in Java》中說:對於併發工作,你需要某種方式來防止兩個任務訪問相同的資源(其實就是共享資源競爭)。
防止這種衝突的方法就是當資源被一個任務使用時,在其上加鎖。第一個訪問某項資源的任務必須鎖定這項資源,
使其他任務在其被解鎖之前,就無法訪問它了,而在其被解鎖之時,另一個任務就可以鎖定並使用它了。
5.1 synchronized的鎖是什麼?
任意對象都可以作爲同步鎖。所有對象都自動含有單一的鎖(監視器)。
同步方法的鎖:沒有顯示指定,默認靜態方法(類名.class)、非靜態方法(this),
同步代碼塊:自己指定,很多時候也是指定爲this或類名.class

5.2 注意:
必須確保使用同一個資源的多個線程共用一把鎖,這個非常重要,否則就無法保證共享資源的安全
一個線程類中的所有靜態方法共用同一把鎖(類名.class),所有非靜態方法共用同一把鎖(this),同步代碼塊(指定需謹慎)

5.3 釋放鎖的操作
 當前線程的同步方法、同步代碼塊執行結束。
 當前線程在同步代碼塊、同步方法中遇到break、return終止了該代碼塊、該方法的繼續執行。
 當前線程在同步代碼塊、同步方法中出現了未處理的Error或Exception,導致異常結束。
 當前線程在同步代碼塊、同步方法中執行了線程對象的wait()方法,當前線程暫停,並釋放鎖。

5.4 不會釋放鎖的操作
線程執行同步代碼塊或同步方法時,程序調用Thread.sleep()、Thread.yield()方法暫停當前線程的執行
線程執行同步代碼塊時,其他線程調用了該線程的suspend()方法將該線程掛起,該線程不會釋放鎖(同步監視器)。
注意:應儘量避免使用suspend()和resume()來控制線程

5.5同步的範圍
1、如何找問題,即代碼是否存在線程安全
(1)明確哪些代碼是多線程運行的代碼
(2)明確多個線程是否有共享數據
(3)明確多線程運行代碼中是否有多條語句操作共享數據
2、如何解決
對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。即所有操作共享數據的這些語句都要放在同步範圍中
3、如果範圍太小:沒鎖住所有有安全問題的代碼,範圍太大:沒發揮多線程的功能。

5.6 同步機制的優缺點:
1,優點:解決了線程安全問題
2,缺點:操作同步代碼時,只能有一個線程參與,其他線程阻塞,相當於單線程,運行時間變長,效率降低

*/

package leanthread;

import com.sun.org.apache.xpath.internal.objects.XNumber;

/**
 * @Description:
 * @Version:1.0.0.1
 * @Date:2020--03--06--21:57
 * @Author:wisdomcodeinside
 * @E-mail:[email protected]
 */
public class ThreadTest3 {
    public static void main(String[] args) {
        //測試Runnable實現類中使用同步代碼塊解決同步問題
        SynchronizedWindow s = new SynchronizedWindow();
        Thread Window1 = new Thread(s);
        Thread Window2 = new Thread(s);
        Thread Window3 = new Thread(s);
        Window1.setName("賣票窗口1");
        Window1.start();
        Window2.setName("賣票窗口2");
        Window2.start();
        Window3.setName("賣票窗口3");
        Window3.start();
        //測試Thread繼承子類中使用同步代碼塊解決同步問題

        for (int i = 0; i < 5; i++) {
            new People(i + "號線程").start();
        }
        //測試Runnable實現類中使用同步方法解決同步問題
        TenMultiple t = new TenMultiple();
        Thread find1 = new Thread(t, "查詢線程1");
        Thread find2 = new Thread(t, "查詢線程2");
        Thread find3 = new Thread(t, "查詢線程3");
        find1.start();
        find2.start();
        find3.start();
        //測試Thread繼承子類中使用同步方法解決同步問題
        for (int i = 0; i < 5; i++) {
            new EatFood(i + "號喫貨線程").start();
        }

    }
}

//Runnable實現類中使用同步代碼塊
class SynchronizedWindow implements Runnable {
    private int ticket = 20;//線程共用一個對象,不用加上static
    Object obj = new Object();

    @Override
    public void run() {
//      Object obj = new Object();//不能在此處造鎖,會破壞唯一性
        while (true) {
            synchronized (obj) {//或者填入this,使用當前類的對象作爲鎖,因爲使用的時候只創建一個對象,可以保證鎖的唯一性
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);//增加阻塞狀態,提高非同步情況下的重票機率
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "賣出一張票," + "票號爲:" + ticket);
                    ticket--;
                } else {
                    System.out.println("票已賣完,謝謝關照");
                    break;
                }
            }
        }
    }
}

//Thread繼承子類中使用同步代碼塊
class People extends Thread {
    //private static Object obj = new Object();//也可以使用靜態屬性,保證類的所有對象共用一把鎖
    @Override
    public void run() {
        synchronized (People.class) {//或者填入靜態屬性obj
            //Java中的類也是一個對象,是Class 類的對象,類只會被類加載器加載一次,所以可以保證鎖的唯一性
            //使用類名.class的方式調用類對象
            System.out.println(Thread.currentThread().getName() + "天仙下凡");
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "人間失格");
            System.out.println(Thread.currentThread().getName() + "駕鶴西遊");
        }
    }

    public People(String name) {
        super(name);
    }
}

//Runnable實現類中使用同步方法
class TenMultiple implements Runnable {
    private int number = 100;

    @Override
    public void run() {
        while(true) {
            FIndNumber();
            if(number <= 0){
                break;
            }
        }
    }

    public synchronized void FIndNumber() {

        if (number % 10 == 0 && number != 0) {
            System.out.println(Thread.currentThread().getName() + "找到一個10的倍數" + number);
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        number--;
    }
}

//Thread繼承子類中使用同步方法
class EatFood extends Thread{
    private static int number = 20;
    @Override
    public void run() {
        while(true) {
            eat();
            if (number <= 0){
                break;
            }
        }
    }
    public synchronized static void eat(){
        if(number > 0) {
            System.out.println(Thread.currentThread().getName() + "食用了第" + (21 - number) + "份大碗寬面");
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            number--;
        }else{
            System.out.println("面已經喫完了");
        }
    }

    public EatFood(String name) {
        super(name);
    }
}
//線程安全的單例懶漢式
class Single{
    private static Single single = null;
    private Single(){

    }
    //方式一,直接將方法聲明爲static synchronized.
//    public static synchronized  Single getInstance(){
//        if(single == null){
//            single = new Single();
//        }
//          return single;
//    }

    //方式二效率更好
    public static Single getInstance(){
        if(single == null) {//如果已經有了對象則不會進入同步代碼塊,直接返回對象
            synchronized (Single.class) {
                if (single == null) {//同步裏面必須再做一次判斷,一開始可能有多個線程進行到此處
                    //如果不加判斷,後面的線程依然會創建新對象。
                    single = new Single();
                }
            }
        }
        return single;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章