線程Thread

基本概念:java

       線程是一個程序內部的順序控制流,一個進程至關於一個任務,一個線程至關於任務的一個執行路徑。緩存

       進程是一個操做系統執行的任務,通常都是.exe文件。一個進程中能夠運行着多個線程。服務器

線程和進程的類似性在於它們都是單一順序控制流。多線程

       多進程就是一個操做系統運行着多個任務。併發

       多線程就是一個程序內部運行着多個順序控制流。dom

       每一個Java運行程序至少有一個主線程,例如:public static void main(String[] args){}就是一個主線程。ide

 

運行方式:函數

       經過start()方法啓動一個線程。工具

       經過run()來執行一個線程,它是線程的主體部分。this

       經過sleep(long millis)方法來使當前線程休眠一段時間,當過了mills時間後再恢復到可運行態,不是運行狀態。所以,sleep()方法不能保證該線程到期後就立馬開始運行。sleep()是靜態方法,只能控制當前正在運行的線程,所以,休眠期間不影響其餘線程的運行。

       經過yield()方法暫停當前執行的線程,讓同優先級的線程輪詢執行一段時間。

       經過手動調用stop()方法來結束一個線程(或者執行到run()方法的末尾,或者拋出未經處理Exception/Error,這兩都是程序自動結束線程)。

       經過join()方法來讓線程A加入到線程B的尾部,在B執行完以前A不能工做。

       經過notify() 喚醒在此對象監視器上等待的單個線程。

       經過notifyAll()  喚醒在此對象監視器上等待的全部線程。

       經過wait() 致使當前的線程等待,直到其餘線程調用此對象的 notify()方法或 notifyAll()方法。

如何建立和啓動線程:

       兩種方式:extends Thread類、implements Runnable接口。

       Thread類:也是實現了Runnable接口。調用方式:繼承Thread類後的子類生成對象後調用start()方法,如:new MyThread().start()。

class MyThread extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println("i="+i);
		}
	}
}

        Runnable接口:只有一個run()方法來定義線程運行體。使用Runnable接口能夠爲多線程提供共享數據。調用方式:將子類對象經過Thread的構造器來執行start()方法,如:new Thread(new MyRunnable ()).start()。

class MyRunnable implements Runnable{
	String name ="xiaom";
	@Override
	public void run() {
		System.out.println("name:"+name);
	}
}

線程狀態:

    ①  新線程態(New Thread)

        產生了一個Thread對象就生成一個新線程。當線程處於「新線程」狀態時,僅僅只是一個空線程對象,系統尚未給它分配資源。所以,只能start()或者stop()操做,除此以外的操做都會引起異常。

    ②  可運行態(Runnable)

        start()方法產生線程運行時所需的資源,調度線程執行,而且調用run()方法時候,線程處於「可運行」狀態。之因此不稱爲「運行態」是由於它不老是一直佔用處理器。特別是對只有一個處理器的PC而言,任什麼時候刻只有一個可運行狀態的線程佔用。Java經過調度來實現多線程對處理器的共享。

    ③  非運行態(Not Runnable)

        當sleep()、wait()等方法被調用或者線程出於I/O等待時稱爲「非運行」狀態。

    ④  死亡態(Dead)

        當run()方法返回,或者別的線程調用stop()方法時,線程進入「死亡」狀態。

 

線程優先級:

       當PC只有一個處理器時,以某種順序在單處理器的狀況下執行多線程被稱爲」調度」。Java採用的是固定優先級調度,根據處於可運行狀態線程的優先級來調度。

       當線程產生時,它繼承原線程的優先級,必要的時候能夠經過Thread.setPriority()方法對優先級更改。若是有多個線程等待運行,系統選擇優先級別最高的可運行線程運行。只有當它中止、自動放棄、或者因爲某種緣由成爲非運行態時低優先級的線程才能運行。若是兩個線程具備相同的優先級,那麼它們會被交替運行。

        在任什麼時候刻,若是一個比其餘線程優先級都高的線程的狀態變爲「可運行「,那麼實時系統將選擇該線程來運行。

 

線程組:

       每一個Java線程都是某個線程組的成員。線程組提供一種機制,將多個線程集於一個對象內,能對它們進行總體操做。譬如,你能用一個方法調用來啓動或掛起組內的全部線程。Java線程組由ThreadGroup類實現。當線程產生時,能夠指定線程組或由實時系統將其放入某個缺省的線程組內。線程只能屬於一個線程組,而且當線程產生後不能改變它所屬的線程組。

 

多線程同步:

       由於併發性,當多個線程同時操做一個可共享資源時易產生數據不一致,所以加入同步鎖來避免該線程沒有完成操做以前有其餘線程進入,從而保證數據的惟一性和準確性。

       使用synchronized關鍵字修飾。

①  修飾方法:

    private synchronized void put(){}

    Java中每一個對象都有一個內置鎖,用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用方法前,須要得到內置鎖,不然會形成阻塞。

    synchronized還能夠修飾static方法,當靜態方法被調用時,鎖住的就是整個Class。

//修飾方法
private synchronized void _outTicket(){
	if(total>0){
		System.out.println(Thread.currentThread().getName()+ "出票" + this.total);
		this.total--;
	}
}

② 修飾代碼塊:

    synchronized(this){}

    被關鍵字修飾的語句塊會自動加上內置鎖,從而實現同步。

class MoreThread implements Runnable {
	private int total = 10;// 總票數

	@Override
	public void run() {
	for (int i = 0; i < 20; i++) {//線程運行數
		try {
			Thread.sleep(1000);
		} catch (Exception e) {
			e.printStackTrace();
		}
		outTicket();
	}
}

	// 出票方法
	private void outTicket() {
		synchronized(this){//修飾代碼塊
			if(total>0){
			System.out.println(Thread.currentThread().getName()+ "出票" + this.total);
				this.total--;
			}
		}
	}
}
public class MyThreadTest {
	public static void main(String[] args) {
		// 多線程同步
		MoreThread t = new MoreThread();
		new Thread(t,"線程1").start();
		new Thread(t,"線程2").start();
		new Thread(t,"線程3").start();
	}
}

    同步是一種高開銷的操做,所以應該儘可能減小同步內容。一般不必同步整個方法,使用synchronized代碼塊同步關鍵代碼便可。

 

線程鎖:

①  原理:

    Java中每一個對象都有一個內置鎖。當程序運行到synchronized修飾的非靜態方法上時會自動得到當前執行代碼的對象的鎖。

    一個對象只有一個鎖,若是一個線程得到當前對象鎖,其餘線程就不能再得到該對象鎖,除非該鎖已被釋放。

    釋放鎖是指持鎖對象已退出了synchronized修飾的方法或者代碼塊。

 

②  鎖和同步的要點:

    a)  只能同步方法,不能同步變量和類。

    b)  沒必要同步類的全部方法,類能夠同時擁有同步方法合非同步方法。

    c)   若是兩個線程要執行一個類中的synchronized方法,而且兩個線程使用相同的實例來調用方法,那麼一次只能有一個線程可以執行方法,另外一個須要等待,直到鎖被釋放。也就是說:若是一個線程在對象上得到一個鎖,就沒有任何其餘線程能夠進入(該對象的)類中的任何一個同步方法。

    d)  若是線程擁有同步和非同步方法,則非同步方法能夠被多個線程自由訪問而不受鎖的限制。

    e)  線程睡眠時,鎖不會釋放。

    f)  線程能夠得到多個鎖。好比,在一個對象的同步方法裏面調用另一個對象的同步方法,則獲取了兩個對象的同步鎖。

 

向線程傳遞數據:

①  經過構造器傳遞

class MyRunnable implements Runnable {
    String name ;
	MyRunnable(String name){
		this.name = name;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("name:" + name+i);
		}
	}
}

調用方式:

new Thread(new MyRunnable("Xiaoming")).start();

②經過變量的setters方法傳遞

class MyRunnable implements Runnable {
	String name ;
	public void setName(String name) {
		this.name = name;
	}


	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("name:" + name+i);
		}
    }
}

調用方式:             

MyRunnable myRunnable = new MyRunnable();
 myRunnable.setName("Xiaoming");
 Thread thread = new Thread(myRunnable);
 thread.start();

③ 經過回調函數傳遞

class Data {
		public int i =100;//初始數據
}

class Work{
	public void process(Data data, int random){
		System.out.println("data.i="+data.i+" random="+random);
		data.i*=random;
	}
}

class NewThread implements Runnable{
	private Work work;
	NewThread(Work work){
		this.work = work;
	}
	@Override
	public void run() {
		Data data = new Data();
		work.process(data, new Random().nextInt(10));//回調函數
		System.out.println("result:"+data.i);
	}
	public static void main(String[] args) {
		new Thread(new NewThread(new Work())).start();
	}
}

線程池:

    線程池做用就是限制系統中執行線程的數量。

    根據系統的環境狀況,能夠自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統資源,多了形成系統擁擠效率不高。用線程池控制線程數量,其餘線程排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處於等待。當一個新任務須要運行時,若是線程池中有等待的工做線程,就能夠開始運行了;不然進入等待隊列。

    ①  減小建立和銷燬線程的次數,每一個工做線程均可以被重複利用,可執行多個任務。

    ②  可根據系統的承受能力,調整線程池中線程的數目,防止由於消耗過多的內存,而把服務器累趴(每一個線程大約須要1MB內存,線程開的越多內存消耗就越大)。

 

  池的建立方式:

       Java裏面線程池的頂級接口是Executor,可是嚴格意義上講Executor並非一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。

       Executors提供如下幾個方法來建立線程池:

        ①  newFixedThreadPool(int nThreads):建立一個可重用固定線程數的線程池。

代碼:

ExecutorService pool =Executors.newFixedThreadPool(3);
pool.execute(new Thread(new MoreThread(),"線程1"));
pool.execute(new Thread(new MoreThread(),"線程2"));
pool.execute(new Thread(new MoreThread(),"線程3"));
			//關閉線程池
pool.shutdown();

        ② newSingleThreadExecutor():建立一個單線程的線程池。線程池只有一個線程在工做,若是這個線程出現異常,會有一個新的線程來替代它。此線程池保證全部任務的執行順序按照任務提交的順序執行。

代碼:

ExecutorService pool = Executors.newSingleThreadExecutor();

        ③ new CachedThreadPool():建立一個可緩存的線程池。若是線程池的大小超過了處理任務所須要的線程,那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增長時,此線程池又能夠智能的添加新線程來處理任務。此線程池不會對線程池大小作限制,線程池大小徹底依賴於操做系統(或者說JVM)可以建立的最大線程大小。

代碼:

ExecutorService pool = Executors.newCachedThreadPool();

        ④  newScheduledThreadPool(int corePoolSize):建立一個線程池,它可安排在給定延遲後運行命令或者按期的執行。

代碼:

ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
//使用延遲,隔一、三、5秒執行當前線程
pool.schedule(new Thread(new MoreThread(),"線程1"), 1000, TimeUnit.MILLISECONDS);
pool.schedule(new Thread(new MoreThread(),"線程2"), 3000, TimeUnit.MILLISECONDS);
pool.schedule(new Thread(new MoreThread(),"線程3"), 5000, TimeUnit.MILLISECONDS);
		
//關閉線程池
pool.shutdown();

自定義線程池ThreadPoolExecutor:

    ThreadPoolExecutor的構造器:

ThreadPoolExecutor(int corePoolSize ,int maximumPoolSize
,long keepAliveTime ,TimeUnit unit
,BlockingQueue<Runnable> workQueue
,ThreadFactory threadFactory
,RejectedExecutionHandler handler);

參數:

       corePoolSize:池中所保存的線程數,包括空閒線程。

       maximumPoolSize:池中容許的最大線程數。

       keepAliveTime:當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間。

       unit:keepAliveTime的時間單位。

       workQueue:執行前用於保持任務的隊列。此隊列僅保持由execute方法提交的Runnable任務。

       threadFactory:執行程序建立新線程時使用的factory。

       handler:因爲超出線程範圍和隊列容量而使執行被阻塞時所使用的處理程序。

 

併發機制-鎖:

       在Java5中,專門提供了鎖對象,利用鎖能夠方便的實現資源的封鎖,利用控制對競爭資源併發訪問的控制,這些內容主要在java.util.concurrent.locks包中,裏面有三個重要的接口:

  •  Condition:將Object監視器方法(wait、notify、notifyAll)分解成大相徑庭的對象,以便經過將這些對象與任意Lock實現組合適用,爲每一個對象提供多個等待。
  •  Lock:提供了比使用synchronized方法和語句更普遍的鎖定操做。
  •  ReadWriteLock:維護了一組相關的鎖定,一個只讀操做,一個寫入操做。

 

直接提供一段Lock代碼:

/**
 * 線程鎖的機制
 * @author admin
 */
public class ThreadLockTest {
	public static void main(String[] args) {
		Ticket ticket = new Ticket(30);//建立總票數
		TicketWindow wind = new TicketWindow();//售票窗口隨機
		Lock lock = new ReentrantLock();//建立鎖
		ExecutorService pool = Executors.newCachedThreadPool();//線程池
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("張力",2), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("李興",1), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("劉蘭蘭",5), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("兔兔",2), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("張力",2), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("姚麗麗",1), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("姚明明",3), ticket, lock)));
		pool.shutdown();
	}
}

/**
 * 售票窗口類
 * @author admin
 */
class TicketWindow{
	//隨機返回一個窗口
	public int wind(){
		return (new Random().nextInt(5)+1);
	}
}

/**
 * 總票數類
 * @author admin
 */
class Ticket{
	private int total;
	//構造器
	public void setTotal(int total) {
		this.total = total;
	}
	public Ticket(int total) {
		this.total = total;
	}
	public int getTotal() {
		return total;
	}
}

/**
 * 售票機制
 * @author admin
 */
class Tecketing implements Runnable{
	private TicketWindow wind; //窗口
	private Buyyer user;
	private Ticket ticket; //總票數
	private Lock lock;
	
	public Tecketing(TicketWindow wind, Buyyer user, Ticket ticket, Lock lock) {
		this.wind = wind;
		this.user = user;
		this.ticket = ticket;
		this.lock = lock;
	}

	@Override
	public void run() {
		//獲取鎖
		lock.lock();
		if(ticket.getTotal()>0){
			System.out.print(user.getName()+"在"+wind.wind()+"窗口購買了"+user.getTicketCount()+"張票,");
			ticket.setTotal(ticket.getTotal()-user.getTicketCount());
			System.out.println("目前剩餘票數:"+ticket.getTotal());
		}else{
			System.out.println("今日票已所有售罄!");
		}
		//釋放鎖,不然其餘線程沒法執行
		lock.unlock();
	}
}

/**
 * 購票者類
 */
class Buyyer{
	private String name; 
	private int ticketCount; //購票數量
	public Buyyer(String name, int ticketCount) {
		this.name = name;
		this.ticketCount = ticketCount;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getTicketCount() {
		return ticketCount;
	}
	public void setTicketCount(int ticketCount) {
		this.ticketCount = ticketCount;
	}
}

ReadWriteLock機制的鎖,讀寫鎖分離,靈活性更好。代碼:

/**
 * ReadWriterLock接口讀寫時分別用鎖
 * @author admin
 */
public class ReadWriterLokTest {
	public static void main(String[] args) {
		Ticket ticket = new Ticket(30);//建立總票數
		TicketWindow wind = new TicketWindow();//售票窗口隨機
		ReadWriteLock lock = new ReentrantReadWriteLock();//建立鎖
		ExecutorService pool = Executors.newCachedThreadPool();//線程池
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("窗口",0), ticket, lock, false)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("張力",2), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("李興",1), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("劉蘭蘭",5), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("兔兔",2), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("張力",2), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("姚麗麗",1), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("姚明明",3), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("窗口",0), ticket, lock, false)));
		pool.shutdown();
	}
}

/**
 * 售票機制
 * @author admin
 */
class Tecketing_1 implements Runnable{
	private TicketWindow wind; //窗口
	private Buyyer user;
	private Ticket ticket; //總票數
	private ReadWriteLock lock;
	private boolean isCheck;//是否查詢
	private int solded =0;//已售賣
	
	public Tecketing_1(TicketWindow wind, Buyyer user, Ticket ticket, ReadWriteLock lock,boolean isCheck) {
		this.wind = wind;
		this.user = user;
		this.ticket = ticket;
		this.lock = lock;
		this.isCheck = isCheck;
	}

	@Override
	public void run() {
		if(isCheck){
			//獲取寫鎖
			lock.writeLock().lock();
			if(ticket.getTotal()>0){
				System.out.print(user.getName()+"在"+wind.wind()+"窗口購買了"+user.getTicketCount()+"張票,");
				ticket.setTotal(ticket.getTotal()-user.getTicketCount());
				System.out.println("目前剩餘票數:"+ticket.getTotal());
			}else{
				System.out.println("今日票已所有售罄!");
			}
			//釋放
			lock.writeLock().unlock();
		}else{
			//獲取讀鎖
			lock.readLock().lock();
			solded +=user.getTicketCount();
			ticket.setTotal(ticket.getTotal()-solded);
			System.out.println("窗口"+wind.wind()+" 正在查詢剩餘票數:"+ticket.getTotal());
			//釋放
			lock.readLock().unlock();
		}
	}
}
相關文章
相關標籤/搜索