一、線程安全
多個線程在執行同一段代碼的時候,每次的執行結果和單線程執行的結果都是一樣的,不存在執行結果的二義性,就可以稱作是線程安全的。
講到線程安全問題,其實是指多線程環境下對共享資源的訪問可能會引起此共享資源的不一致性。因此,為避免線程安全問題,應該避免多線程環境下對此共享資源的並發訪問。
線程安全問題多是由全局變量和靜態變量引起的,當多個線程對共享數據只執行讀操作,不執行寫操作時,一般是線程安全的;當多個線程都執行寫操作時,需要考慮線程同步來解決線程安全問題。
用模擬票顯示線程安全
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 已經隱含地為您執行同步。這些情況包括:
- 由靜態初始化器(在靜態字段上或 static{} 塊中的初始化器)
- 初始化數據時
- 訪問 final 字段時
- 在創建線程之前創建對象時
- 線程可以看見它將要處理的對象時
鎖的原理:
- Java中每個對象都有一個內置鎖
- 當程序運行到非靜態的synchronized同步方法上時,自動獲得與正在執行代碼類的當前實例(this實例)有關的鎖。獲得一個對象的鎖也稱為獲取鎖、鎖定對象、在對象上鎖定或在對象上同步。
- 當程序運行到synchronized同步方法或代碼塊時才該對象鎖才起作用。
- 一個對象只有一個鎖。所以,如果一個線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個線程釋放(或返回)鎖。這也意味着任何其他線程都不能進入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。
- 釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。
如果是多線程同時操作(讀取或者修改重點是修改)一個數據 如果這個數據沒有在設成synchronized的方法里的加 會造成更新丟失或者數據損壞 這會對你的程序有致命的影響
如果給方法加上synchronized 那這個方法里的數據就都會是線程安全的 不會造成更新丟失或者數據損壞 缺點是會帶來額外的系統資源開銷
說了這么多其實意思就是你要是寫多線程程序就用hashmap 如果是單線程就用hashtable