天天看點

Java知識【線程同步】

2.線程同步

2.1賣票【應用】

  • 案例需求

    某電影院目前正在上映國産大片,共有100張票,而它有3個視窗賣票,請設計一個程式模拟該電影院賣票

  • 實作步驟
  • 定義一個類SellTicket實作Runnable接口,裡面定義一個成員變量:private int tickets = 100;
  • 在SellTicket類中重寫run()方法實作賣票,代碼步驟如下
  • 判斷票數大于0,就賣票,并告知是哪個視窗賣的
  • 賣了票之後,總票數要減1
  • 票賣沒了,線程停止
  • 定義一個測試類SellTicketDemo,裡面有main方法,代碼步驟如下
  • 建立SellTicket類的對象
  • 建立三個Thread類的對象,把SellTicket對象作為構造方法的參數,并給出對應的視窗名稱
  • 啟動線程
  • 代碼實作
public class SellTicket implements Runnable {
    private int tickets = 100;
    //在SellTicket類中重寫run()方法實作賣票,代碼步驟如下
    @Override
    public void run() {
        while (true) {
            if(ticket <= 0){
                    //賣完了
                    break;
                }else{
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticket + "張票");
                }
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
        //建立SellTicket類的對象
        SellTicket st = new SellTicket();

        //建立三個Thread類的對象,把SellTicket對象作為構造方法的參數,并給出對應的視窗名稱
        Thread t1 = new Thread(st,"視窗1");
        Thread t2 = new Thread(st,"視窗2");
        Thread t3 = new Thread(st,"視窗3");

        //啟動線程
        t1.start();
        t2.start();
        t3.start();
    }
}      

2.2賣票案例的問題【了解】

  • 賣票出現了問題
  • 相同的票出現了多次
  • 出現了負數的票
  • 問題産生原因

    線程執行的随機性導緻的,可能在賣票過程中丢失cpu的執行權,導緻出現問題

2.3同步代碼塊解決資料安全問題【應用】

  • 安全問題出現的條件
  • 是多線程環境
  • 有共享資料
  • 有多條語句操作共享資料
  • 如何解決多線程安全問題呢?
  • 基本思想:讓程式沒有安全問題的環境
  • 怎麼實作呢?
  • 把多條語句操作共享資料的代碼給鎖起來,讓任意時刻隻能有一個線程執行即可
  • Java提供了同步代碼塊的方式來解決
  • 同步代碼塊格式:
synchronized(任意對象) { 
  多條語句操作共享資料的代碼 
}      

synchronized(任意對象):就相當于給代碼加鎖了,任意對象就可以看成是一把鎖

  • 同步的好處和弊端
  • 好處:解決了多線程的資料安全問題
  • 弊端:當線程很多時,因為每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會降低程式的運作效率
  • 代碼示範
public class SellTicket implements Runnable {
    private int tickets = 100;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) { // 對可能有安全問題的代碼加鎖,多個線程必須使用同一把鎖
                //t1進來後,就會把這段代碼給鎖起來
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //視窗1正在出售第100張票
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "張票");
                    tickets--; //tickets = 99;
                }
            }
            //t1出來了,這段代碼的鎖就被釋放了
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();

        Thread t1 = new Thread(st, "視窗1");
        Thread t2 = new Thread(st, "視窗2");
        Thread t3 = new Thread(st, "視窗3");

        t1.start();
        t2.start();
        t3.start();
    }
}      

2.4同步方法解決資料安全問題【應用】

  • 同步方法的格式

    同步方法:就是把synchronized關鍵字加到方法上

修飾符 synchronized 傳回值類型 方法名(方法參數) { 
  方法體;
}      

同步方法的鎖對象是什麼呢?

this

  • 靜态同步方法

    同步靜态方法:就是把synchronized關鍵字加到靜态方法上

修飾符 static synchronized 傳回值類型 方法名(方法參數) { 
  方法體;
}      

同步靜态方法的鎖對象是什麼呢?

類名.class

  • 代碼示範
public class MyRunnable implements Runnable {
    private static int ticketCount = 100;

    @Override
    public void run() {
        while(true){
            if("視窗一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean result = synchronizedMthod();
                if(result){
                    break;
                }
            }

            if("視窗二".equals(Thread.currentThread().getName())){
                //同步代碼塊
                synchronized (MyRunnable.class){
                    if(ticketCount == 0){
                       break;
                    }else{
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticketCount + "張票");
                    }
                }
            }

        }
    }

    private static synchronized boolean synchronizedMthod() {
        if(ticketCount == 0){
            return true;
        }else{
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticketCount + "張票");
            return false;
        }
    }
}      

public class Demo {

public static void main(String[] args) {

MyRunnable mr = new MyRunnable();

Thread t1 = new Thread(mr);
    Thread t2 = new Thread(mr);

    t1.setName("視窗一");
    t2.setName("視窗二");

    t1.start();
    t2.start();
}      

}

2.5Lock鎖【應用】

雖然我們可以了解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪裡加上了鎖,在哪裡釋放了鎖,為了更清晰的表達如何加鎖和釋放鎖,JDK5以後提供了一個新的鎖對象Lock

Lock是接口不能直接執行個體化,這裡采用它的實作類ReentrantLock來執行個體化

  • ReentrantLock構造方法
方法名 說明
ReentrantLock() 建立一個ReentrantLock的執行個體
  • 加鎖解鎖方法
方法名 說明
void lock() 獲得鎖
  • 代碼示範
public class Ticket implements Runnable {
    //票的數量
    private int ticket = 100;
    private Object obj = new Object();
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            //synchronized (obj){//多個線程必須使用同一把鎖.
            try {
                lock.lock();
                if (ticket <= 0) {
                    //賣完了
                    break;
                } else {
                    Thread.sleep(100);
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在賣票,還剩下" + ticket + "張票");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            // }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);

        t1.setName("視窗一");
        t2.setName("視窗二");
        t3.setName("視窗三");

        t1.start();
        t2.start();
        t3.start();
    }
}      

2.6死鎖【了解】

  • 概述

    線程死鎖是指由于兩個或者多個線程互相持有對方所需要的資源,導緻這些線程處于等待狀态,無法前往執行

  • 什麼情況下會産生死鎖
  1. 資源有限
  2. 同步嵌套
  • 代碼示範
public class Demo {
    public static void main(String[] args) {
        Object objA = new Object();
        Object objB = new Object();

        new Thread(()->{
            while(true){
                synchronized (objA){
                    //線程一
                    synchronized (objB){
                        System.out.println("小康同學正在走路");
                    }
                }
            }
        }).start();

        new Thread(()->{
            while(true){
                synchronized (objB){
                    //線程二
                    synchronized (objA){
                        System.out.println("小薇同學正在走路");
                    }
                }
            }
        }).start();
    }
}