java-多线程安全-锁
一 同步函數
1.1 一般的方法
同步的另一種體現形式:同步函數。
同步函數使用的鎖是哪個?
經過分析:大概猜的是this,因為函數必須被對象調用。
驗證:
寫一個同步代碼塊,寫一個同步函數,如果同步代碼塊中的鎖對象和同步函數中的鎖對象是同一個,
就同步了,就沒有錯誤的數據了。如果不是同一個鎖對象,就不同步出現錯誤數據。
讓兩個線程,一個線程在同步代碼塊中執行,一個線程在同步函數中執行。
總結:同步函數使用的鎖時this。
同步函數和同步代碼塊有什么區別嗎?
同步函數使用的鎖是固定的this。當線程任務只需要一個同步時完全可以使用同步函數。
同步代碼塊使用的鎖可以是任意對象。當線程任務中需要多個同步時,必須通過鎖來區分,這時必須使用同步代碼塊。
同步代碼塊較為常用。
?
package test;class Ticket implements Runnable {private int tickets = 100;private Object obj = new Object();boolean flag = true;public void run() { while (true) { sale(); } } public synchronized void sale()// 同步函數,使用的鎖對象 this。 { if (tickets > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "...sale..." + tickets--);// 打印線程名稱。 } } } class ThreadDemo4 { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); try { Thread.sleep(10); } catch (InterruptedException e) { } // 切換標記,之前,讓主線程停一會,這時就只有一個t1線程在,它就會執行同步代碼塊。 t.flag = false; t2.start(); } }?
?
1.2 靜態方法
static 同步函數,使用的鎖不是this,而是字節碼文件對象, 類名.class?
class Ticket implements Runnable {private static int tickets = 100;private Object obj = new Object();boolean flag = true;public void run(){if(flag){ while(true){ synchronized(Ticket.class){ if(tickets>0){ try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印線程名稱。 } } } } else{ while(true){ this.sale(); } } } public static synchronized void sale()// { if(tickets>0) { try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印線程名稱。 } } } class ThreadDemo5 { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); try{Thread.sleep(10);}catch(InterruptedException e){} //切換標記,之前,讓主線程停一會,這時就只有一個t1線程在,它就會執行同步代碼塊。 t.flag = false; t2.start(); } }?
二 單例的安全問題
//餓漢式。 多線程并發餓漢式沒問題。 class Single {private static final Single s = new Single();private Single(){}public static Single getInstance(){return s;} }//懶漢式。 class Single {private static Single s = null;private Single(){}/*并發訪問會有安全隱患,所以加入同步機制解決安全問題。但是,同步的出現降低了效率。可以通過雙重判斷的方式,解決效率問題,減少判斷鎖的次數。重在分析
*/public static Single getInstance(){if(s==null){synchronized(Single.class){if(s==null)// -->0 -->1s = new Single();}}return s;} }class Demo implements Runnable {public void run(){Single.getInstance();} }class ThreadDemo6 {public static void main(String[] args) {System.out.println("Hello World!");} }
?三 死鎖
同步的另一個弊端:
情況之一:當線程任務中出現了多個同步(多個鎖)時,如果同步中嵌套了其他的同步。
這時容易引發一種現象:死鎖。
這種情況能避免就避免掉。
例:一碗飯.一個人拿一個筷子,誰也不放.
//Thread-0 synchronized(obj1) { -->thread-0 obj1 synchronized(obj2) {}} //Thread-1 synchronized(obj2) { Thread-1 obj2 synchronized(obj1) {}}?例如
class Ticket implements Runnable { private int tickets = 100; private Object obj = new Object(); boolean flag = true; public void run() { if(flag){ while(true){ synchronized(obj){ sale();//this lock; } } } else{ while(true){ this.sale(); } } }public synchronized void sale()//this lock { synchronized(obj)//obj lock { if(tickets>0) { try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印線程名稱。 } } } } class ThreadDemo7 { public static void main(String[] args) { Ticket t = new Ticket();Thread t1 = new Thread(t); Thread t2 = new Thread(t);t1.start(); try{Thread.sleep(10);}catch(InterruptedException e){} //切換標記,之前,讓主線程停一會,這時就只有一個t1線程在,它就會執行同步代碼塊。 t.flag = false;t2.start(); } }?又例如
class Test implements Runnable {private boolean flag;Test(boolean flag){this.flag = flag;}public void run(){if(flag){while(true){synchronized(MyLock.LOCKA){System.out.println(Thread.currentThread().getName()+"...if......locka");synchronized(MyLock.LOCKB){System.out.println(Thread.currentThread().getName()+"...if......lockb");}}}}else{while(true){synchronized(MyLock.LOCKB){System.out.println(Thread.currentThread().getName()+"...else......lockb");synchronized(MyLock.LOCKA){System.out.println(Thread.currentThread().getName()+"...else......locka");}}}}} } //定義一個用于存儲鎖對象類。 class MyLock {public static final Object LOCKA = new Object();public static final Object LOCKB = new Object(); }class DeadLockTest {public static void main(String[] args) {//創建兩個線程任務。Test t1 = new Test(true);Test t2 = new Test(false);Thread t11 = new Thread(t1);Thread t22 = new Thread(t2);t11.start();t22.start();} }?
四 生產者和消費者
多線程中最為常見的應用案例:
生產者消費者問題。
生產和消費同時執行,需要多線程。
但是執行的任務卻不相同,處理的資源確實相同的:線程間的通信。
1,描述一下資源。
2,描述生產者,因為具備著自己的任務。
3,描述消費者,因為具備著自己的任務。
問題1:數據錯誤:已經被生產很早期的商品,才被消費到。
出現線程安全問題,加入了同步解決。使用同步函數。
問題已解決:不會在消費到之前很早期的商品。
問題2:發現了連續生產卻沒有消費,同時對同一個商品進行多次消費。
希望的結果應該是生產一個商品,就被消費掉。生產下一個商品。
搞清楚幾個問題?
生產者什么時候生產呢?消費者什么時候應該消費呢?
當盤子中沒有面包時,就生產,如果有了面包,就不要生產。
當盤子中已有面包時,就消費,如果沒有面包,就不要消費。
?
問題1:?已經被生產很早期的商品,才被消費到。
出現線程安全問題,加入了同步解決。使用同步函數。
問題已解決:不會在消費到之前很早期的商品。
?
?
問題2:發現了連續生產卻沒有消費,同時對同一個商品進行多次消費。
希望的結果應該是生產一個商品,就被消費掉。生產下一個商品。
搞清楚幾個問題?
生產者什么時候生產呢?消費者什么時候應該消費呢?
當盤子中沒有面包時,就生產,如果有了面包,就不要生產。
當盤子中已有面包時,就消費,如果沒有面包,就不要消費。
?
生產者生產了商品后應該告訴消費者來消費。這時的生產者應該處于等待狀態。
消費者消費了商品后,應該告訴生產者,這時消費者處于等待狀態。
等待:wait();
告訴:notify();//喚醒
問題解決:實現生產一個消費一個。
4.1 等待/喚醒機制
wait(): 會讓線程處于等待狀態,其實就是將線程臨時存儲到了線程池中。
notify():會喚醒線程池中任意一個等待的線程。
notifyAll():會喚醒線程池中所有的等待線程。
記住:這些方法必須使用在同步中,因為必須要標識wait,notify等方法所屬的鎖。
同一個鎖上的notify,只能喚醒該鎖上的被wait的線程。
例:等待吃飯
還有銀行?
為什么這些方法定義在Object類中呢?
因為這些方法必須標識所屬的鎖,而鎖可以是任意對象,任意對象可以調用的方法必然時Object類中的方法。
舉例:小朋友抓人游戲
/1,描述資源。屬性:商品名稱和編號, 行為:對商品名稱賦值,獲取商品。 class Resource {private String name;private int count = 1;//定義標記。private boolean flag = false;//1,提供設置的方法。public synchronized void set(String name){if(flag)try{this.wait();}catch(InterruptedException e){}//給成員變量賦值并加上編號。this.name = name + count;//編號自增。count++;//打印生產了哪個商品。System.out.println(Thread.currentThread().getName()+"......生產者...."+this.name);//將標記改為true。flag = true;//喚醒消費者。this.notify();}public synchronized void out(){if(!flag)try{this.wait();}catch(InterruptedException e){}System.out.println(Thread.currentThread().getName()+"....消費者...."+this.name);//將標記該為false。flag = false;//喚醒生產者。this.notify();} }//2,描述生產者。 class Producer implements Runnable {private Resource r ;// 生產者一初始化就要有資源,需要將資源傳遞到構造函數中。 Producer(Resource r){this.r = r;}public void run(){while(true){r.set("面包");}} }//3,描述消費者。 class Consumer implements Runnable {private Resource r ;// 消費者一初始化就要有資源,需要將資源傳遞到構造函數中。 Consumer(Resource r){this.r = r;}public void run(){while(true){r.out();}} }class ThreadDemo9 {public static void main(String[] args) {//1,創建資源對象。Resource r = new Resource();//2,創建線程任務。Producer pro = new Producer(r);Consumer con = new Consumer(r);//3,創建線程。Thread t1 = new Thread(pro);Thread t2 = new Thread(con);t1.start();t2.start();} }?五 多生產和多消費
?加入多生產多消費
public static void main(String[] args) {//1,創建資源對象。Resource r = new Resource();//2,創建線程任務。Producer pro = new Producer(r);Consumer con = new Consumer(r);//3,創建線程。Thread t1 = new Thread(pro);Thread t2 = new Thread(pro);Thread t3 = new Thread(con);Thread t4 = new Thread(con);t1.start();t2.start();t3.start();t4.start();}?
?
?
問題1?生產了商品沒有被消費,同一個商品被消費多次。
Thread-2......生產者....面包40527//沒有被消費。
Thread-3......消費者....面包40528
Thread-2....消費者....面包40528
分析過程
?
?
被喚醒的線程沒有判斷標記,造成問題1的產生。
解決:只要讓被喚醒的線程必須判斷標記就可以了。將if判斷標記的方式改為while判斷標記。記住:多生產多消費,必須時while判斷條件。
?
class Resource {private String name;private int count = 1;//定義標記。private boolean flag = false;//1,提供設置的方法。public synchronized void set(String name)// {while(flag) //try{this.wait();}catch(InterruptedException e){}// t1等 t2等//給成員變量賦值并加上編號。this.name = name + count;//商品1 商品2 商品3//編號自增。count++;//2 3 4//打印生產了哪個商品。System.out.println(Thread.currentThread().getName()+"......生產者...."+this.name);//生產 商品1 生產商品2 生產商品3//將標記改為true。flag = true;//喚醒消費者。this.notify();}public synchronized void out()// {while(!flag)//try{this.wait();}catch(InterruptedException e){}//t3等 //t4等System.out.println(Thread.currentThread().getName()+"....消費者...."+this.name);//消費 商品1//將標記該為false。flag = false;//喚醒生產者。this.notify();} }//2,描述生產者。 class Producer implements Runnable {private Resource r ;// 生產者一初始化就要有資源,需要將資源傳遞到構造函數中。 Producer(Resource r){this.r = r;}public void run(){while(true){r.set("面包");}} }//3,描述消費者。 class Consumer implements Runnable {private Resource r ;// 消費者一初始化就要有資源,需要將資源傳遞到構造函數中。 Consumer(Resource r){this.r = r;}public void run(){while(true){r.out();}} }class ThreadDemo10 {public static void main(String[] args) {//1,創建資源對象。Resource r = new Resource();//2,創建線程任務。Producer pro = new Producer(r);Consumer con = new Consumer(r);//3,創建線程。Thread t1 = new Thread(pro);Thread t2 = new Thread(pro);Thread t3 = new Thread(con);Thread t4 = new Thread(con);t1.start();t2.start();t3.start();t4.start();} }?
?
問題2:發現while判斷后,死鎖了。
原因:生產方喚醒了線程池中生產方的線程。本方喚醒了本方。
解決:希望本方要喚醒對方,沒有對應的方法,所以只能喚醒所有。
?
?六 Condition
Condition/*
jdk1.5以后提供多生產多消費的解決方案。
在java.util.concurrent.locks 軟件包中提供相應的解決方案
Lock接口:比同步更厲害,有更多操作。lock():獲取鎖 unlock():釋放鎖;
提供了一個更加面對對象的鎖,在該鎖中提供了更多的顯示的鎖操作。
替代同步。
升級到JDK1.5,先把同步改成 Lock。
已經將舊鎖替換成新鎖,那么鎖上的監視器方法(wait,notify,notifyAll)也應該替換成新鎖的監視器方法。
而jdk1.5中將這些原有的監視器方法封裝到了一個Condition對象中。
想要獲取監視器方法,需要先獲取Condition對象。
Condition對象的出現其實就是替代了Object中的監視器方法。
await();
signal();
signalAll();
將所有的監視器方法替換成了Condition。
功能和ThreadDemo10.java老程序的功能一樣,僅僅是用新的對象。改了寫法而已。
但是問題依舊;效率還是低。
希望本方可以喚醒對方中的一個。
老程序中可以通過兩個鎖嵌套完成,但是容易引發死鎖。
新程序中,就可以解決這個問題,只用一個鎖,
可以在一個鎖上加上多個監視器對象。
*/
?
轉載于:https://www.cnblogs.com/liu-wang/p/8284664.html
總結
以上是生活随笔為你收集整理的java-多线程安全-锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通过lseek产生空洞文件
- 下一篇: html 使用button调用函数