多線程學下來之後,感覺還是挺暈的,但是還好,能聽的懂。。。
不過重要的還是要做一下學習筆記,那接下來我們又學習了多線程裡面更加複雜深層次的一些東西,經過一番學習,對多線程裡面的部分知識點我都給他叫上一個小名:
線程間通信等待喚醒機制-->小時候玩的遊戲 ”冰雪融化”。
同步鎖-->”随用随借,用完就還”
死鎖-->”進不來出不去”
1. 同步鎖的出現:
什麼叫同步鎖?
鎖定是控制多個線程對共享資源進行通路的工具。通常,鎖定提供了對共享資源的獨占通路。一次隻能有一個線程獲得鎖定,對共享資源的所有通路都需要首先獲得鎖定。
前面我們學過因為多線程的存在,這樣難免存在一個共有資源被多個線程搶占使用的情形,這就導緻了共享資料正确同步的問題,比如說我們多個視窗賣票,我們怎麼可能接受買到票号為0的票,或者票号為負數的票,這裡面就存在一個多線程資源搶占帶來一個資料同步和正确性的問題,這時候才出現一個鎖,這個鎖可以保證在某一段時間内隻有一個線程在使用這個共有資源,待這個線程使用完這個資源之後将鎖歸還,然後和之前其他被鎖阻塞在外的線程再次加入搶鎖,使用資源的隊列中,那麼java中的這個鎖叫做同步鎖,用synchronized關鍵字了修飾定義。
然後我們來寫一段代碼定義幾個線程并來使用同步代碼塊鎖:
/*
下面這段代碼使用來實作兩個視窗同時共售票100張票,也就說賣一張少一張,而且不能出現100之外的其他任何票号
*/
packagecom.threadDemo;
public classSaleTicketDemo
{
public static voidmain(String[] args)
{
//建立兩個線程并啟動,兩個線程操作的都是一個對象的run()方法。
SaleTicket st = newSaleTicket();
Threadt1 = newThread(st);
Threadt2 = newThread(st);
t1.start();
t2.start();
}
}
class SaleTicket implements Runnable
{
//聲明總票數
private int ticket = 100;
//用來作為同步塊的鎖作為同步塊的參數傳遞,同步塊的鎖其實可以任意傳//入一個對象即可,能當作唯一性的鎖就可以了,這個我也不知道為什麼。
Object obj = new Object();
@Override
public void run()
{
while(true)
{
/*
在While循環内加入sychronized塊,并将涉及到操作共有資料ticket變量的代碼加入到同步塊中,還要将o這個對象作為鎖作為參數傳遞給同步塊。是以當一個線程執行到這後,拿到鎖,進入之後,就自動加鎖了,其他的線程無法進入該同步塊,因為它沒鎖,待線程執行完後,将鎖歸還,其他的線程//才有機會搶到鎖進入執行同步塊中的語句了,那麼這裡鎖就是這個o對象。
*/
synchronized(obj)
{
if(ticket >0)
{
try{ Thread.sleep(10);}catch(Exception e){};
System.out.println(Thread.currentThread().getName()+"<<>>>"+ticket--);
}
}
}
}
}
執行結果如下:
Thread-0<<>>>100
Thread-0<<>>>99
Thread-0<<>>>98
……
Thread-1<<>>>96
Thread-1<<>>>95
Thread-1<<>>>94
……
Thread-1<<>>>72
Thread-0<<>>>71
Thread-0<<>>>70
……
Thread-0<<>>>63
……
Thread-0<<>>>1
是以我們發現程式正确的完成了我們想要的功能,沒有出現0号票和負數票,而且是交替的在執行。
假設以上同步塊拿掉之後,執行結果會是怎麼樣呢?發現這個同步塊拿掉之後,列印的結果是兩個線程在交替執行,但是卻出現了0号票。
為什麼會出現零号票呢?我們分析是不是會出現這樣一種情況呢,假設現在ticket值為1,0線程先進來,進來之後執行了sleep,然後1線程又進來了,此時0線程sleep時間已過,執行了ticket--,然後線程1也醒過來了,執行了ticket--,那麼這時候是不是列印了0号票呢?是以沒有同步鎖的存在就發生了線程安全性問題。
2. 靜态同步函數的鎖和非靜态同步函數的鎖
靜态同步函數:簡單了解就是在一個類中靜态方法上使用synchronized修飾的方法,非靜态就不多說了,當然是修飾在非靜态的執行個體方法上面。
我們來通過一段代碼來區分非靜态函數和靜态函數的差別,以及它們線上程執行到該同步函數的時候,使用的鎖到底是哪一個。
packagecom.threadDemo;
public classSaleTicketDemo
{
public static voidmain(String[] args)
{
SaleTicketst = newSaleTicket();
Threadt1 = newThread(st);
Threadt2 = newThread(st);
t1.start();
try{ Thread.sleep(10);}catch(Exception e){};
st.flag = false;
t2.start();
}
}
class SaleTicket implements Runnable
{
private int ticket =100;
boolean flag = true;
@Override
public void run()
{
if(flag)
{
while(true)
{
//這是一個普通的同步塊,使用鎖是this,this關鍵之隻能在非靜态的方法中使用
//原因待會再講
synchronized(this)
{
if(ticket >0)
{
try{ Thread.sleep(10);}catch(Exceptione){};
System.out.println(Thread.currentThread().getName()+"<<>>>"+ticket--);
}
}
}
}
else
{
while(true)
{
show();
}
}
}
//在非靜态方法上使用synchronized修飾,this表示目前對象,建立了一個非靜态
//同步函數鎖
public synchronized void show()
{
if(ticket >0)
{
try{ Thread.sleep(10);}catch(Exception e){};
System.out.println(Thread.currentThread().getName()+"[[[[]]]]"+ticket--);
}
}
}
執行結果:
Thread-0<<>>>100
Thread-0<<>>>99
Thread-0<<>>>98
Thread-0<<>>>97
……
Thread-0<<>>>85
Thread-0<<>>>84
……
Thread-1[[[[]]]]83
Thread-1[[[[]]]]1
是以從以上的執行結果可以看的出,結果中沒有出現不理想的票号,這就說明非靜态同步函數和同步塊使用的是一個鎖,因隻有一個鎖才能保證共享資料的同步,也就是說隻有一個鎖,某一段時間隻有持鎖的線程在操作資料,操作完之後其他的線程才有機會拿到鎖去執行,大家看得到我在同步塊中傳入的鎖是this,是以得知非靜态同步函數中使用的鎖也是this。
在來看一段代碼,我們在看看靜态代碼塊又是怎麼一回事呢?
packagecom.threadDemo;
public classSaleTicketDemo
{
public static voidmain(String[] args)
{
SaleTicketst = newSaleTicket();
Threadt1 = newThread(st);
Threadt2 = newThread(st);
t1.start();
try{ Thread.sleep(10);}catch(Exception e){};
st.flag = false;
t2.start();
}
}
class SaleTicket implements Runnable
{
//要在靜态方法中使用成員變量,就要将ticket使用static修飾
private static int ticket = 100;
boolean flag = true;
@Override
public void run()
{
if(flag)
{
while(true)
{
//這裡還是傳入this鎖
synchronized(this)
{
if(ticket > 0)
{
try{ Thread.sleep(10);}catch(Exception e){};
System.out.println(Thread.currentThread().getName()+"<<>>>"+ticket--);
}
}
}
}
else
{
while(true)
{
show();
}
}
}
//這個方法改為了static修飾,并且加上synchronized,這樣就建立一個//靜态同步鎖。
public static synchronized void show()
{
if(ticket > 0)
{
try{ Thread.sleep(10);}catch(Exception e){};
System.out.println(Thread.currentThread().getName()+"[[[[]]]]"+ticket--);
}
}
}
執行結果:
Thread-0<<>>>100
Thread-1[[[[]]]]99
Thread-0<<>>>98
Thread-1[[[[]]]]97
Thread-0<<>>>96
Thread-1[[[[]]]]95
Thread-0<<>>>94
……
Thread-0<<>>>1
Thread-1[[[[]]]]0
是以從結果看得出,結果發生了變化,不良的票号終于出現了,0票号來了
,是以肯定是出現兩個鎖,導緻兩個線程都使用各自的鎖,最終發生0票号的情況,而我同步塊中使用的是this鎖,那既然使用的是不同的鎖,那麼靜态同步函數中使用的鎖是哪一個呢?由此我們可以聯想到在靜态的方法中不能引用this,因為靜态方法在被加載的時候也被存放到了記憶體中,而此時沒有對象生成,是以this是不能使用的,那麼此時存在的隻有類的class檔案或者說類的位元組碼檔案對象,是以靜态同步函數使用的鎖是類的位元組碼檔案對象,是類.class。
3. 線程中引發的單利設計模式(懶漢式和餓漢式)
餓漢式:
public class Singler
{
//最好加上final使其指向的對象終生不變
privatestatic final Singler s = new Singler();
privateSingler(){};
publicstatic Singler getInstance()
{ return s; }
}
懶漢式
public class Singler
{
//這裡千萬不能加final,否s的值則始終是null了
private static Singlers = null ;
private Singler()
{
}
public staticSingler getInstance()
{
If(s == null)
{
//倘若這段代碼在被多線程使用,那麼當某個線程執行到if語句内//的話,就被CPU排程或者進入sleep等情況,其他線程進入if語句//内new出了一個Singler對象并傳回給方法調用出,那麼等最開
//的那個線程切換回來的話,又new出來一個新的Singler對象了,//那麼同步鎖就能解決這樣的問題。
s = new Singler();
return s;
}
return s;
}
}
懶漢式同步版本:
解決了以上懶漢式在多線程同時通路static共享資源時對象延遲加載的弊端,使用同步鎖可以解決這個問題,但是加上同步鎖之後就稍微影響了程式的效率,那麼可以加入雙重判斷,來加以緩和。
public class Singler
{
private staticSingler s = null ;
private Singler()
{
}
public staticSingler getInstance()
{ if(s == null)
{
synchronized(Singler.class)
{
If(s == null)
{
s = new Singler();
return s;
}
}
}
return s;
}
}
4. 死鎖
死鎖一般出現在兩個嵌套同步函數或同步塊中,比如說A線程拿着Lock1的鎖,然後卻沒有内層同步塊的鎖Lock2,而另一個線程B拿着Lock2的鎖,卻沒有内層同步塊的鎖Lock1,這樣就導緻的死鎖,導緻程式暫停等現象。
直接看代碼:
package com.thread;
public class DeadLockDemo
{
public static voidmain(String[] args)
{
Thread t1 = new Thread(new DeadLock(true));
Thread t2 = new Thread(new DeadLock(false));
t1.start();
t2.start();
}
}
class DeadLock implements Runnable
{
private boolean flag = false;
public DeadLock( boolean flag)
{
this.flag = flag;
}
@Override
public void run()
{
if(flag)
{
while(true)
{
//在某一時刻某個線程拿到obj1這個鎖,想要進入之後想獲得obj2的鎖,卻發現//這個鎖被另一個線程在使用,即else語句塊中,而此時沒執行完外層的同步塊
//内語句,而裡層同步塊語句因為沒obj2這個鎖,導緻它此時"進不來出不去"
//的局面,進入阻塞狀态等待obj2鑰匙被釋放。
synchronized(Lock.obj1)
{
System.out.println("if synchronizedlock.obj1");
synchronized(Lock.obj2)
{
System.out.println("ifsynchronized lock.obj2");
}
}
}
}
else
{
while(true)
{
// 在某一時刻某個線程拿到obj2這個鎖,想要進入之後想獲得obj1的鎖,卻發//現這個鎖被另一個線程在使用,即if語句塊中,而此時沒執行完外層的同步塊之内語句,而裡
//層的同步塊語句因為沒obj1這個鎖,導緻它此時"進不來出不去"
//的局面,進入阻塞狀态等待obj1鑰匙被釋放。
synchronized(Lock.obj2)
{
System.out.println("elsesynchronized lock.obj2");
synchronized(Lock.obj1)
{
System.out.println("else synchronized lock.obj1");
}
}
}
}
}
}
//定義了兩個鎖
class Lock
{
public static Object obj1 = new Object();
public static Object obj2 = new Object();
}
執行結果:
else synchronized lock.obj2
if synchronized lock.obj1
以上就是一個典型死鎖例子。
5. 線程間的通信的等待和喚醒機制
比如說我們要實作這樣一個功能,有一個勞務所,一個公司人力資源部,一個公司的某個工廠,勞務所給公司人力資源部輸送人力,然後人力資源部又将輸入過來的人力派送到工廠上班,那麼這個功能如果用多線程來實作,要 如何作呢?
首先從上面的這段内容可以抽出三個主要資訊,一個是資源,那麼這裡指的是勞工,另一個是輸送人力的情景,一個派發人力到工廠的情景。
package com.thread;
public class WaitAndNotify
{
public static void main(String[] args)
{
Person p = new Person();
Input in = new Input(p);
Output out = new Output(p);
Thread t1 = new Thread(in);
try{Thread.sleep(10);}catch(Exception e){};
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
/*
* 定義一個人的資源,裡面有人的姓名和性别,已經設定姓名和性别以及獲得姓名和性别的方法。
*/
class Person
{
private String name ;
private String sex;
//定義一個标記,用來标記勞務所不要重複輸送一個相同的人,和人力資源部不//要重複的去派發同一個人到工廠上班,也就是說實作送一個拿一個交替效果
public boolean flag;
public void setName(Stringname)
{
this.name = name ;
}
public void setSex( String sex)
{
this.sex = sex;
}
public StringgetName()
{
return this.name;
}
public String getSex()
{
return this.sex;
}
}
/*
* 接下來要定義兩個線程,一個input 一個output的線程,
* 表示教育訓練機構向公司輸送人力,公司又向集團内的工廠
* 派送人力。
*/
class Input implements Runnable
{
//要對勞務,人這個資源進行操作,就要持有一個Person的引用。
private Person p;
public Input(Person p)
{
this.p = p;
}
@Override
public void run()
{
int x = 1;
while(true)
{
synchronized(p)
{
if(p.flag)
{
//這裡判斷flag是否為true,假如為true表示裡面輸送過去了一個勞務,然後就執//行wait(),交出執行權,讓執行權交給人力派發的一方去派發人到工廠。
try{p.wait();} catch(Exception e){};
}
else
{
//這裡面的方法主要就是簡單的模拟不斷的輸送勞務,這裡就用不斷更換人名和//性别替代
if(x == 0)
{
p.setName("Ansen_zhang");
p.setSex("男");
}
else
{
p.setName("Lily_wang");
p.setSex("女");
}
//當flag不為true的時候,表明勞務已經被派發出去了,要重新輸送人
//過來,當輸送完之後,将标記設為flag,并通知喚醒派發人員去派發。
x = (x+1)%2;
p.flag = true;
p.notify();
}
}
}
}
}
class Output implements Runnable
{
//要對勞務,人這個資源進行操作,就要持有一個Person的引用。
private Person p ;
public Output(Person p)
{
this.p = p;
}
@Override
public void run()
{
while(true)
{
synchronized(p)
{
if(p.flag)
{
//這裡就是簡單的實作往工廠派發人力的過程,這裡就用列印人名和姓名表示。
System.out.println(p.getName() + "<<>>"+p.getSex());
//這裡也是一樣判斷是否為true,假如是true就直接取人并派發,派發完就把flag
//設定為false,喚醒勞務送再去重新整理人力,如果不是true,說明沒有人力更新,就//執行wait(),将執行權交出,并好讓勞務所去重新整理人力。
p.flag = false;
p.notify();
}
else
{
try{p.wait();}catch(Exception e){};
}
}
}
}
}
6. 線程操作的方法如wait()和notify()、notifyAll()等方法為什麼要定義在Object中類中呢?
A. 首先這些方法明确規定,這些方法需要由一個螢幕或者說一個鎖來調用。
B. 而且作用在目前或者其他的使用同一個螢幕的線程上,比如說p.wait()它隻能作用在目前使用p這個螢幕的線程,也就是自身。
C. 是以它的作用是導緻目前的線程等待,直到其他線程調用此對象的
notify()
方法或
notifyAll()
方法,notify
()
喚醒在此對象螢幕上等待的單個線程,notifyAll
()
喚醒在此對象螢幕上等待的所有線程,由此看得出這些方法必須在同一個對象螢幕上(鎖)才能發揮其相應作用。
D. 那定義在Object類中,是因為在聲明同步塊或者同步方法的時候,會設定某一個特定的或者一個任意的對象(對象螢幕),而這些對象又繼承了Object類,是以可以很自然就可以有這個對象的wait()、
notify()
或
notifyAll()
方法
7. 生產者和消費者
//如下定義了一個産品類,産品類有自己的屬性,然後建立了兩個Runnable線
//程類,一個為生産者Producer和消費者Consumer,這兩個類持有了對産品類
//的引用,可以方法和操作産品類對象的屬性,達到生産者在重新整理産品的屬性,//模仿了産品生産的過程,而消費者便可以取得産品的資訊,模仿了産
//品被消費的過程。
packagecom.thread;
public classProducerAndConsumer
{
public static void main(String[] args)
{
Product p = new Product();
Producer pr = new Producer(p);
Consumer cr = new Consumer(p);
Thread t1 = new Thread(pr);
Thread t2 = new Thread(pr);
Thread t3 = new Thread(cr);
Thread t4 = new Thread(cr);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//定一個産品類
class Product
{
private String name;
private int ID = 1;
public boolean flag = false;
public synchronized void setProperty(String name)
{
while(flag)
{
try
{
this.wait();
}
catch(Exception e)
{
};
}
this.name =name+" "+ID++;
System.out.println(Thread.currentThread().getName()+" 生産出 "+this.name);
flag = true;
this.notifyAll();
}
public synchronized void getInfo()
{
//這裡使用Wlile而不是if實用為while會條件會被判斷兩次,不會到一個産品被//消費兩次,或程式挂起的狀況。
while(!flag)
{
try
{
this.wait();
}
catch(Exception e)
{
};
}
System.out.println(Thread.currentThread().getName()+" 消費了 "+this.name+" o(∩_∩)o...哈哈!!!");
flag = false;
//這裡notifyAll()是為了防止之喚醒本對列的線程,卻沒有喚醒對方的線程,最
//終導緻程式都進入wait狀态,程式挂起。
this.notifyAll();
}
}
class Producer implements Runnable
{
private Product p;
public Producer(Product p)
{
this.p = p;
}
@Override
public void run()
{
while(true)
{
p.setProperty("糖果");
}
}
}
class Consumer implements Runnable
{
private Product p ;
publicConsumer(Product p)
{
this.p = p;
}
@Override
public void run()
{
while(true)
{
p.getInfo();
}
}
}
8. If判斷和While技巧
if語句隻會在if語句的條件判斷塊中判斷那一次,而while會在進入while之内還會再做一次判斷,也就是說往一個線程進入while之後立馬給wait()或sleep住了,那麼等它喚醒之後,還會再去判斷一次while循環條件。
9. JDK 5.0 關于線程的新特性Lock 和
Condition
A. 在JDK1.4 之後,即5.0開始出現了Lock接口和Condition接口,它們用來取代了synchronized同步鎖,以及在鎖的基礎上的一些wait(),notify()…方法,它們出現之後不僅實作舊版本的功能之外,而且還使得線程同步使用更加靈活,功能還能更廣泛。
B. Condition接口的對象充當了螢幕(鎖)的角色,通過它可以調用和wait、notify等一樣功能的方法即await()線程等待、signal()、signalAll()喚醒一個或多個被相同螢幕調用await()等方法的線程。
C. 實作了本對列喚醒對方對列的效果。
執行個體代碼:
package com.thread;
importjava.util.concurrent.locks.Condition;
importjava.util.concurrent.locks.ReentrantLock;
public classProducerAndConsumer
{
public static void main(String[] args)
{
//建立四個線程。
Product p = new Product();
Producer pr = new Producer(p);
Consumer cr = new Consumer(p);
Thread t1 = new Thread(pr);
Thread t2 = new Thread(pr);
Thread t3 = new Thread(cr);
Thread t4 = new Thread(cr);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//定一個産品類
class Product
{
private String name;
private int ID = 1;
public boolean flag = false;
//建立一個Lock對象,一個同步鎖
ReentrantLock lock = new ReentrantLock();
//建立第一個Condiditi對象,用來實作螢幕的await(),signal()的方法。
Condition condition1 = lock.newCondition();
//建立第二個Condiditi對象,用來實作螢幕的await(),signal()的方法,
//并且用來本對列喚醒對方對列使用。
Condition condition2 = lock.newCondition();
public void setProperty(String name) throws InterruptedException
{
lock.lock();//獲得鎖,類似之前的synchronized
try
{
while(flag)
{
//使用condition1調用await方法是線程進入等待。
condition1.await();
}
this.name =name+" "+ID++;
System.out.println( Thread.currentThread().getName()+" 生産出 "+this.name);
flag = true;
//喚醒一個被condition2螢幕設定為等待的線程,也就是對方線程。
condition2.signal();
}
finally
{
// 無能如何都要執行釋放鎖的動作,這樣本方或者對方線程才有使用鎖的具備。、//執行資格。
lock.lock();
}
}
public synchronized void getInfo() throwsInterruptedException
{
lock.lock();//獲得鎖,類似之前的synchronized
try
{
while(!flag)
{
//使用condition2螢幕對象調用await方法是線程進入等待。
condition2.await(); }
System.out.println(Thread.currentThread().getName()+" 消費了 "+this.name+" o(∩_∩)o...哈哈!!!");
flag = false;
//喚醒一個被condition1螢幕設定為等待的線程,也就是對方線程。
condition1.signal();
}
finally
{
lock.unlock();// 無能如何都要執行釋放鎖的動作,這樣本方或者對方線程才有使用鎖的具備執行資格。
}
}
}
class Producer implements Runnable
{
private Product p;
public Producer(Product p)
{
this.p = p;
}
@Override
public void run()
{
while(true)
{
try
{
p.setProperty("糖果");
}
catch (InterruptedExceptione)
{
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable
{
private Product p ;
publicConsumer(Product p)
{
this.p = p;
}
@Override
public void run()
{
while(true)
{
try
{
p.getInfo();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
10. interrupted
()
方法
如果線程在調用 Object 類的 wait()、wait(long) 或 wait(long, int) 方法,或者該類的 join()、join(long)、join(long, int)、sleep(long) 或sleep(long, int) 方法過程中受阻,則再調用這個方法就會中使得斷狀态将被清除,它還将收到一個InterruptedException。
11. 停止線程
Tread方法中的stop方法已經過時,不建議被使用,是以當我們要停止一個線程的時候,最好采用循環控制,或條件控制的方式讓線程結束run()方法。
12. 守護線程或使用者線程
setDaemon
(boolean on)
将該線程标記為守護線程或使用者線程。該方法必須在啟動線程前調用。
解釋: 也就是說當一個程式中隻剩下一個或者多個守護程序或者使用者程序時,Java虛拟機會自動退出,也就是這些此線程會随着非守護線程存在在而存在,一旦程式中沒有了非守護程序,或者程式中隻剩下守護程序時程式自動退出。
13. Join()的方法
A. 當A線程執行到B線程的.join()的方法時,A會暫停執行,直到B線程執行完畢,A才會執行或有可能繼續執行。
B. join可以用來臨時加入線程執行。
代碼1:
public static void main(String[] args)
{
Person p = new Person();
Input in = new Input(p);
Output out = new Output(p);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
//t1調用了join方法,當住方法執行到這句話的時候,主程式會停下來,等待t1
//執行完畢,在繼續執行。
t1.join();
t2.start();
}
代碼2:
public static void main(String[]args)
{
Person p = new Person();
Input in = new Input(p);
Output out = new Output(p);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
//t1調用了join方法,當主方法執行到這的時候,線程t1和t2會交替執行,但是此//ain方法會停住,且會等待t1執行完畢之後在恢複執行。
t1.join();
}
14. Yield()方法、
讓一個線程暫停,執行其他的線程….老師說這個方法用的不是太多,而且用起來也比較簡單,就不多說了,要看其他的課程了,寫的不好,請大家擔待,可能隻有在自己看,自己複習的時候才有效果吧,哈哈,對不住了各位。