怎麼解決線程的安全問題呢?java
基本上全部解決線程安全問題的方式都是採用「序列化臨界資源訪問」的方式,即在同一時刻只有一個線程操做臨界資源,操做完了才能讓其餘線程進行操做,也稱做同步互斥訪問。安全
在Java中通常採用synchronized
和Lock
來實現同步互斥訪問。多線程
首先咱們先來了解一下互斥鎖,互斥鎖:就是能達到互斥訪問目的的鎖。ide
若是對一個變量加上互斥鎖,那麼在同一時刻,該變量只能有一個線程能訪問,即當一個線程訪問臨界資源時,其餘線程只能等待。優化
在Java中,每個對象都有一個鎖標記(monitor),也被稱爲監視器,當多個線程訪問對象時,只有獲取了對象的鎖才能訪問。this
在咱們編寫代碼的時候,可使用synchronized
修飾對象的方法或者代碼塊,當某個線程訪問這個對象synchronized
方法或者代碼塊時,就獲取到了這個對象的鎖,這個時候其餘對象是不能訪問的,只能等待獲取到鎖的這個線程執行完該方法或者代碼塊以後,才能執行該對象的方法。spa
咱們來看個示例進一步理解synchronized
關鍵字:線程
public class Example { public static void main(String[] args) { final InsertData insertData = new InsertData(); new Thread() { public void run() { insertData.insert(Thread.currentThread()); }; }.start(); new Thread() { public void run() { insertData.insert(Thread.currentThread()); }; }.start(); } } class InsertData { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public void insert(Thread thread){ for(int i=0;i<5;i++){ System.out.println(thread.getName()+"在插入數據"+i); arrayList.add(i); } } }
這段代碼的執行是隨機的(每次結果都不同):code
Thread-0在插入數據0` `Thread-1在插入數據0` `Thread-1在插入數據1` `Thread-1在插入數據2` `Thread-1在插入數據3` `Thread-1在插入數據4` `Thread-0在插入數據1` `Thread-0在插入數據2` `Thread-0在插入數據3` `Thread-0在插入數據4
如今咱們加上synchronized
關鍵字來看看執行結果:對象
public synchronized void insert(Thread thread){ for(int i=0;i<5;i++){ System.out.println(thread.getName()+"在插入數據"+i); arrayList.add(i); } }
輸出:
Thread-0在插入數據0` `Thread-0在插入數據1` `Thread-0在插入數據2` `Thread-0在插入數據3` `Thread-0在插入數據4` `Thread-1在插入數據0` `Thread-1在插入數據1` `Thread-1在插入數據2` `Thread-1在插入數據3` `Thread-1在插入數據4
能夠發現,線程1
會等待線程0
插入完數據以後再執行,說明線程0
和線程1
是順序執行的。
從這兩個示例中,咱們能夠知道synchronized
關鍵字能夠實現方法同步互斥訪問。
在使用synchronized
關鍵字的時候有幾個問題須要咱們注意:
synchronized
的方法時,其餘synchronized
的方法是不能被訪問的,道理很簡單,一個對象只有一把鎖;synchronized
方法時,其餘線程能夠訪問該對象的非synchronized
方法,由於訪問非synchronized
不須要獲取鎖,是能夠隨意訪問的;object1
的synchronized
方法fun1
,另一個線程B須要訪問對象object2
的synchronized
方法fun1
,即便object1
和object2
是同一類型),也不會產生線程安全問題,由於他們訪問的是不一樣的對象,因此不存在互斥問題。synchronized
代碼塊對於咱們優化多線程的代碼頗有幫助,首先咱們來看看它長啥樣:
synchronized(synObject) {}
當在某個線程中執行該段代碼時,該線程會獲取到該對象的synObject
鎖,此時其餘線程沒法訪問這段代碼塊,synchronized
的值能夠是this
表明當前對象,也能夠是對象的屬性,用對象的屬性時,表示的是對象屬性的鎖。
有了synchronized
代碼塊,咱們能夠將上述添加數據的例子修改爲以下兩種形式:
class InsertData { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public void insert(Thread thread){ synchronized (this) { for(int i=0;i<100;i++){ System.out.println(thread.getName()+"在插入數據"+i); arrayList.add(i); } } } }
上述代碼就是synchronized
代碼塊添加鎖的兩種方式,能夠發現添加synchronized
代碼塊,要比直接在方法上添加synchronized
關鍵字更加靈活。
當咱們用sychronized
關鍵字修飾方法時,這個方法只能同時讓一個線程訪問,可是有時候極可能只有一部分代碼須要同步,而這個時候使用sychronized
關鍵字修飾的方法是作不到的,可是使用sychronized
代碼塊就能夠實現這個功能。
而且若是一個線程執行一個對象的非static synchronized
方法,另一個線程須要執行這個對象所屬類的static synchronized
方法,此時不會發生互斥現象,由於訪問static synchronized
方法佔用的是類鎖,而訪問非static synchronized
方法佔用的是對象鎖,因此不存在互斥現象。
來看一段代碼:
class InsertData { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); private Object object = new Object(); public void insert(Thread thread){ synchronized (object) { for(int i=0;i<100;i++){ System.out.println(thread.getName()+"在插入數據"+i); arrayList.add(i); } } } }
執行結果:
執行insert` `執行insert1` `執行insert1完畢` `執行insert完畢