昨天重新學(xué)習(xí)了java多線程的使用,多線程的難點(diǎn)就在線程之間的協(xié)調(diào)。在《操作系統(tǒng)》一課中,我們學(xué)習(xí)了進(jìn)程,其實(shí)多線程和多進(jìn)程一樣,都會(huì)涉及到多個(gè)進(jìn)程或者線程對(duì)某一資源共享訪問(wèn)的問(wèn)題,當(dāng)多個(gè)線程都需要修改這個(gè)資源的時(shí)候就會(huì)出現(xiàn)線程安全問(wèn)題。
比如說(shuō)在銀行開(kāi)個(gè)賬戶會(huì)有一個(gè)存折和一張卡,如果某一天同一時(shí)間丈夫拿著存折去柜臺(tái)取錢,而妻子拿著銀行卡去ATM取錢。當(dāng)丈夫查詢余額里面有3000元,正準(zhǔn)備取2000元,這時(shí)候妻子也到ATM里面查詢也有3000,也取2000元。其實(shí)銀行不可能讓我們這么做,如果這樣的話那我們天天取錢去了,還搞什么工作啊。其實(shí)在丈夫查詢的時(shí)候已經(jīng)對(duì)該賬號(hào)上了鎖,另外的銀行卡要取錢的話必須等待該鎖被釋放。下面用一個(gè)程序模擬這個(gè)例子:
1 package com.sync;
2
3 public class TestSync2 implements Runnable{
4 public BankCard bc = new BankCard();
5 public static void main(String[] args) {
6 TestSync2 test = new TestSync2();
7 Thread wife = new Thread(test);
8 Thread husband = new Thread(test);
9 wife.setName("wife");
10 husband.setName("husband");
11 wife.start();
12 husband.start();
13 }
14 public void run() {
15 bc.getMoney(Thread.currentThread().getName(), 2000);
16 }
17 }
18 class BankCard{
19 PRivate static int money = 3000;//模擬賬戶余額
20 public synchronized void getMoney(String name,int m){
21 //synchronized(this){
22 try {
23 Thread.sleep(1);
24 } catch (InterruptedException e) {
25 e.printStackTrace();
26 }
27 if(money > m){
28 System.out.println(name+"取走了"+m+"元");
29 money = money - m;
30 }else{
31 System.out.println("對(duì)不起,您的余額不足!");
32 }
33 //}
34 }
35 }
上面的例子如果在getMoney()方法上面不加synchronized關(guān)鍵字的話,輸出結(jié)果為:
wife取走了2000元
husband取走了2000元
而加上synchronized后,輸出結(jié)果為:
wife取走了2000元
對(duì)不起,您的余額不足!
上面兩種情況說(shuō)明,如果多個(gè)線程同時(shí)訪問(wèn)某個(gè)資源,而不給該資源枷鎖的話,就會(huì)出現(xiàn)問(wèn)題。而加上synchronized關(guān)鍵字后就可以避免這種錯(cuò)誤發(fā)生了。它能夠保證只有一個(gè)線程能夠訪問(wèn)getMoney()這個(gè)方法,其他藥訪問(wèn)該方法的線程必須等待。
鎖住某個(gè)資源可以用synchronized關(guān)鍵字來(lái)修飾一個(gè)方法或者同步代碼塊,這樣能保證同一時(shí)間只能由一個(gè)線程訪問(wèn)該資源。
①、當(dāng)兩個(gè)并發(fā)線程訪問(wèn)同一個(gè)對(duì)象object中的這個(gè)synchronized(this)同步代碼塊時(shí),一個(gè)時(shí)間內(nèi)只能有一個(gè)線程得到執(zhí)行。另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。
②、然而,當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問(wèn)該object中的非synchronized(this)同步代碼塊。
③、尤其關(guān)鍵的是,當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對(duì)object中所有其它synchronized(this)同步代碼塊的訪問(wèn)將被阻塞。
我們都知道,操作系統(tǒng)中多個(gè)進(jìn)程之間如果不進(jìn)行協(xié)調(diào)就很容易出現(xiàn)死鎖的情況,死鎖的四個(gè)條件:互斥、占有等待、非剝奪、循環(huán)等待。我們只要破壞其中一個(gè)條件就能避免死鎖發(fā)生。線程之間也容易出現(xiàn)死鎖,下面這個(gè)例子就演示了死鎖的情況:
1 package com.sync;
2
3 import com.thread.SleepTest;
4
5
6 public class TestDeadLock implements Runnable{
7 int flag = 1;
8 static Object o1 = new Object();
9 static Object o2 = new Object();
10 public void run() {
11 System.out.println(flag);
12 if(flag == 1){
13 synchronized (o1) {
14 try {
15 Thread.sleep(1000);
16 } catch (InterruptedException e) {
17 e.printStackTrace();
18 }
19 synchronized (o2) {
20 System.out.println("1");
21 }
22 }
23 }
24 if(flag == 0){
25 synchronized (o2) {//鎖住某個(gè)對(duì)象,相當(dāng)于占有該對(duì)象不讓其他人使用
26 try {
27 Thread.sleep(1000);
28 } catch (InterruptedException e) {
29 e.printStackTrace();
30 }
31 synchronized (o1) {
32 System.out.println("0");
33 }
34 }
35 }
36 }
37 public static void main(String[] args) {
38 TestDeadLock t1 = new TestDeadLock();
39 TestDeadLock t2 = new TestDeadLock();
40 t1.flag = 1;
41 t2.flag = 0;
42 new Thread(t1).start();
43 new Thread(t2).start();
44 }
45 }
運(yùn)行程序輸出1 0后就進(jìn)入死鎖狀態(tài),該程序永遠(yuǎn)也不會(huì)停止,因?yàn)閮蓚€(gè)線程同時(shí)處于等待狀態(tài)。線程t1鎖住了o1對(duì)象,等待o2對(duì)象,而線程t2鎖住o2等待o2對(duì)象,誰(shuí)也不讓誰(shuí),這就進(jìn)入了一個(gè)循環(huán)占有等待的情況了,死鎖也就出現(xiàn)了。
所以,如果多個(gè)線程如果不進(jìn)行協(xié)調(diào)的話很容易出現(xiàn)死鎖的問(wèn)題。操作系統(tǒng)中使用進(jìn)程調(diào)度來(lái)協(xié)調(diào)各個(gè)進(jìn)程,那么java重如何對(duì)各個(gè)線程進(jìn)行協(xié)調(diào)呢?
java中主要使用Object類中的wait()、notify()、notifyAll()方法來(lái)協(xié)調(diào)各個(gè)線程。典型的例子有哲學(xué)家吃飯問(wèn)題、生產(chǎn)者和消費(fèi)者問(wèn)題、理發(fā)師問(wèn)題。下面一個(gè)用一個(gè)例子來(lái)演示生產(chǎn)者和消費(fèi)者問(wèn)題。
問(wèn)題描述:生產(chǎn)者負(fù)責(zé)做饅頭,做好饅頭后放進(jìn)指定的簍子里面,消費(fèi)者消費(fèi)該簍子里面的饅頭。簍子里只能裝一定量的饅頭,滿了以后生產(chǎn)者必須進(jìn)入等待狀態(tài),消費(fèi)者吃完饅頭后也必須進(jìn)入等待狀態(tài)。
1 package com.sync;
2
3 public class ProductAndConsumer {
4 public static void main(String[] args) {
5 Basket b = new Basket();
6 Product p = new Product(b);
7 Consumer c = new Consumer(b);
8 new Thread(p).start();
9 new Thread(c).start();
10 }
11 }
12
13 class ManTou{
14 int id;
15 public ManTou(int id) {
16 this.id = id;
17 }
18 @Override
19 public String toString() {
20 return "ManTou"+id;
21 }
22 }
23
24 //裝饅頭的籃子
25 class Basket{
26 int index = 0; //相當(dāng)于棧頂指針
27 ManTou[] manTous = new ManTou[6];
28 //往籃子里面放饅頭
29 public synchronized void push(ManTou m){
30 while(index == manTous.length){
31 try {
32 this.wait();
33 } catch (InterruptedException e) {
34 e.printStackTrace();
35 }
36 }
37 this.notify();
38 manTous[index] = m;
39 index++;
40 }
41 //往籃子里面取饅頭
42 public synchronized ManTou pop(){
43 while(index == 0){
44 try {
45 this.wait();
46 } catch (InterruptedException e) {
47 e.printStackTrace();
48 }
49 }
50 this.notify();
51 index--;
52 return manTous[index];
53 }
54 }
55 //生產(chǎn)者
56 class Product implements Runnable{
57 Basket basket;
58 public Product(Basket basket) {
59 this.basket = basket;
60 }
61 public void run() {
62 for (int i = 0; i < 20; i++) {
63 ManTou m = new ManTou(i);
64 basket.push(m);
65 System.out.println("生產(chǎn)了"+m);
66 try {
67 Thread.sleep(1);
68 } catch (InterruptedException e) {
69 e.printStackTrace();
70 }
71
72 }
73 }
74 }
75
76 //消費(fèi)者
77 class Consumer implements Runnable{
78 Basket basket;
79 public Consumer(Basket basket) {
80 this.basket = basket;
81 }
82 public void run() {
83 for (int i = 0; i < 20; i++) {
84 ManTou m = basket.pop();
85 System.out.println("消費(fèi)了"+m);
86 try {
87 Thread.sleep((int)(Math.random()*1000));
88 } catch (InterruptedException e) {
89 e.printStackTrace();
90 }
91 }
92 }
93 }
wait()、notify()、notifyAll()方法的作用:
wait():導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 方法。
notify():喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線程。
notifyAll():喚醒在此對(duì)象監(jiān)視器上等待的所有線程。
wait()與sleep()的區(qū)別:
兩個(gè)方法的共同點(diǎn)就是讓當(dāng)前線程進(jìn)入等待狀態(tài)。
不同點(diǎn):
wait()之后,鎖就不歸我所有了,必須等醒過(guò)來(lái)后才能擁有該鎖,并且必須要有人喚醒它才會(huì)醒過(guò)來(lái)
sleep()不同,鎖還是歸我所有,一段時(shí)間后會(huì)自動(dòng)醒過(guò)來(lái)
|
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注