Java 實現線程安全的三種方式

一個程序在運行起來的時候會轉換成進程,通常含有多個線程。

 通常情況下,一個進程中的比較耗時的操作(如長循環、文件上傳下載、網絡資源獲取等),往往會採用多線程來解決。

比如顯示生活中,銀行取錢問題、火車票多個售票窗口的問題,通常會涉及到併發的問題,從而需要多線程的技術。

 當進程中有多個併發線程進入一個重要數據的代碼塊時,在修改數據的過程中,很有可能引發線程安全問題,從而造成數據異常。例如,正常邏輯下,同一個編號的火車票只能售出一次,卻由於線程安全問題而被多次售出,從而引起實際業務異常。

第一種實現線程安全的方式  同步代碼塊

package com.bpan.spring.beans.thread;

import com.sun.org.apache.regexp.internal.recompile;

public class ThreadSynchronizedSecurity {
    
    static int tickets = 10;
    
    class SellTickets implements Runnable{

        @Override
        public void run() {
            // 同步代碼塊
            while(tickets > 0) {
                
                synchronized (this) {
                    
//                    System.out.println(this.getClass().getName().toString());
                    
                    if (tickets <= 0) {
                        
                        return;
                    }
                    
                    System.out.println(Thread.currentThread().getName()+"--->售出第:  "+tickets+" 票");
                    tickets--;
                    
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                
                if (tickets <= 0) {
                    
                    System.out.println(Thread.currentThread().getName()+"--->售票結束!");
                }
            }
        }
    }
    
    
    public static void main(String[] args) {
        
        
        SellTickets sell = new ThreadSynchronizedSecurity().new SellTickets();
        
        Thread thread1 = new Thread(sell, "1號窗口");
        Thread thread2 = new Thread(sell, "2號窗口");
        Thread thread3 = new Thread(sell, "3號窗口");
        Thread thread4 = new Thread(sell, "4號窗口");
        
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        
        
    }
    

}

第二種 方式   同步方法 

package com.bpan.spring.beans.thread;

public class ThreadSynchroniazedMethodSecurity {
    
    
    static int tickets = 10;
    
    class SellTickets implements Runnable{

        @Override
        public void run() {
            //同步方法
            while (tickets > 0) {
                
                synMethod();
                
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                
                if (tickets<=0) {
                    
                    System.out.println(Thread.currentThread().getName()+"--->售票結束");
                }
                
            }
            
            
        }
        
        synchronized void synMethod() {
            
            synchronized (this) {
                if (tickets <=0) {
                    
                    return;
                }
                
                System.out.println(Thread.currentThread().getName()+"---->售出第 "+tickets+" 票 ");
                tickets-- ;
            }
            
        }
        
    }
    public static void main(String[] args) {
        
        
        SellTickets sell = new ThreadSynchroniazedMethodSecurity().new SellTickets();
        
        Thread thread1 = new Thread(sell, "1號窗口");
        Thread thread2 = new Thread(sell, "2號窗口");
        Thread thread3 = new Thread(sell, "3號窗口");
        Thread thread4 = new Thread(sell, "4號窗口");
        
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        
    }

}

第三種 方式    Lock鎖機制, 通過創建Lock對象,採用lock()加鎖,unlock()解鎖,來保護指定的代碼塊 

package com.bpan.spring.beans.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadLockSecurity {
    
    static int tickets = 10;
    
    class SellTickets implements Runnable{
        
        Lock lock = new ReentrantLock();

        @Override
        public void run() {
            // Lock鎖機制
            while(tickets > 0) {
                
                try {
                    lock.lock();
                    
                    if (tickets <= 0) {
                        
                        return;
                    }
                        
                    System.out.println(Thread.currentThread().getName()+"--->售出第:  "+tickets+" 票");
                    tickets--;
                } catch (Exception e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }finally {
                    
                    lock.unlock();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
                
            if (tickets <= 0) {
                
                System.out.println(Thread.currentThread().getName()+"--->售票結束!");
            }
            
        }
    }
    
    
    public static void main(String[] args) {
        
        
        SellTickets sell = new ThreadLockSecurity().new SellTickets();
        
        Thread thread1 = new Thread(sell, "1號窗口");
        Thread thread2 = new Thread(sell, "2號窗口");
        Thread thread3 = new Thread(sell, "3號窗口");
        Thread thread4 = new Thread(sell, "4號窗口");
        
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        
        
    }
    

}

 

 最後總結:

  由於synchronized是在JVM層面實現的,因此係統可以監控鎖的釋放與否;而ReentrantLock是使用代碼實現的,系統無法自動釋放鎖,需要在代碼中的finally子句中顯式釋放鎖lock.unlock()。

  另外,在併發量比較小的情況下,使用synchronized是個不錯的選擇;但是在併發量比較高的情況下,其性能下降會很嚴重,此時ReentrantLock是個不錯的方案。

 

 補充:  

  在使用synchronized 代碼塊時,可以與wait()、notify()、nitifyAll()一起使用,從而進一步實現線程的通信。
其中,wait()方法會釋放佔有的對象鎖,當前線程進入等待池,釋放cpu,而其他正在等待的線程即可搶佔此鎖,獲得鎖的線程即可運行程序;線程的sleep()方法則表示,當前線程會休眠一段時間,休眠期間,會暫時釋放cpu,但並不釋放對象鎖,也就是說,在休眠期間,其他線程依然無法進入被同步保護的代碼內部,當前線程休眠結束時,會重新獲得cpu執行權,從而執行被同步保護的代碼。
wait()和sleep()最大的不同在於wait()會釋放對象鎖,而sleep()不會釋放對象鎖。

  notify()方法會喚醒因爲調用對象的wait()而處於等待狀態的線程,從而使得該線程有機會獲取對象鎖。調用notify()後,當前線程並不會立即釋放鎖,而是繼續執行當前代碼,直到synchronized中的代碼全部執行完畢,纔會釋放對象鎖。JVM會在等待的線程中調度一個線程去獲得對象鎖,執行代碼。

  需要注意的是,wait()和notify()必須在synchronized代碼塊中調用。

  notifyAll()是喚醒所有等待的線程。

package com.bpan.spring.beans.thread;

public class ThreadDemo {
    
    static final Object obj = new Object();
    
    //第一個子線程
    static class ThreadA implements Runnable{

        @Override
        public void run() {
            
            
            int count = 10;
            while(count > 0) {
                
                synchronized (ThreadDemo.obj) {
                    
                    System.out.println("A-----"+count);
                    count--;
                    
                    synchronized (ThreadDemo.obj) {
                        
                        //notify()方法會喚醒因爲調用對象的wait()而處於等待狀態的線程,從而使得該線程有機會獲取對象鎖。
                        //調用notify()後,當前線程並不會立即釋放鎖,而是繼續執行當前代碼,直到synchronized中的代碼全部執行完畢,
                        ThreadDemo.obj.notify();
                        
                        try {
                            ThreadDemo.obj.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            }
            
        }
        
    }
    
    static class ThreadB implements Runnable{
        
        
        @Override
        public void run() {
            
            int count = 10;
            
            while(count > 0) {
                
                synchronized (ThreadDemo.obj) {
                    System.out.println("B-----"+count);
                    count--;
                    
                    synchronized (ThreadDemo.obj) {
                    
                        //notify()方法會喚醒因爲調用對象的wait()而處於等待狀態的線程,從而使得該線程有機會獲取對象鎖。
                        //調用notify()後,當前線程並不會立即釋放鎖,而是繼續執行當前代碼,直到synchronized中的代碼全部執行完畢,
                        ThreadDemo.obj.notify();
                        
                        try {
                            ThreadDemo.obj.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                    
                }
                
            }
            
        }
        
    }
    
    public static void main(String[] args) {
        
        
        new Thread(new ThreadA()).start();
        new Thread(new ThreadB()).start();
        
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章