在多線程併發的狀況下,有時就涉及到對於同一資源的讀寫,若是不進行一些處理,容易出現數據混亂,結果和實際不一致等問題。java中能夠使用synchronized關鍵字對資源鎖定。java
synchronized有2種用法:
1.修飾代碼塊,以某個對象爲鎖,鎖的範圍是指定的代碼塊。
2.修飾方法,其實也能夠等價於修飾代碼塊,好比修飾普通方法:多線程
synchronized void doxx(){ //......... }
等價於併發
void doxx(){ synchronized (this){ //......... } }
以當前實體爲鎖對象,整個方法都是在代碼塊中。也能修飾靜態方法:dom
public class Test{ static synchronized void doxx(){ //......... } }
等價於ide
public class Test{ static void doxx(){ synchronized (Test.class){ //......... } } }
先舉一個反例demo:this
import java.util.List; import java.util.Random; public class Thread1 extends Thread{ private List list; public Thread1(List list) { this.list=list; } @Override public void run() { try { for(int i=0;i<100;i++){ Thread.sleep(10);//模擬處理一些業務,這樣也更容易重現問題 int randon = new Random().nextInt(); list.add(randon); } } catch (InterruptedException e) { e.printStackTrace(); } } }
public static void main(String[] args) throws InterruptedException { List list = new ArrayList<>(1000); Thread1 t1 = new Thread1(list); t1.start(); Thread1 t2 = new Thread1(list); t2.start(); t1.join(); t2.join(); System.out.println("size="+list.size()); }
執行結果:線程
size=162
這個結果是不肯定,每次執行均可能不同。demo裏啓動了2個線程,每一個執行100次,按理list的數據量應該會是200個。這個就是本文開始提到的,多個線程讀寫同一個對象時,發生了數據異常。那麼咱們再用synchronized對demo進行小小的改造。code
import java.util.List; import java.util.Random; public class Thread1 extends Thread{ private List list; public Thread1(List list) { this.list=list; } @Override public void run() { try { for(int i=0;i<100;i++){ Thread.sleep(10);//模擬處理一些業務,這樣也更容易重現問題 int randon = new Random().nextInt(); synchronized (list) {//就只改這個地方 list.add(randon); } } } catch (InterruptedException e) { e.printStackTrace(); } } }
main方法保持不變,結果以下:對象
size=200
可見使用synchronized關鍵字後,代碼的執行結果恢復了正常。資源
import java.util.List; import java.util.Random; public class Thread1 extends Thread{ private List list; public Thread1(List list) { this.list=list; } public synchronized void run() { try { for(int i=0;i<100;i++){ Thread.sleep(10);//模擬處理一些業務,這樣也更容易重現問題 int randon = new Random().nextInt(); list.add(randon); } } catch (InterruptedException e) { e.printStackTrace(); } } }
main方法不變,執行結果:
size=150
這是不少人開始接觸多線程時,會出現的錯誤,明明對run方法用了synchronized 關鍵字怎麼出來的結果是不對的。根據上面提到的咱們把代碼轉變一下:
import java.util.List; import java.util.Random; public class Thread1 extends Thread{ private List list; public Thread1(List list) { this.list=list; } public void run() { try { synchronized(this){//把synchronized改到這個地方 for(int i=0;i<100;i++){ Thread.sleep(10);//模擬處理一些業務,這樣也更容易重現問題 int randon = new Random().nextInt(); list.add(randon); } } } catch (InterruptedException e) { e.printStackTrace(); } } }
synchronized用在this上,因此t1.start()鎖定的是t1,t2.start()鎖定的是t2,2者鎖定的對象不一樣天然就沒有相應的效果。
那是否是synchronized用在方法上就沒有做用呢?固然不會,先看下面的例子:
import java.util.Random; public class Thread1 extends Thread{ private SyncList list; public Thread1(SyncList list) { this.list=list; } public void run() { try { for(int i=0;i<100;i++){ Thread.sleep(10);//模擬處理一些業務,這樣也更容易重現問題 int randon = new Random().nextInt(); list.addList(randon);//注意這裏 } } catch (InterruptedException e) { e.printStackTrace(); } } }
import java.util.ArrayList; import java.util.List; public class SyncList<E>{ private List<E> list; public SyncList(List list) { this.list = list; } public void addList(E obj){ list.add(obj); } public List<E> getList() { return list; } }
public static void main(String[] args) throws InterruptedException { SyncList list = new SyncList(new ArrayList<>(1000)); Thread1 t1 = new Thread1(list); t1.start(); Thread1 t2 = new Thread1(list); t2.start(); t1.join(); t2.join(); System.out.println("size="+list.getList().size()); }
執行結果:
size=161
修改一下SyncList:
import java.util.ArrayList; import java.util.List; public class SyncList<E>{ private List<E> list; public SyncList(List list) { this.list = list; } public synchronized void addList(E obj){//僅在這裏加上synchronized list.add(obj); } public List<E> getList() { return list; } }
執行結果:
size=200
這個就是synchronized用在方法上的一個例子,鎖定的對象都是同一個SyncList,因此最終結果是正確的。所以synchronized使用上很重要的一點,是保障多個線程鎖定的對象要一致。
public class Thread1 extends Thread{ private Object lock; public Thread1(Object lock) { this.lock=lock; } public void run() { try { synchronized (lock) { for(int i=5;i>-1;i--){ System.out.println(Thread.currentThread().getName()+":"+i); Thread.sleep(200); int j= 100/i; } } } catch (InterruptedException e) { e.printStackTrace(); } } }
public static void main(String[] args) throws InterruptedException { Object lock = new Object(); for(int i=0;i<2;i++){ Thread1 t1 = new Thread1(lock); t1.start(); t1.join(); Thread.sleep(10); } }
執行結果:
Thread-0:5 Thread-0:4 Thread-0:3 Thread-0:2 Thread-0:1 Thread-0:0 Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero at Thread1.run(Thread1.java:12) Thread-1:5 Thread-1:4 Thread-1:3 Thread-1:2 Thread-1:1 Thread-1:0 Exception in thread "Thread-1" java.lang.ArithmeticException: / by zero at Thread1.run(Thread1.java:12)
能夠看到因爲Thread-0先獲取鎖,Thread-1一直處於等待狀態,Thread-0一直執行到i=0時,程序發生異常,鎖被釋放。Thread-1就得到了鎖開始執行。
1.synchronized能夠修飾方法或者代碼塊。2.儘可能使用代碼塊以及減小代碼塊的範圍,有利於提升程序運行效率。3.要注意鎖定的對象是否一致。