多線程1(java基礎)

一、什麼是多線程?

1、進程

      進程是指正在運行的程序,但是cpu執行的並不是進程而是線程。

2、線程

      線程是進程內一個相對獨立的、可調度的執行單元或控制單元。操作系統可執行的最小單位是線程。一個進程中至少一個線程。

3、多線程

      線程在操作系統中是可以併發運行的,這樣可以充分利用外圍設備。以java.exe爲例,該進程至少包含兩個線程,一個是執行代碼的線程,另一個是回收和釋放內存的線程。他們是併發執行的,我們稱一個進程中多個線程運行的爲多線程。

4、多線程的意義(多線程與cpu的關係)

      目前我們常用的操作系統爲分時系統,這類操作系統的特點是在極短時間內在不同的程序間切換執行,讓我們看起來就像是很多程序同時運行。而多線程的存在就可以提升cup的切換速度,提高執行效率。這裏需注意的是cpu在切換線程執行是隨機的,具體下面有實例


二、線程的幾種狀態

1、就緒:線程獲取了除cpu意外的全部資源,等待CPU的調度。

2、執行:線程獲得CPU執行權限,正在執行。

3、掛起(休眠):因爲終端請求或者操作系統要求,線程停止運行,被掛起。掛起的線程不能自己甦醒,必須被其他線程喚醒。

4、阻塞:線程因爲I/O等操作導致無法運行,轉到就緒狀態。

5、消亡:stop()或者進程結束。

三、java的兩種線程創建方式

1、將類聲明爲 Thread 的子類(繼承的方式)

      創建方法:

      1)定義一個demo類繼承Thread類。
      2)複寫Thread類的run方法。
         理由:run中存放的是自定義運行的代碼
      3)調用線程的start方法。
         作用:1.啓動線程 ;2.調用run方法。

程序示例:

package exam1;

/* 創建兩個線程,讓其與主程序交替運行。
 * 1)定義一個demo類繼承Thread類。
 * 2)複寫Thread類的run方法。
 *   理由:run中存放的是自定義運行的代碼
 * 3)調用線程的start方法。
 *   作用:1.啓動線程 ;2.調用run方法。
 */

class demo extends Thread//定義一個demo類繼承Thread類。
{
    //private String name;
    demo(String name)
    {
        //this.name = name;
        super(name);
    }
    public void run()//複寫Thread類的run方法,其中主要存放線程需要執行的代碼
    {
        for(int i=0; i<60;i++)
//        System.out.println(name+"  run!!---"+i);
            //currentThread() 獲取當前線程的對象
            //getName()獲取當前線程的名稱
        System.out.println(this.currentThread().getName()+"run!--"+i);
    }
}

public class Test1
{

    public static void main(String[] args)
    {
        demo t1 = new demo("one++++");
        demo t2 = new demo("two----");
//        t1.run();//直接調用方法,並不新建一個進程
//        t2.run();//
        t1.start();//調用線程的start方法,新建一個進程並在進程中調用方法。
        t2.start();
        
        
        for(int i = 0; i < 100; i++)
        System.out.println("Main---!!"+i);
    }
}


執行結果:


從結果中可以看出:同一個進程的幾個線程在執行過程中,是隨機交替執行的。

2、聲明實現 Runnable 接口的類

 * 步驟:
 * 1.定義類實現Runnable接口。
 * 2.覆蓋Runnable接口中的RUN方法。
 * 3.通過Thread類建立線程對象。
 * 4.將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造函數。

package exam1;

/*
 * 需求:簡單的賣票程序。(多線程,實現Runnable接口)
 *    多窗口同時買票。
 *    
 *    
 * 步驟:
 * 1.定義類實現Runnable接口。
 * 2.覆蓋Runnable接口中的RUN方法。
 * 3.通過Thread類建立線程對象。
 * 4.將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造函數。
 * 
 */

class Ticket implements Runnable//實現Runnable接口
{
    private int Tick = 100;
    public void run()
    {
        while(Tick>0)
        {
            System.out.println(Thread.currentThread().getName()+"  run---"+Tick--);
        }
    }
}


public class Test2
{    

    public static void main(String[] args)
    {
        Ticket T = new Ticket();
        Thread t1 = new Thread(T);
        Thread t2 = new Thread(T);
        Thread t3 = new Thread(T);
        Thread t4 = new Thread(T);
        Thread t5 = new Thread(T);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }

}
/*---繼承的方式購票系統--*/
/*class Ticket extends Thread
{
    private static int Tick = 100;//
    public void run()
    {
        while(Tick>0)
        {
            System.out.println(currentThread().getName()+"....."+Tick--);
        }
    }
}


public class Test2
{    

    public static void main(String[] args)
    {
        Ticket T1 = new Ticket();
        Ticket T2 = new Ticket();
        Ticket T3 = new Ticket();
        Ticket T4 = new Ticket();
        Ticket T5 = new Ticket();
        T1.start();
        T2.start();
        T3.start();
        T4.start();
        T5.start();

    }

}*/

兩種方式的聯繫和區別:繼承方式的侷限性是繼承了Thread類用來創建線程,那麼這個類就無法繼承其他的類。而通過實現Runnable接口創建線程不存在這個限制。定義線程時候最好採用實現方式,避免java單繼承的侷限性。

四、安全問題

在運行上面的售票程序的時候出現了賣出了0號,-1號等錯誤的票。


1、出現上述問題的原因是當一個進程執行共享的數據時候,該語句執行一部分就停止瞭然後切換到了另一線程。這就是著名的生產者,消費者問題。

2、解決方案。

     同步函數

       格式:

                在函數上加上synchronized修飾符即可。

        那麼同步函數用的是哪一個鎖呢?

        函數需要被對象調用。那麼函數都有一個所屬對象引用。就是this。所以同步函數使用的鎖是this。



1)同步代碼塊。

     示例:
     *  synchronized(object)
     *   {
     *       需要同步的代碼塊。
     *   }

     生產者消費者解釋:參考http://blog.chinaunix.net/uid-21411227-id-1826740.html

package exam1;

/*
 * 需求:簡單的賣票程序。(多線程,實現Runnable接口)
 *    多窗口同時買票。
 *    
 *    
 * 步驟:
 * 1.定義類實現Runnable接口。
 * 2.覆蓋Runnable接口中的RUN方法。
 * 3.通過Thread類建立線程對象。
 * 4.將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造函數。
 * 注:run方法是存放線程運行代碼的位置。
 */


/* 多線程出現安全問題:
 * 產生原因:多個線程共享一個數據,且是多條語句操作時候。一個線程對多條語句只執行了一部分,並沒有執行完,
 * 此時另一個線程參與進來。
 * 
 * 解決辦法:對多條操作共享數據的語句,一個線程在執行此語句時候其他線程不允許操作。
 *       JAVA提供的解決方案:同步代碼塊。
 *       示例:
 *       synchronized(object)
 *       {
 *       需要同步的代碼塊。
 *       }
 *       解釋:火車上的廁所,進去一個人將鎖鎖上。這裏的synchronized(object)就相當於廁所門。
 *       同步的好處:解決了多線程的安全問題。
 *       弊端:線程每次執行都會判斷一次鎖,比較消耗資源。
 * 
 */

class Ticket implements Runnable//實現Runnable接口
{
    private int Tick = 100;
    Object obj = new Object();//new一個同步代碼塊需要的對象
    public void run()
    {
        while(true)
        {
            synchronized(obj)//同步代碼塊
            {
                if(Tick > 0)
                {
                try{Thread.sleep(10);}catch(Exception e){}//使用sleep()模擬真實情況,測試安全問題
                System.out.println(Thread.currentThread().getName()+"  sale---"+Tick--);
                }
            }
        }
        
    }
}

     2)同步函數

     示例:

             public synchronized void function(參數)
              {
              }

              跟同步代碼塊一樣,同步函數也有一個所屬對象引用,也就是一個鎖(this)。

     代碼:

package exam1;

/* 需求: 銀行金庫。
 * 有兩個用戶分別存300元,每次存100元,存三次。
 *
 * 目的:驗證此程序有無安全問題?且如何解決。
 * 
 * 如何找到問題?
 * 1. 哪些帶代碼是多線程運行代碼?
 * 2. 哪些是共享數據?
 * 3. 哪些語句是操作共享數據的?
 */

class Bank
{
	private int sum;//金庫的錢的總數。 
	public synchronized void add(int m)//同步函數
	{
//		synchronized (this)//同步代碼塊解決
//		{
		sum = sum + m;
		try{Thread.sleep(10);}catch(Exception e){}//停10ms模擬
		System.out.println("sum="+sum);
//		}
	}
}
class Cus implements Runnable
{
	private Bank b = new Bank();//新建一個名爲b的Bank對象。
	public void run()
	{
		for(int i=0; i<3; i++)
		{
			b.add(100);
		}
	}
}

public class Test3
{

	public static void main(String[] args)
	{
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();

	}

}

五、靜態函數的同步。

     同步函數被靜態修飾後,所屬對象引用不是this,所以鎖不再是this了,此時內存中雖然沒有本類對象,但是一定有該類對應的字節碼對象。

     即:類名.class

     代碼:

   

package exam1;
/*
 * 如果同步函數被 靜態修飾後,使用的鎖是什麼呢?(不是this)
 * 此時該對象的類型爲 類名.class,使用的鎖是該方法所在類的字節碼文件對象。
 */

class Ticket2 implements Runnable//實現Runnable接口
{
	private static int Tick = 100;
//	Object obj = new Object();//new一個同步代碼塊需要的對象
	boolean flag = true;
	public void run()
	{
		while(true)
		{
			synchronized(Ticket2.class)//靜態的同步方法
			{
				if(Tick > 0)
				{
			    try{Thread.sleep(10);}catch(Exception e){}//使用sleep()模擬真實情況,測試安全問題
				System.out.println(Thread.currentThread().getName()+"  sale---"+Tick--);
				}
			}
		}
		}

	}
} 

六、單例設計模式。

//懶漢式,作用是延遲加載.這裏很重要。
class Single
{
    private static Single s = null;
    private Single(){}
    public static Single getInstance()//此處因爲s是共享的數據,而對其操作的是多條語句。
    {
        synchronized(Single.class)//同步代碼塊
        {
        if(s == null)
            s = new Single();
        }
        return s;
    }
}


代碼:

package exam1;


/*@ 面試中惡漢式和懶漢式的區別。
 *@ 
 *@
 */

/*
 * 單例設計模式
 */

//惡漢式

/*
class Single
{
	private static final Single s = new Single();//final更加嚴謹
	private Single(){}
	public static Single getInstance()
	{
		return s;
	}
} 
*/

//懶漢式,作用是延遲加載。
class Single
{
	private static Single s = null;
	private Single(){}
	public static Single getInstance()//此處因爲s是共享的數據,而對其操作的是多條語句。
	{
		synchronized(Single.class)//同步代碼塊
		{
		if(s == null)
			s = new Single();
		}
		return s;
	}
}

public class Test6 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

	}

}

七、死鎖

兩個進程同時持有自己的鎖,然後去獲取對方的鎖,導致相互永遠等待的情況,這稱爲死鎖。

代碼:

package exam1;

class Test_8 implements Runnable///實現Runnable接口(端口實現多線程)
{
	private boolean flag;
	Test_8(boolean flag)
	{
		this.flag = flag;
	}
	public void run() {
		if(flag)
		{
			
			synchronized(Mylock.locka)//a鎖內嵌套b鎖
			{
				System.out.println("if locka");
				synchronized(Mylock.lockb)
				{
					System.out.println("if lockb");
				}
			}
		}
		else
		{
			synchronized(Mylock.lockb)//b鎖內嵌套a鎖
			{
				System.out.println("if lockb");
				synchronized(Mylock.locka)
				{
					System.out.println("if locka");
				}
			}
		}	
	}
}

class Mylock//存儲兩個鎖。
{
    static Object locka = new Object();
    static Object lockb = new Object();
}


public class Test8 {

	public static void main(String[] args) {
		Thread t1 = new Thread(new Test_8(true));
		Thread t2 = new Thread(new Test_8(false));
		t1.start();
		t2.start();
	}

}

運行結果:

這裏看到鎖上了,兩個線程都持有對應的鎖,且要獲取對方的鎖。導致程序鎖死。


發佈了28 篇原創文章 · 獲贊 36 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章