線程安全、線程同步


一、線程安全

多個線程在執行同一段代碼的時候,每次的執行結果和單線程執行的結果都是一樣的,不存在執行結果的二義性,就可以稱作是線程安全的。

講到線程安全問題,其實是指多線程環境下對共享資源的訪問可能會引起此共享資源的不一致性。因此,為避免線程安全問題,應該避免多線程環境下對此共享資源的並發訪問。

線程安全問題多是由全局變量和靜態變量引起的,當多個線程對共享數據只執行讀操作,不執行寫操作時,一般是線程安全的;當多個線程都執行寫操作時,需要考慮線程同步來解決線程安全問題。

 用模擬票顯示線程安全

package com.oracle.demo07;

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

public class Tickets3 implements Runnable {

    private int tickect = 100;
//    創建鎖接口對象
Lock lock=new ReentrantLock();

    public void run() {
while(true){
    lock.lock();
    if(tickect>0){
        try {
            Thread.sleep(50);
            System.out.println(Thread.currentThread().getName()+":出售第"+tickect+"張票");
            tickect--;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        不管有沒有異常都執行
        finally {
            lock.unlock();
            
        }
        
    }}
    
}
    }
package com.oracle.demo07;

public class demo01 {
    public static void main(String[] args) {
//        創建線程任務
        Tickets3 tickects=new Tickets3();
        Thread t1=new Thread(tickects);
        Thread t2=new Thread(tickects);
        Thread t3=new Thread(tickects);
        t1.start();
        t2.start();
        t3.start();
        
    }

}

 

二、線程同步(synchronized/Lock)

線程同步:將操作共享數據的代碼行作為一個整體,同一時間只允許一個線程執行,執行過程中其他線程不能參與執行。目的是為了防止多個線程訪問一個數據對象時,對數據造成的破壞。

(1)同步方法(synchronized)

對共享資源進行訪問的方法定義中加上synchronized關鍵字修飾,使得此方法稱為同步方法。可以簡單理解成對此方法進行了加鎖,其鎖對象為當前方法所在的對象自身。多線程環境下,當執行此方法時,首先都要獲得此同步鎖(且同時最多只有一個線程能夠獲得),只有當線程執行完此同步方法后,才會釋放鎖對象,其他的線程才有可能獲取此同步鎖,以此類推...格式如下:

public synchronized void run() { // .... }

 

(2)同步代碼塊(synchronized)

使用同步方法時,使得整個方法體都成為了同步執行狀態,會使得可能出現同步范圍過大的情況,於是,針對需要同步的代碼可以直接另一種同步方式——同步代碼塊來解決。格式如下:

synchronized (obj) { // .... }

其中,obj為鎖對象,因此,選擇哪一個對象作為鎖是至關重要的。一般情況下,都是選擇此共享資源對象作為鎖對象。

 

(3)同步鎖(Lock)

 使用Lock對象同步鎖可以方便地解決選擇鎖對象的問題,唯一需要注意的一點是Lock對象需要與資源對象同樣具有一對一的關系。Lock對象同步鎖一般格式為:

復制代碼
class X { // 顯示定義Lock同步鎖對象,此對象與共享資源具有一對一關系 private final Lock lock = new ReentrantLock(); public void m(){ // 加鎖  lock.lock(); //... 需要進行線程安全同步的代碼 // 釋放Lock鎖  lock.unlock(); } }
復制代碼

 

什么時候需要同步:

(1)可見性同步:在以下情況中必須同步: 1)讀取上一次可能是由另一個線程寫入的變量 ;2)寫入下一次可能由另一個線程讀取的變量

(2)一致性同步:當修改多個相關值時,您想要其它線程原子地看到這組更改—— 要么看到全部更改,要么什么也看不到。

這適用於相關數據項(如粒子的位置和速率)和元數據項(如鏈表中包含的數據值和列表自身中的數據項的鏈)。

在某些情況中,您不必用同步來將數據從一個線程傳遞到另一個,因為 JVM 已經隱含地為您執行同步。這些情況包括:

  1. 由靜態初始化器(在靜態字段上或 static{} 塊中的初始化器)
  2. 初始化數據時 
  3. 訪問 final 字段時
  4. 在創建線程之前創建對象時 
  5. 線程可以看見它將要處理的對象時

 

鎖的原理:

  • Java中每個對象都有一個內置鎖
  • 當程序運行到非靜態的synchronized同步方法上時,自動獲得與正在執行代碼類的當前實例(this實例)有關的鎖。獲得一個對象的鎖也稱為獲取鎖、鎖定對象、在對象上鎖定或在對象上同步。
  • 當程序運行到synchronized同步方法或代碼塊時才該對象鎖才起作用。
  • 一個對象只有一個鎖。所以,如果一個線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個線程釋放(或返回)鎖。這也意味着任何其他線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。
  • 釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。
鎖與同步要點:
1)、只能同步方法,而不能同步變量和類;
2)、每個對象只有一個鎖;當提到同步時,應該清楚在什么上同步?也就是說,在哪個對象上同步?
3)、不必同步類中所有的方法,類可以同時擁有同步和非同步方法。
4)、如果兩個線程要執行一個類中的synchronized方法,並且兩個線程使用相同的實例來調用方法,那么一次只能有一個線程能夠執行方法,另一個需要等待,直到鎖被釋放。也就是說:如果一個線程在對象上獲得一個鎖,就沒有任何其他線程可以進入(該對象的)類中的任何一個同步方法。
5)、如果線程擁有同步和非同步方法,則非同步方法可以被多個線程自由訪問而不受鎖的限制。
6)、線程睡眠時,它所持的任何鎖都不會釋放。
7)、線程可以獲得多個鎖。比如,在一個對象的同步方法里面調用另外一個對象的同步方法,則獲取了兩個對象的同步鎖。
8)、同步損害並發性,應該盡可能縮小同步范圍。同步不但可以同步整個方法,還可以同步方法中一部分代碼塊。
9)、在使用同步代碼塊時候,應該指定在哪個對象上同步,也就是說要獲取哪個對象的鎖。
10)、同步靜態方法,需要一個用於整個類對象的鎖,這個對象是就是這個類(XXX.class)。
 
線程安全與線程同步的關系:
實現線程安全就是在類的方法里加上了synchronized
如果是多線程同時操作(讀取或者修改重點是修改)一個數據 如果這個數據沒有在設成synchronized的方法里的加 會造成更新丟失或者數據損壞 這會對你的程序有致命的影響
如果給方法加上synchronized 那這個方法里的數據就都會是線程安全的 不會造成更新丟失或者數據損壞 缺點是會帶來額外的系統資源開銷
說了這么多其實意思就是你要是寫多線程程序就用hashmap 如果是單線程就用hashtable
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM