第十一章:Java_多线程
1.理解程序、進程、線程的概念
程序可以理解為靜態的代碼
進程可以理解為執行中的程序。
線程可以理解為進程的進一步細分,程序的一條執行路徑
每個Java程序都有一個隱含的主線程: main 方法
使用多線程的優點:
Thread的常用方法:
/** Thread的常用方法:* 1.start():啟動線程并執行相應的run()方法* 2.run():子線程要執行的代碼放入run()方法中* 3.currentThread():靜態的,調取當前的線程* 4.getName():獲取此線程的名字* 5.setName():設置此線程的名字* 6.yield():調用此方法的線程釋放當前CPU的執行權* 7.join():在A線程中調用B線程的join()方法,表示:當執行到此方法,A線程停止執行,直至B線程執行完畢,* A線程再接著join()之后的代碼執行* 8.isAlive():判斷當前線程是否還存活* 9.sleep(long l):顯式的讓當前線程睡眠l毫秒* 10.線程通信:wait() notify() notifyAll()* * 設置線程的優先級* getPriority() :返回線程優先值 setPriority(int newPriority) :改變線程的優先級*/ class SubThread1 extends Thread {public void run() {for (int i = 1; i <= 100; i++) {// try {// Thread.currentThread().sleep(1000);// } catch (InterruptedException e) {// // TODO Auto-generated catch block// e.printStackTrace();// }System.out.println(Thread.currentThread().getName() + ":"+ Thread.currentThread().getPriority() + ":" + i);}} }public class TestThread1 {public static void main(String[] args) {SubThread1 st1 = new SubThread1();st1.setName("子線程1");st1.setPriority(Thread.MAX_PRIORITY);st1.start();Thread.currentThread().setName("========主線程");for (int i = 1; i <= 100; i++) {System.out.println(Thread.currentThread().getName() + ":"+ Thread.currentThread().getPriority() + ":" + i);// if(i % 10 == 0){// Thread.currentThread().yield();// }// if(i == 20){// try {// st1.join();// } catch (InterruptedException e) {// // TODO Auto-generated catch block// e.printStackTrace();// }// }}System.out.println(st1.isAlive());} }線程的開啟過程:
2.如何創建java程序的線程(重點)
方式一:繼承于Thread類
class PrintNum extends Thread{public void 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);//10p2.setPriority(Thread.MIN_PRIORITY);//1p1.start();p2.start();} }方式二:實現Runnable接口
class SubThread implements Runnable{public void 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);Thread t2 = new Thread(s);t1.setName("線程1");t2.setName("線程2");t1.start();t2.start();} }兩種方式的對比:
哪個比較好?實現的方式較好。class Thread implements Runnable
①解決了單繼承的局限性。②如果多個線程有共享數據的話,建議使用實現方式,同時,共享數據所在的類可以作為Runnable接口的實現類。
聯系:
繼承的方式其實也是實現的Runnable接口。
線程里的常用方法:
start() run() currentThread() getName() setName(String name) yield() join() sleep() isAlive() getPriority() setPriority(int i); wait() notify() notifyAll()
補充:線程的分類
Java中的線程分為兩類:一種是守護線程,一種是用戶線程。
- 它們在幾乎每個方面都是相同的,唯一的區別是判斷JVM何時離開。
- 守護線程是用來服務用戶線程的,通過在start()方法前調用thread.setDaemon(true)可以把一個用戶線程變成一個守護線程。
- Java垃圾回收就是一個典型的守護線程。
- 若JVM中都是守護線程,當前JVM將退出。
3.線程的生命周期
4.線程的同步機制(重點、難點)
前提:如果我們創建的多個線程,存在著共享數據,那么就有可能出現線程的安全問題:當其中一個線程操作共享數據時,還未操作完成,另外的線程就參與進來,導致對共享數據的操作出現問題。
解決方式:要求一個線程操作共享數據時,只有當其完成操作完成共享數據,其它線程才有機會執行共享數據。
方式一:
同步代碼塊:
synchronized(同步監視器){
//操作共享數據的代碼
}
注:1.同步監視器:俗稱鎖,任何一個類的對象都可以才充當鎖。要想保證線程的安全,必須要求所有的線程共用同一把鎖!
2.使用實現Runnable接口的方式創建多線程的話,同步代碼塊中的鎖,可以考慮是this。如果使用繼承Thread類的方式,慎用this!
3.共享數據:多個線程需要共同操作的變量。 明確哪部分是操作共享數據的代碼。
方式二:
同步方法:將操作共享數據的方法聲明為synchronized。
比如:public synchronized void show(){ //操作共享數據的代碼}
注:1.對于非靜態的方法而言,使用同步的話,默認鎖為:this。如果使用在繼承的方式實現多線程的話,慎用!
2.對于靜態的方法,如果使用同步,默認的鎖為:當前類本身。以單例的懶漢式為例。 Class clazz = Singleton.class
總結: 釋放鎖:wait();
不釋放鎖: sleep() yield() suspend() (過時,可能導致死鎖)
1.同步機制例題:售票
//使用實現Runnable接口的方式,售票 /** 此程序存在線程的安全問題:打印車票時,會出現重票、錯票* 1.線程安全問題存在的原因?* 由于一個線程在操作共享數據過程中,未執行完畢的情況下,另外的線程參與進來,導致共享數據存在了安全問題。* * 2.如何來解決線程的安全問題?* 必須讓一個線程操作共享數據完畢以后,其它線程才有機會參與共享數據的操作。* * 3.java如何實現線程的安全:線程的同步機制* * 方式一:同步代碼塊* synchronized(同步監視器){* //需要被同步的代碼塊(即為操作共享數據的代碼)* }* 1.共享數據:多個線程共同操作的同一個數據(變量)* 2.同步監視器:由一個類的對象來充當。哪個線程獲取此監視器,誰就執行大括號里被同步的代碼。俗稱:鎖* 要求:所有的線程必須共用同一把鎖!* 注:在實現的方式中,考慮同步的話,可以使用this來充當鎖。但是在繼承的方式中,慎用this!* * 方式二:同步方法* 將操作共享數據的方法聲明為synchronized。即此方法為同步方法,能夠保證當其中一個線程執行* 此方法時,其它線程在外等待直至此線程執行完此方法。* >同步方法的鎖:this* * 4.線程的同步的弊端:由于同一個時間只能有一個線程訪問共享數據,效率變低了。* */class Window4 implements Runnable {int ticket = 100;// 共享數據public void run() {while (true) {show();}}public synchronized void show() {if (ticket > 0) {try {Thread.currentThread().sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName() + "售票,票號為:"+ ticket--);}} }public class TestWindow4 {public static void main(String[] args) {Window4 w = new Window4();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();} }2.懶漢式改進(加入同步機制)
//關于懶漢式的線程安全問題:使用同步機制 //對于一般的方法內,使用同步代碼塊,可以考慮使用this。 //對于靜態方法而言,使用當前類本身充當鎖。 class Singleton {private Singleton() {}private static Singleton instance = null;public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;} }public class TestSingleton {public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2);// Class clazz = Singleton.class;} }死鎖:
不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖
死鎖是我們在使用同步時,需要避免的問題!
1.死鎖的簡單例子:
//死鎖的問題:處理線程同步時容易出現。 //不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖 //寫代碼時,要避免死鎖! public class TestDeadLock {static StringBuffer sb1 = new StringBuffer();static StringBuffer sb2 = new StringBuffer();public static void main(String[] args) {new Thread() {public void run() {synchronized (sb1) {try {Thread.currentThread().sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}sb1.append("A");synchronized (sb2) {sb2.append("B");System.out.println(sb1);System.out.println(sb2);}}}}.start();new Thread() {public void run() {synchronized (sb2) {try {Thread.currentThread().sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}sb1.append("C");synchronized (sb1) {sb2.append("D");System.out.println(sb1);System.out.println(sb2);}}}}.start();}}5.線程的通信
wait() 與 notify() 和 notifyAll()
- wait():令當前線程掛起并放棄CPU、同步資源,使別的線程可訪問并修改共享資源,而當前線程排隊等候再次對資源的訪問
- notify():喚醒正在排隊等待同步資源的線程中優先級最高者結束等待
- notifyAll ():喚醒正在排隊等待資源的所有線程結束等待.
Java.lang.Object提供的這三個方法只有在synchronized方法或synchronized代碼塊中才能使用,否則會報java.lang.IllegalMonitorStateException異常
如下的三個方法必須使用在同步代碼塊或同步方法中!
wait():當在同步中,執行到此方法,則此線程“等待”,直至其他線程執行notify()的方法,將其喚醒,喚醒后繼續其wait()后的代碼
notify()/notifyAll():在同步中,執行到此方法,則喚醒其他的某一個或所有的被wait的線程。
例題:1.兩個線程交替打印1-100自然數 2.生產者、消費者的例子
兩個線程交替打印1-100自然數
//線程通信。如下的三個關鍵字使用的話,都得在同步代碼塊或同步方法中。 //wait():一旦一個線程執行到wait(),就釋放當前的鎖。 //notify()/notifyAll():喚醒wait的一個或所有的線程 //使用兩個線程打印 1-100. 線程1, 線程2 交替打印class PrintNum implements Runnable {int num = 1;Object obj = new Object();public void run() {while (true) {synchronized (obj) {obj.notify();if (num <= 100) {try {Thread.currentThread().sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":"+ num);num++;} else {break;}try {obj.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}}public class TestCommunication {public static void main(String[] args) {PrintNum p = new PrintNum();Thread t1 = new Thread(p);Thread t2 = new Thread(p);t1.setName("甲");t2.setName("乙");t1.start();t2.start();} }生產者、消費者問題:
/** 生產者/消費者問題* 生產者(Productor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品,* 店員一次只能持有固定數量的產品(比如:20),如果生產者試圖生產更多的產品,店員會叫生產者停一下,* 如果店中有空位放產品了再通知生產者繼續生產;如果店中沒有產品了,店員會告訴消費者等一下,* 如果店中有產品了再通知消費者來取走產品。分析:1.是否涉及到多線程的問題?是!生產者、消費者2.是否涉及到共享數據?有!考慮線程的安全3.此共享數據是誰?即為產品的數量4.是否涉及到線程的通信呢?存在這生產者與消費者的通信*/ class Clerk{//店員int product;public synchronized void addProduct(){//生產產品if(product >= 20){try {wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}else{product++;System.out.println(Thread.currentThread().getName() + ":生產了第" + product + "個產品");notifyAll();}}public synchronized void consumeProduct(){//消費產品if(product <= 0){try {wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}else{System.out.println(Thread.currentThread().getName() + ":消費了第" + product + "個產品");product--;notifyAll();}} }class Producer implements Runnable{//生產者Clerk clerk;public Producer(Clerk clerk){this.clerk = clerk;}public void run(){System.out.println("生產者開始生產產品");while(true){try {Thread.currentThread().sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}clerk.addProduct();}} } class Consumer implements Runnable{//消費者Clerk clerk;public Consumer(Clerk clerk){this.clerk = clerk;}public void run(){System.out.println("消費者消費產品");while(true){try {Thread.currentThread().sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}clerk.consumeProduct();}} }public class TestProduceConsume {public static void main(String[] args) {Clerk clerk = new Clerk();Producer p1 = new Producer(clerk);Consumer c1 = new Consumer(clerk);Thread t1 = new Thread(p1);//一個生產者的線程Thread t3 = new Thread(p1);Thread t2 = new Thread(c1);//一個消費者的線程t1.setName("生產者1");t2.setName("消費者1");t3.setName("生產者2");t1.start();t2.start();t3.start();} }另加:菜鳥教程 Java多線程編程
Java多線程的使用:
有效利用多線程的關鍵是理解程序是并發執行而不是串行執行的。例如:程序中有兩個子系統需要并發執行,這時候就需要利用多線程編程。
通過對多線程的使用,可以編寫出非常高效的程序。不過請注意,如果你創建太多的線程,程序執行的效率實際上是降低了,而不是提升了。
請記住,上下文的切換開銷也很重要,如果你創建了太多的線程,CPU 花費在上下文的切換的時間將多于執行程序的時間!
線程池:
1、線程池,其實就是一個容納多個線程的容器,其中的線程可以反復使用,省去了頻繁創建線程對象的操作,無需反復創建線程而消耗過多資源。(是什么)
2、那么,我們為什么需要用到線程池呢?每次用的時候手動創建不行嗎?
在java中,如果每個請求到達就創建一個新線程,開銷是相當大的。在實際使用中,創建和銷毀線程花費的時間和消耗的系統資源都相當大,甚至可能要比在處理實際的用戶請求的時間和資源要多的多。除了創建和銷毀線程的開銷之外,活動的線程也需要消耗系統資源。如果在一個jvm里創建太多的線程,可能會使系統由于過度消耗內存或”切換過度”而導致系統資源不足。為了防止資源不足,需要采取一些辦法來限制任何給定時刻處理的請求數目,盡可能減少創建和銷毀線程的次數,特別是一些資源耗費比較大的線程的創建和銷毀,盡量利用已有對象來進行服務。(為什么)
線程池主要用來解決線程生命周期開銷問題和資源不足問題。通過對多個任務重復使用線程,線程創建的開銷就被分攤到了多個任務上了,而且由于在請求到達時線程已經存在,所以消除了線程創建所帶來的延遲。這樣,就可以立即為請求服務,使用應用程序響應更快;另外,通過適當的調整線程中的線程數目可以防止出現資源不足的情況。(什么用)
3、線程池都是通過線程池工廠創建,再調用線程池中的方法獲取線程,再通過線程去執行任務方法。
4、這里介紹兩種使用線程池創建線程的方法
- 1):使用Runnable接口創建線程池
- 2)使用Callable接口創建線程池
總結
以上是生活随笔為你收集整理的第十一章:Java_多线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第十章:Java_IO流
- 下一篇: 第十二章:Java_常用类