1. 程式人生 > 其它 >Java多執行緒(三)執行緒同步

Java多執行緒(三)執行緒同步

Java多執行緒(三)

1.多執行緒同步

執行緒安全

之前售票的例子中,多執行緒共享tickets可能導致執行緒安全問題。

舉例:

class SaleThread implements Runnable{
    private int tickets=10;
    public void run(){
        while(true){
            if(tickets>0){
                try{
                    Thread.sleep(100);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在發售第"+tickets--+"張票");
            }
        }
    }
}

public class Page369 {
    public static void main(String[] args) {
        SaleThread saleThread=new SaleThread();
        new Thread(saleThread,"視窗1").start();
        new Thread(saleThread,"視窗2").start();
        new Thread(saleThread,"視窗3").start();
        new Thread(saleThread,"視窗4").start();
    }
}

錯誤原因: 在while迴圈中新增sleep()方法,這樣就模擬了售票過程中的延遲。由於執行緒有延遲,當票號減為1,假設視窗2執行緒此時出售1號票,對票號進行判斷後進入while迴圈,由於售票前通過sleep()模擬耗時,此時票號任為1,其他執行緒會進行售票,所以最終會出現0,-1,-2這樣的票號。

同步程式碼塊

執行緒安全問題是由於多個執行緒同時處理共享資源造成的,想要解決執行緒安全問題,必須保證處理共享資源的程式碼在任意時刻只能有一個執行緒訪問。為此Java中提供了執行緒同步機制。將共享資原始碼放置在一個使用synchronized關鍵字修飾的程式碼塊中,這個程式碼塊稱為同步程式碼塊。

語法:

synchronized(lock){
    //操作共享資原始碼塊
}

舉例:

class SaleThread2 implements Runnable{
    private int tickets=10;
    Object lock=new Object();	//定義任意一個物件作為同步程式碼塊的鎖
    public void run(){
        while(true){
            //定義同步程式碼塊
            synchronized(lock){
                if(tickets>0){
                    try{
                        Thread.sleep(100);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在發售第"+tickets--+"張票");
                }
            }
        }
    }
}

public class Page371 {
    public static void main(String[] args) {
        SaleThread2 saleThread2=new SaleThread2();
        new Thread(saleThread2,"視窗1").start();
        new Thread(saleThread2,"視窗2").start();
        new Thread(saleThread2,"視窗3").start();
        new Thread(saleThread2,"視窗4").start();
    }
}

同步方法

把共享資源放在synchronized定義的區域內,便為這些操作都加了同步鎖。在方法前也可以用synchronized修飾,被修飾的方法為同步方法,它能實現和同步程式碼塊同樣的功能。

舉例:

class SaleThread3 implements Runnable {
    private int tickets=10;
    public void run(){
        while(true){
            saleTicket();
        }
    }
    private synchronized void saleTicket(){
        if(tickets>0){
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"正在發售第"+tickets--+"張票");
        }
    }
}

public class Page372 {
    public static void main(String[] args) {
        SaleThread3 saleThread=new SaleThread3();
        new Thread(saleThread,"視窗1").start();
        new Thread(saleThread,"視窗2").start();
        new Thread(saleThread,"視窗3").start();
        new Thread(saleThread,"視窗4").start();
    }
}

同步鎖

synchronized同步程式碼塊和同步方法也有一些限制,比如它無法中斷一個正在等候獲得鎖的執行緒,也無法通過輪詢得到鎖,如果不想等下去就沒辦法得到鎖。從JDK5開始Java增加了一個功能更強大的Lock鎖。Lock鎖與synchronized隱式鎖在功能上基本相同,但Lock鎖可以讓某個執行緒在持續獲取同步鎖失敗後返回,不再繼續等待,在使用時也更加靈活。

舉例:

import java.util.concurrent.locks.*;

class LockThread implements Runnable{
    private int tickets=10;
    //定義一個Lock鎖物件
    private final Lock lock=new ReentrantLock();
    public void run(){
        while(true){
            lock.lock();    //對程式碼塊加鎖
            if(tickets>0){
                try{
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+"正在發售第"+tickets--+"張票");
                }catch(InterruptedException e){
                    e.printStackTrace();
                }finally{
                    lock.unlock();  //執行完程式碼塊後釋放鎖
                }
            }
        }
    }
}

public class Page374 {
    public static void main(String[] args) {
        LockThread lockThread=new LockThread();
        new Thread(lockThread,"視窗1").start();
        new Thread(lockThread,"視窗2").start();
        new Thread(lockThread,"視窗3").start();
        new Thread(lockThread,"視窗4").start();
    }
}

通過Lock介面的實現類ReentrantLock來建立Lock鎖物件,並通過Lock鎖物件的lock()方法和unlock()方法對核心程式碼塊進行上鎖和解鎖。

死鎖問題

兩個執行緒在執行時都在等待對方的鎖,這樣就導致程式停滯,稱為死鎖。

舉例:

class DeadLockThread implements Runnable{
    //定義兩個不同的鎖物件
    static Object chopsticks=new Object();
    static Object knifeAndFork=new Object();
    private boolean flag;
    DeadLockThread(boolean flag){
        this.flag=flag;
    }
    public void run(){
        if(flag){
            while(true){
                synchronized(chopsticks){
                    System.out.println(Thread.currentThread().getName()+"---if---chopsticks");
                    synchronized(knifeAndFork){
                        System.out.println(Thread.currentThread().getName()+"---if---knifeAndFork");
                    }
                }
            }
        }else{
            while(true){
                synchronized(knifeAndFork){
                    System.out.println(Thread.currentThread().getName()+"---else---knifeAndFork");
                    synchronized (chopsticks){
                        System.out.println(Thread.currentThread().getName()+"---else---chopsticks");
                    }
                }
            }
        }
    }
}

public class Page376 {
    public static void main(String[] args) {
        DeadLockThread thread1=new DeadLockThread(true);
        DeadLockThread thread2=new DeadLockThread(false);
        new Thread(thread1,"Chinese").start();
        new Thread(thread2,"American").start();
    }
}

兩個執行緒都需要對方所佔用的鎖,但是都無法釋放自己所擁有鎖。