一、什麼是多線程?
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();
}
}
運行結果:
這裏看到鎖上了,兩個線程都持有對應的鎖,且要獲取對方的鎖。導致程序鎖死。