------<a href="http://www.itheima.com" target="blank">Java培訓、Android培訓、iOS培訓、.Net培訓</a>、期待與您交流! -------
1.理解程序、進程、線程的概念
程序可以理解為靜態的代碼,計算機指令的集合,它以文件的形式存儲在磁盤上。
進程可以理解為執行中的程序,一個單獨程序打開了單獨的一段地址空間進行單獨的工作。
線程可以理解為進程的進一步細分,程序的一條執行路徑。
多線程並非是指許多個線程同時運行,而是cpu的快速切換。
線程大致的粗分為五個狀態:
創建 通過 new Thread及其子類
運行 正在執行的線程,占據cpu
阻塞 擁有執行資格,只是沒有搶到cpu,這是隨機的有cpu決定
凍結 無執行資格,處於休眠狀態
消亡 run方法執行完畢(1.5以前可以通過調用stop方法來強制結束線程)
線程實質是由Windows來創建,而java已經將創建線程這一功能封裝好了,只需要用就可以了,繼承java.lang包中的Thread類即可。大致分為兩種方法
方法一:直接繼承Thread類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25class PrintNum extends Thread{//繼承Thread類
public void run(){
//復寫run方法,子線程執行的代碼
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public PrintNum(String name){
super(name);
}
}
public class TestThread {
public static void main(String[] args) {
PrintNum p1 = new PrintNum("線程1");
PrintNum p2 = new PrintNum("線程2");
p1.setPriority(Thread.MAX_PRIORITY);//優先級10
p2.setPriority(Thread.MIN_PRIORITY);//優先級1
p1.start();//啟動線程並調用run方法
p2.start();
}
}
分為三個步驟:1.創建線程 2.復寫Tread中的run方法,也就是把要執行的代碼塊放其中 3.調用stat方法,啟動線程並調用run方法
方法二:實現Runnable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23class SubThread implements Runnable{
public void run(){//2.復寫run方法
//子線程執行的代碼
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class TestThread{
public static void main(String[] args){
SubThread s = new SubThread();
Thread t1 = new Thread(s);//1.創建線程
Thread t2 = new Thread(s);
t1.setName("線程1");
t2.setName("線程2");
t1.start();//3.啟動並調用run方法
t2.start();
}
}
兩種方法的比較:
如果沒有該類沒有繼承其他類則用繼承方法,run方法是寫在Thread類中,而且對於一大段代碼中出現過的多次循環體也直接采用匿名內部類將其包裝起來實現多線程比較簡潔
1 2 3 4 5 6 7new Thread(){
public void run(){
for(int i=0;i<100;i++){
System.out.println(i);
}
}
}.start();
但是單繼承具有局限性,如果多個線程有共享數據的話,建議使用實現方式,同時,共享數據所在的類可以作為Runnable接口的實現類,run方法是寫在Runnable接口中。比如,兩個窗口同時買100張票。
class gxzy implements Runnable{//實現Runnable接口
private int piao=100;
public void run(){//重寫run方法
while(true){
if(piao>0)
System.out.println(Thread.currentThread().getName()+" 第"+piao--+"票");
}
}
}
public class maipiao {
public static void main(String[] args){
gxzy gx = new gxzy();
Thread xz1 =new Thread(gx);//創建線程
Thread xz2 =new Thread(gx);
xz1.setName("第1號買票窗口");
xz2.setName("第2號買票窗口");
xz1.start();//啟動線程並調用run方法
xz2.start();
}
}
但是上面的程序有個安全問題存在,即如果在線程運行到if語句之下還未執行輸出語句時,cpu被其他程序(是程序哦)給占了,那麼此線程處於阻塞狀態,而其他線程排在後面,當這個線程恢復為運行狀態時剛好賣出的是最後一張票,則有可能後面的線程會賣出第0張票,寫一個sleep方法就可以清晰的看到問題所在了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16class gxzy implements Runnable{
private int piao=100;
Object obj = new Object();
public void run(){
while(true){
if(piao>0)
try{
Thread.sleep(3);//停3毫秒
}
catch(Exception e){
}
System.out.println(Thread.currentThread().getName()+" 第"+piao--+"票");
}
}
}
那麼這個時候就需要采用同步來解決了,同步提供一個鎖,在這個線程執行run方法的時候鎖會關上,其他線程是無法進去的,直到這個線程執行完畢鎖打開,其他線程才能呢進來。
synchronized關鍵字為同步,有兩種寫法可以寫出同步代碼塊,裡面的鎖可以是任意對象,一般采用object類或現有資源類
1synchronized(對象){執行的代碼塊}
還有一種是用synchronized修飾在函數上的稱為同步函數,也就是把那想要同步的代碼塊單獨拿出來寫在一個方法裡,再用synchronized修飾。這裡的鎖是this,如果函數經static修飾,因為靜態裡不能有this也先於對象存在所以他的鎖為java.class字節碼文件對象,格式為 類名.class。
無論是同步代碼塊還是同步代碼函數,使用時都有兩個前提,有兩個或以上的線程,多個線程使用同一個鎖。
看看單例模式中的懶漢式,用同步解決他的線程問題
1 2 3 4 5 6 7 8 9 10 11 12 13class aa{
private static aa bb =null;
private aa(){}
public static aa lei(){
if(bb==null);//雙重判斷,提高效率
synchronized(aa.class){//創建了對象之後,將拒絕所有線程的訪問
if(bb==null){
bb=new aa();
}
return bb;
}
}
}
我們沒有用同步函數而是用的代碼塊,這樣更靈活一些,如果直接采用同步函數,將十分低效,即使是當創建了對象也會不斷有線程進來訪問鎖,而鎖的一大弊端是很耗費資源。而采用代碼塊使用雙重判斷,在鎖前面來一個判斷,則不需要再對鎖進行訪問了。還有一點值得注意的還返回對象的函數是靜態的,而靜態的鎖為類名.class。