基本概念: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包中,裏面有三個重要的接口:
直接提供一段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(); } } }