天天看點

黑馬程式員——Java基礎--線程多線程

------ Java教育訓練、Android教育訓練、iOS教育訓練、.Net教育訓練、期待與您交流! -------

多線程

一、概念
  1、程序:是一個正在執行中的程式。
    每一個程序執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元。
  線程:就是程序中的一個獨立的控制單元。線程在控制着程序的執行。一個程序中至少有一個線程。
  java VM 啟動時會有一個程序java.exe。該程序中至少有一個線程負責java程式的執行。而且這個線程運作的代碼存在于main方法中。該線程稱之為主線程。
  	**擴充:其實更細節的說jvm,jvm啟動不止一個線程,還有負責垃圾回收機制的線程。
二、建立多線程      
  如何在自定義代碼中,自定義一個線程呢?
	通過對api的查找,java已經提供了對線程這類事物的描述,就是thread類。
  1、建立線程的第一種方式:繼承Thread類。      
步驟:
	a.定義類繼承thread。  
	b.複寫Thread類中的run方法。
       
  		目的:将自定義代碼存儲在run方法,讓線程運作。      
c.調用線程的start方法。      
    該方法兩個作用:啟動線程,調用run方法。 **多次啟動一個線程是非法的。特别是當線程已經結束執行後,不能再重新啟動。    在程式運作時發現運作結果每一次都不同。 因為多個線程都在擷取CPU的執行權,CPU執行到誰,誰就運作。說明一點,在某一時刻,隻能有一個程式在運作。(多核  除外)。CPU在做着快速的切換,已達到看上去是同時運作的效果。     我們可以形象把多線程的運作行為看成在互相搶奪CPU的執行權。 這就是多線程的一個特性:随機性。誰搶到誰執行,至于執行多長時間,CPU說了算。   2、為什麼要覆run方法呢 Thread類用于描述線程。 該類就定義了一個功能,用于存儲線程要運作的代碼,該存儲功能就是run方法。也就是Thread類中的run方法,用于存儲  線程要運作的代碼。
示例:      
class Demo extends Thread
{
	
	public void run()
	{
		for(int x=0;x<60;x++)
			System.out.println("demo run"+x);
	}
}

class ThreadDemo
{
	public static void main(String[] args)
	{
		//for(int x;x<400;x++)
			//System.out.println("hello world!");

		Demo d = new Demo();//建立好一個線程。
		d.start();//開啟線程并執行該線程的run方法。
		//d.run();//僅僅是對象調用方法,而線程建立了,并沒有運作。

		for(int x=0;x<60;x++)
			System.out.println("hello world!"+x);

	}
}
           
練習:
      
/*
建立兩個線程,和主線程交替運作。
原來線程都有自己預設的名稱。
thread-編号,編号從0開始。
static Thread currentThraed():擷取目前線程對象。是靜态的
getName():擷取線程名稱。
設定線程名稱:setName或者構造函數。

*/
class Test extends Thread
{
	
	//private String name;
	Test(String name)
	{
		//this.name = name;
		super(name);
	}
	public void run()
	{
	
		for(int x=0; x<60;x++)
		{
System.out.println((Thread.currentThread()==this)+"...."this.getName()+"..run..."+x);
		}
		
	}
}


class ThreadTest
{
	public static void main(String[] args)
	{
		Test t1 = new Test("one---");
		Test t2 = new Test("two+++");
		t1.start();
		t2.start();
//		t1.run();
//		t2.run();
		
		for(int x=0;x<60;x++)
		{
			System.out.println("main..."+x);
		}
	
	}
}
           
  3、建立線程的第二種方式:實作Runnable接口
	步驟:
	  a.定義類實作Runnable接口。
	  b.覆寫Runnable接口中的run方法。将線程要運作的代碼存放在該run方法中。
	  c.通過Thread類建立線程對象。
	  d.将Runnable接口的子類對象作為實際參數傳遞給Thread類的構造函數。
  	  *為什麼要将Runnable接口的子類對象傳遞給Thread的構造函數?
   	  因為,自定義的run方法所屬的對象是Runnable接口的子類對象。是以要讓線程去指定指定對象的run方法。就必須明	确該run方法所屬對象。      
  e.調用Thread類的start方法開啟線程并調用Runnable接口子類的run方法。
  **(面試必考)實作方式和繼承方式有什麼差別?
	實作方式的好處:避免了單繼承的局限性。在定義線程時,建議使用實作方式。
  4、兩種方式差別:
    繼承Thread:線程代碼存放Thread子類run方法中。
  	實作Runnable:線程代碼存放在接口的子類的run方法。
示例:
      
class Ticket implements Runnable//extends Thread
{
	private int tick = 100;
	public void run()
	{
		while(true)
		{
			if(tick>0)
			{
				System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
			}		
		}
	}
}
class TicketDemo
{
	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);

		t1.start();
		t2.start();
		t3.start();
		t4.start();
		

	/*	Ticket t1 = new Ticket();
		Ticket t2 = new Ticket();
		Ticket t3 = new Ticket();
		Ticket t4 = new Ticket();
	
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	*/	

	}
}
           
對以上示例通過分析,發現,列印出0,-1,-2等錯票。
  5、多線程的運作出現了安全問題。
	a.問題的原因:
     當多條語句在操作同一個線程共享資料時,一個線程對多條語句隻執行了一部分,還沒有執行完,另一個線程參與進來執  行,導緻共享資料的錯誤。
	b.解決辦法:
  	對多條操作共享資料的語句,隻能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。
  java對于多線程的安全問題提供了專業的解決模式。
	就是:同步代碼塊。
		synchronized(對象)
		{
			需要被同步的代碼
		}
	對象如同鎖。持有鎖的線程可以在同步中執行。沒有持有鎖的線程及時擷取CPU的執行權,也進不去,因為沒有擷取鎖。
  6、同步的前提:
	a.必須要有兩個或者兩個以上的線程。
	b.必須是多個線程使用同一個鎖。
	必須保證同步中隻能有一個線程在運作。
  好處:解決多線程的安全問題。
  弊端:多個線程需要判斷鎖,較為消耗資源,
      
class Ticket implements Runnable//extends Thread
{
	private int tick = 1000;
	Object obj = new Object();
	public void run()
	{
		while(true)
		{
			synchronized(obj)
			{
				if(tick>0)
				{
/*
為什麼不能抛異常?因為run()方法複寫的Runnable接口的方法,父類沒有抛異常,子類有異常隻能try
					//try{Thread.sleep(10);}catch(Exception //e){}
*/
					System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
				}
			}		
		}
	}
}
class TicketDemo2
{
	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);

		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
           
練習:
      
/*
需求:
銀行有一個金庫。
有兩個儲戶分别存300,每次存100,存3次。

目的:該程式是否有安全問題,如果有,如何解決?

如何找到問題:
1.明确那些代碼多線程運作代碼。
2.明确共享資料。
3.明确多線程運作代碼中那些語句是操作共享資料的。

*/

class Bank
{
	private int sum;
	//Object obj = new Object();
	public synchronized void add(int n)//同步函數
	{
		
		//synchronized(obj)//同步代碼塊
		//{
			sum = sum + n;
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println("sum="+sum);
		/}
	}
}

class Cus implements Runnable
{
	private Bank b = new Bank();
	public void run()
	{
		for(int x=0;x<3;x++)
		{
			b.add(100);
		}
	}

}

class BankDemo
{
	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 Ticket implements Runnable//extends Thread
{
	private int tick = 100;
	Object obj = new Object();
	boolean flag = true;
	public void run()
	{
		if(flag)
		{
		while(true)
		{
			synchronized(this)
			{
				if(tick>0)
				{
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"...code:"+tick--);	
				}
			}	
		}
		}
		else
			while(true)
				show();
		
	}
	public synchronized void show()//this
	{
		if(tick>0)
		{
			try{Thread.sleep(10);}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"...show:"+tick--);
		}
	}
}
class ThisLockDemo
{
	public static void main(String[] args)
	{
		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		
		t1.start();
		//try{Thread.sleep(10);}catch(Exception e){}
		t.flag = false;
		t2.start();

		//Thread t3 = new Thread(t);
		//Thread t4 = new Thread(t);

		//t3.start();
		//t4.start();
	}
}
           
  7、如果同步函數被靜态修飾後,使用的鎖是什麼呢?
	通過驗證,發現不再是this。因為靜态方法中也不可以定義this。
    靜态進記憶體時,記憶體中沒有本類對象,但是一定有該類對應的位元組碼檔案對象。
     	類名.class  該對象的類型是class。
	靜态的同步方法,使用的鎖是該方法所在類的位元組碼檔案對象,類名.class
      
  應用:單例模式中的展現      
//餓漢式。
/*
class Single
{
	private static final Single s = new Single;
	private Single(){}
	public static Single getInstance()
	{
		returen s;
	}
}
*/

//懶漢式

class Single
{
	private static Single s = null;
	private Single(){}
	public static Single getInstance()
	{
		//雙重判斷,減少判斷鎖的次數,提高效率
		if(s==null)
		{
			synchronized(Single.class)
			{
				if(s==null)
					s = new Single();
				return s;
			}
		}
	}
}
           
死鎖:
class Test implements Runnable
{
	private boolean flag;
	Test(boolean flag)
	{
		this.flag = flag;
	}
	public void run()
	{
		if(flag)
		{
			synchronized(MyLock.locka)
			{
				System.out.println("if locka");
				synchronized(MyLock.lockb)
				{
					System.out.println("if lockb");
				}
			}
		}
		else
		{
			synchronized(MyLock.lockb)
			{
				System.out.println("else lockb");
				synchronized(MyLock.locka)
				{
					System.out.println("else locka");
				}
			}
		}
	}
}
class MyLock
{
	static Object locka = new Object();
	static Object lockb = new Object();
}
class DeadLockTest
{
	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 Res
{
	private String name;
	private String sex;
	private boolean flag = false;

	public synchronized void set(String name,String sex)
	{
		if(flag)
			try{this.wait();}catch(Exception e){}
		this.name = name;
		this.sex = sex;
		flag = true;
		this.notify();
	}
	public synchronized void out()
	{
		if(!flag)
			try{this.wait();}catch(Exception e){}
		System.out.println(name+"...."+sex);
		flag = false;
		this.notify();
	}
}
class Input implements Runnable
{
	private Res r;
	
	Input(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		int x = 0;
		while(true)
		{

			if(x==0)
				r.set("mike","man");
				
			else
				r.set("麗麗","女 女") ;
			
			x=(x+1)%2;


/*			synchronized(r)
			{
			if(r.flag)
				try{r.wait();}catch(Exception e){}
			if(x==0)
			{
				
				r.name ="mike";
				r.sex = "man";
			
			}
			else
			{
			
				r.name ="麗麗";
				r.sex = "女 女";
			
			
			}
			x=(x+1)%2;
			r.flag= true;
			r.notify();
			}
*/
		}
	}
}
class Output implements Runnable
{
	private Res r;
	
	Output(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{	
			r.out();


/*			synchronized(r)
			{
			if(!r.flag)
				try{r.wait();}catch(Exception e){}
			System.out.println(r.name+"...."+r.sex);
			r.flag = false;
			r.notify();
			}
*/
		}	
	}
}
class InputOutputDemo
{
	public static void main(String[] args)
	{
		Res r =new Res();
		new Thread(new Input(r)).start();
		new Thread(new Output(r)).start();

/*
		Input in = new Input(r);
		Output out = new Output(r);
		Thread t1 =new Thread(in);
		Thread t2 = new Thread(out);
			
		t1.start();
		t2.start();
*/
	}
}
           
  1、多線程間通訊中常用方法      
wait:
	ntify();
	notifyAll();
	都使用在同步中,因為要對持有螢幕(鎖)的線程操作。是以要使用在同步中,因為隻有同步才具有鎖。
	*為什麼這些操作線程的方法要定義Object類中呢?
	因為這些方法在操作同步中線程時,都必須要辨別它們所操作線程隻有的鎖。隻有同一個鎖上的被等待線程,可以被同一個鎖上的notify喚醒。不可以對不同鎖中的線程喚醒。
	也就是說,等待和喚醒必須是同一個鎖。
	而鎖可以是任意對象,是以可以被任意對象調用的方法定義Object類中。
      
示例:      
/*
對于多個生産者和消費者。
為什麼要定義while判斷标記。
原因:讓被喚醒的線程再一次判斷标記。

為什麼定義notifyAll,
以為需要喚醒對方線程。
因為隻有notify,容易出現至喚醒本方線程的情況,導緻程式中的所有線程都等待。
*/

class ProduceConsumerDemo
{
	public static void main(String[] args)
	{
		Resource r = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
	
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);
	
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;
	public synchronized void set(String name)
	{
		while(flag)
			try{wait();}catch(Exception e){}
		this.name=name+"---"+count++;
		
		System.out.println(Thread.currentThread().getName()+"..生産者"+this.name);
		flag=true;
		this.notifyAll();
	}
	public synchronized void out()
	{
		while(!flag)
			try{wait();}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"......消費者"+this.name);
		flag=false;
		this.notifyAll();
		
	}
}

class Producer implements Runnable
{
	private Resource res;
	
	Producer(Resource res)
	{
		this.res=res;
	}
	public void run()
	{
		while(true)
		{
			res.set("商品");
		} 
	}
}
class Consumer implements Runnable
{
	private Resource res;
	
	Consumer(Resource res)
	{
		this.res=res;
	}
	public void run()
	{
		while(true)
		{
			res.out();
		}
	}

}
           
JDK1.5更新版
      
class ProduceConsumerDemo2
{
	public static void main(String[] args)
	{
		Resource r = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
	
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);
	
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

/*
JDK1.5中提供了多線程更新解決方案。
将同步synchronized替換成現實Lock操作。
将Object中wait,notify,notifyAll,替換成了Condition對象。
該對象可以Lock鎖進行擷取。

該事例中,實作了本方隻喚醒了對方的操作。

*/

class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;

	private Lock lock = new ReentrantLock();
	//一個鎖Lock可以對應對個Condition。
	private Condition condition_pro = lock.newCondition();
	private Condition condition_con = lock.newCondition();
	public void set(String name)throws InterruptedException
	{
		lock.lock();
		try
		{
		while(flag)
			condition_pro.await();
		this.name=name+"---"+count++;
		
		System.out.println(Thread.currentThread().getName()+"..生産者"+this.name);
		flag=true;
		condition_con.signal();
		}
		finally
		{
		lock.unlock();//釋放鎖的動作一定執行。
		}
	}
	public void out()throws InterruptedException
	{
		lock.lock();
		try
		{
		while(!flag)
			condition_con.await();
		System.out.println(Thread.currentThread().getName()+"......消費者"+this.name);
		flag=false;
		condition_pro.signal();
		}
		finally
		{
			lock.unlock();
		}
	}
}

class Producer implements Runnable
{
	private Resource res;
	
	Producer(Resource res)
	{
		this.res=res;
	}
	public void run()
	{
		while(true)
		{
			try
			{
				res.set("商品");
			}
			catch(InterruptedException e)
			{

			}
			
		}
	}
}
class Consumer implements Runnable
{
	private Resource res;
	
	Consumer(Resource res)
	{
		this.res=res;
	}
	public void run()
	{
		while(true)
		{
			
			try
			{
				res.out();
			}
			catch(InterruptedException e)
			{

			}
		}
	}

}
           
  2、停止線程      
a.定義循環結束标志
	因為線程運作代碼一般都是循環,隻要控制了循環即可
       
 b.使用interrupt(中斷)方法。	      
該方法是結束線程的當機狀态,使線程回到運作狀态中來。      
注:stop方法已經過時不在使用。 分析:如何停止線程? 隻有一種,run方法結束。 開啟多線程運作,運作代碼通常是循環結構。 隻要控制住循環,就可以讓run方法結束,也就是線程結束。     *特殊情況: 當線程處于當機狀态。就不會讀取到标記,那麼線程就不會結束。 當沒有指定的方式讓當機的線程恢複到運作狀态時,這時需要對當機進行清除。強制讓線程恢複到運作狀态中來,這樣就  可以操作标記讓線程結束。 Thread類提供該方法 interrupt();
class StopThread implements Runnable
{
	private boolean flag = true;
	public synchronized void run()
	{
		while(flag)
		{
			try{wait();}
			catch(InterruptedException e)
			{System.out.println(Thread.currentThread().getName()+"...Exception");
			flag=  false;
			}
			System.out.println(Thread.currentThread().getName()+"...run");
		}
	}
	public void changeFlag()
	{
		flag = false;
		
	}
}

class StopThreadDemo
{
	public static void main(String[] args)
	{
		StopThread st = new StopThread();
			
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
	
		t1.setDaemon(true);
		t2.setDaemon(true);

		t1.start();
		t2.start();
	
		int num = 0;
		while(true)
		{
			if(num++ == 60)
			{
				//st.changeFlag();
				
				//t1.interrupt();
				//t2.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName()+"......."+num);
		}
		
	}

}
           
  3、其他方法示範      
/*
join:
當A線程執行到了B線程的.join()方法時,A就會等待,等B線程都執行完,A才會執行。

join可以用來臨時加入線程執行。
*/

class Demo implements Runnable
{
	public void run()
	{
		for(int x= 0;x<70;x++)
		{
			System.out.println(Thread.currentThread().getName()+"...."+x);
			Thread.yield();//暫停目前正在執行的線程對象。
		}
	}
}

class JoinDemo
{
	public static void main(String[] args)throws Exception
	{
		Demo d = new Demo();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);

		t1.start();
		//t1.setPriority(Thread.MAX_PRIORITY);優先級設定(MAX_PRIORITY最高優先級)
		
		t2.start();
		//主線程等待t1結束
		t1.join();
		for(int x=0;x<80;x++)
		{
			System.out.println("main....."+x);
		}
		System.out.println("over");

	}
}
           
練習:
class ThreadTest2
{
	public static void main(String[] args)
	{
		new Thread()
		{
			public void run()
			{
				for(int x=0;x<100;x++)
				{
					System.out.println(Thread.currentThread().getName()+"......"+x);
				}
			}
		}.start();
		
		for(int x=0;x<100;x++)
		{
			System.out.println(Thread.currentThread().getName()+"......"+x);
		}

		Runnable r = new Runnable()
		{
			public void run()
			{
				for(int x=0;x<100;x++)
				{
					System.out.println(Thread.currentThread().getName()+"......"+x);
				}
			}
		};
		new Thread(r).start();
		
	
	}
}

/*

class Test1 extends Thread
{
	public void run()
	{
		for(int x=0;x<100;x++)
		{
			System.out.println(Thread.currentThread().getName()+"......"+x);
		}
	}	
}

*/