程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> java多線程系列3-線程同步,java多線程3-線程

java多線程系列3-線程同步,java多線程3-線程

編輯:JAVA綜合教程

java多線程系列3-線程同步,java多線程3-線程


如果一個資源被多個線程同時訪問,可能會遭到破壞,這篇文章介紹java線程同步來解決這類問題

引入問題

某電影院目前正在上映賀歲大片,共有100張票,而它有3個售票窗口售票,請設計一個程序模擬該電影院售票。

方法一:繼承Thread類

public class SellTicket extends Thread {
 
    // 定義100張票
    // private int tickets = 100;
    // 為了讓多個線程對象共享這100張票,我們其實應該用靜態修飾
    private static int tickets = 100;
 
    @Override
    public void run() {
        // 定義100張票
        // 是為了模擬一直有票
        while (true) {
            if (tickets > 0) {
                System.out.println(getName() + "正在出售第" + (tickets--) + "張票");
            }
        }
    }
}
/*
 * 繼承Thread類來實現。
 */
public class SellTicketDemo {
    public static void main(String[] args) {
        // 創建三個線程對象
        SellTicket st1 = new SellTicket();
        SellTicket st2 = new SellTicket();
        SellTicket st3 = new SellTicket();
 
        // 給線程對象起名字
        st1.setName("窗口1");
        st2.setName("窗口2");
        st3.setName("窗口3");
 
        // 啟動線程
        st1.start();
        st2.start();
        st3.start();
    }
}

方法二:實現Runnable接口

public class SellTicket implements Runnable {
    // 定義100張票
    private int tickets = 100;
 
    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第"
                        + (tickets--) + "張票");
            }
        }
    }
}
/*
 * 實現Runnable接口的方式實現
 */
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();
    }
}

電影院售票程序,從表面上看不出什麼問題,在真實生活中,售票時網絡是不能實時傳輸的,總是存在延遲的情況,所以,在出售一張票以後,需要一點時間的延遲

改實現接口方式的賣票程序,每次賣票延遲100毫秒,代碼如下:

public class SellTicket implements Runnable {
    // 定義100張票
    private int tickets = 100;
    @Override
    public void run() {
        while (true) {
            // t1,t2,t3三個線程
            // 這一次的tickets = 1;
            if (tickets > 0) {
                // 為了模擬更真實的場景,我們稍作休息
                try {
                    Thread.sleep(100); //t1進來了並休息,t2進來了並休息,t3進來了並休息,
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
 
                System.out.println(Thread.currentThread().getName() + "正在出售第"
                        + (tickets--) + "張票");
                //窗口1正在出售第1張票,tickets=0
                //窗口2正在出售第0張票,tickets=-1
                //窗口3正在出售第-1張票,tickets=-2
            }
        }
    }
}
/*
 * 實現Runnable接口的方式實現
 */
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();
    }
}

出現問題:

相同的票出現多次

CPU的一次操作必須是原子性的

還出現了負數的票

隨機性和延遲導致的

解決線程安全問題的基本思想與方法

首先想為什麼出現問題?(也是我們判斷是否有問題的標准)

  • 是否是多線程環境
  • 是否有共享數據
  • 是否有多條語句操作共享數據

如何解決多線程安全問題呢?

基本思想:讓程序沒有安全問題的環境。

把多個語句操作共享數據的代碼給鎖起來,讓任意時刻只能有一個線程執行即可。

解決線程安全問題實現1--同步代碼塊

格式:synchronized(對象){需要同步的代碼;}

同步可以解決安全問題的根本原因就在那個對象上。該對象如同鎖的功能。

修改上面的代碼如下:

public class SellTicket implements Runnable {
    // 定義100張票
    private int tickets = 100;
    //創建鎖對象
    private Object obj = new Object();
    
    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "張票");
                }
            }
        }
    }
}
/*
 * 同步代碼塊:
 *         synchronized(對象){
 *             需要同步的代碼;
 *         }
 * 
 *         A:對象是什麼呢?
 *             我們可以隨便創建一個對象試試。
 *         B:需要同步的代碼是哪些呢?
 *             把多條語句操作共享數據的代碼的部分給包起來
 * 
 *         注意:
 *             同步可以解決安全問題的根本原因就在那個對象上。該對象如同鎖的功能。
 *             多個線程必須是同一把鎖。
 */
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--同步方法

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

1、同步方法的鎖對象:this

public class SellTicket implements Runnable {
    private static int tickets = 100;
    private Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "張票 ");
                }
            }
        }
    }
    private synchronized void sellTicket() {
        if(tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + "正在出售第" + (tickets--) + "張票 ");
        }
    }
}

2、靜態方法的鎖對象:類的字節碼文件對象。

public class SellTicket implements Runnable {
    private static int tickets = 100;
    private Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            synchronized (SellTicket.class) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "張票 ");
                }
            }
        }
    }
    private static synchronized void sellTicket() {
        if(tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + "正在出售第" + (tickets--) + "張票 ");
        }
    }
}

同步的前提:

  • 多個線程
  • 多個線程使用的是同一個鎖對象

同步的好處:同步的出現解決了多線程的安全問題。

同步的弊端:當線程相當多時,因為每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會降低程序的運行效率。

解決線程安全問題實現3--Lock鎖的使用

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

ReentrantLock (Java Platform SE 6)

一個可重入的互斥鎖 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行為和語義,但功能更強大。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class SellTicket implements Runnable {
 
    // 定義票
    private int tickets = 100;
 
    // 定義鎖對象
    private Lock lock = new ReentrantLock();
 
    @Override
    public void run() {
        while (true) {
            try {
                // 加鎖
                lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "張票");
                }
            } finally {
                // 釋放鎖
                lock.unlock();
            }
        }
    }
 
}

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved