一、多線程
/*
線程由兩種實作方式:
第一種方式:
class MyThread extends Thread{
public void run(){
需要進行執行的代碼,如循環。
}
}
public class TestThread{
public static void main(String[] args){
Thread t1=new Mythread();
T1.start();
}
}
隻有等到所有的線程全部結束之後,程序才退出。
第二種方式:
Class MyThread implements Runnable{
Public void run(){
Runnable target=new MyThread();
Thread t3=new Thread(target);
Thread.start();//啟動線程
}
}
線程中的7種狀态:開始,可運作,運作,阻塞(鎖死,等待隊列),結束,
(有的書上也隻有認為前五種狀态:而将“鎖池”和“等待隊列”都看成是“阻塞”狀态的特殊情況:
這種認識也是正确的,但是将“鎖池”和“等待隊列”單獨分離出來有利于對程式的了解)
注意:圖中标記依次為
①輸入完畢; ②wake up; ③t1退出
⑴如等待輸入(輸入裝置進行處理,而CUP不處理),則放入阻塞,直到輸入完畢。
⑵線程休眠sleep()
⑶t1.join()指停止main(),然後在某段時間内将t1加入運作隊列,直到t1退出,main()才結束。
特别注意:①②③與⑴⑵⑶是一一對應的。
線程間通信:
其實就是多個線程在操作同一個資源,但是操作的動作不同。
等待喚醒機制:
wait();notify();notifyAll();都使用在同步中,因為要對持有螢幕(鎖)的線程操作。
是以要使用在同步中,因為隻有同步才有鎖。
為什麼這些操作線程的方法要定義在object類中呢?
因為這些方法在操作同步中線程時,都必須要标記他們所操作線程隻有的鎖
不可以對不同鎖中的線程進行喚醒。
也就是說,等待和喚醒彼此是同一個鎖。
而鎖可以是任意對象,是以可以被任意對象調用的方法定義在object中。
生産者消費者模式:
對于多個生産者和消費者,為什麼要用while判斷标記:
原因:讓被喚醒的線程再一次判斷标記。
為什麼定義notifyAll?
因為需要喚醒對方線程。如果隻用notify,容易出現隻喚醒本方線程,導緻
程式中的所有線程都在等待。
JDK1.5中提供了多線程更新解決方案:
将同步synchronized替換成現實lock操作。
将object中的wait,notify,notifyAll替換成condition對象。
該對象可以lock鎖,進行擷取。
在該示例中,實作了本方隻喚醒對方的操作。
釋放鎖的動作一定要執行,放在finally裡。
停止線程:
1、定義循環結束标記:
因為線程運作代碼一般都是在循環,隻要控制了循環即可。
2、使用interrupt(中斷)方法
該方法是結束線程的當機狀态,使線程回到運作狀态中來。
注:stop方法已經過時,不再使用。
stop方法已經過時,如何停止線程?
隻有一種,讓run方法結束。開啟多線程運作,運作代碼通常是循環結構,
隻要控制住循環,就可以讓run方法結束,也就是線程結束。
特殊情況:當線程處于當機狀态,就不會讀取标記,那麼線程不會結束。
當沒有指定的方式讓當機的線程恢複到運作狀态時,這時就需要對當機進行清除。
強制讓線程恢複到運作狀态中來,這樣就可以操作标記讓線程結束。
Thread類中提供了interrupt();方法,可以讓線程恢複到運作狀态。
守護線程:(setDemo();)
将線程标記為守護線程時(在開啟線程前開啟),
當正在運作的線程都是守護線程時(被守護線程已經結束),
JVM自動退出(守護線程沒有存在的價值,自動關閉線程)。
join();方法:
當A線程執行到B線程的.join()方法時,A就會等待,等B線程都執行完後,A才會執行。
join可以用來臨時加入線程執行。
鎖池:
線程因為未拿到鎖标記而發生的阻塞不同于前面五個基本狀态中的阻塞,稱為鎖池。
每個對象都有自己的一個鎖池的空間,用于放置等待運作的線程。這些線程中哪個線程拿到鎖标記由系統決定。
死鎖:
鎖标記如果過多,就會出現線程等待其他線程釋放鎖标記,而又都不釋放自己的鎖标記供其他線程運作的狀況。就是死鎖。
死鎖的問題通過線程間的通信的方式進行解決。線程間通信機制實際上也就是協調機制。
線程間通信使用的空間稱之為對象的等待隊列,則個隊列也是屬于對象的空間的。
線程的實作方式(implements Runnable)和繼承方式(extends Thread)的差別?
1、實作方式的好處:避免單繼承的局限性,在定義線程時,建議使用實作方式。
2、差別:繼承Thread線程代碼存放在Thread子類的run方法中,
而實作Runnable線程代碼存放在接口子類的run方法中。
同步的前提:
1、必須要有兩個或兩個以上的線程。
2、必須是多個線程使用同一個鎖。
好處:
解決多線程的安全問題。
弊端:
多線程需要判斷鎖,較為消耗資源。
如果同步函數被靜态修飾後,使用是鎖是什麼?
通過驗證,發現不少this,因為靜态沒有this,靜态進記憶體中,記憶體沒有本類對象,但是一定有該類對應的位元組碼檔案對象。
即類名.class。該對象的類型是class。靜态的同步方法,使用的鎖就是類名.class。
*/
二、單例設計模式:
package sonyi;
/*
//單例設計模式:
*/
public class SingleDemo {
public static void main(String[] args) {
Single s = Single.getInstance();
}
}
//餓漢式:執行個體同時加載
/*
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(){
//雙重判斷,在多線程中,隻要有建立一個對象之後,之後進來的線程直接判斷條件(s == null),
//而減少了判斷同步鎖,提高了效率。同步鎖保證了對象的唯一性。
if(s == null){
synchronized (Single.class) {//靜态中不可以用this作為鎖,而是用該類的位元組碼對象作為鎖
if(s == null)
s = new Single();
}
}
return s;
}
}
三、死鎖
package sonyi;
/*
死鎖:一般是同步中嵌套同步
鎖标記如果過多,就會出現線程等待其他線程釋放鎖标記,而又都不釋放自己的鎖标記供其他線程運作的狀況。就是死鎖。
死鎖的問題通過線程間的通信的方式進行解決。線程間通信機制實際上也就是協調機制。
線程間通信使用的空間稱之為對象的等待隊列,則個隊列也是屬于對象的空間的。
*/
public class DeadLockDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
class Test implements Runnable{
private boolean flag;
public Test(boolean flag) {
this.flag = flag;
}
public void run(){
if(flag){
synchronized (MyLock.locka) {
System.out.println("if locka");
synchronized (MyLock.lockb) {//拿着a鎖要b鎖。
System.out.println("if lockb");
}
}
}
else {
synchronized (MyLock.lockb) {
System.out.println("else lockb");
synchronized (MyLock.locka) {//拿着b鎖要a鎖。
System.out.println("else locka");
}
}
}
}
}
//建立兩個鎖
class MyLock{
static Object locka = new Object();
static Object lockb = new Object();
}