重點(diǎn)部分:線程的創(chuàng)建與使用、線程的同步
?
基本概念:程序、進(jìn)程、線程
程序(program)是為完成特定任務(wù)、用某種語言編寫的一組指令的集合。即指一段靜態(tài)的代碼,靜態(tài)對象。
?
進(jìn)程(process)是程序的一次執(zhí)行過程,或是正在運(yùn)行的一個程序。是一個動態(tài)的過程:有它自身的產(chǎn)生、存在和消亡的過程。——生命周期
?
線程(thread),進(jìn)程可進(jìn)一步細(xì)化為線程,是一個程序內(nèi)部的一條執(zhí)行路徑。
?
并行:多個CPU同時執(zhí)行多個任務(wù)。比如:多個人同時做不同的事。
并發(fā):一個CPU(采用時間片)同時執(zhí)行多個任務(wù)。比如:秒殺、多個人做同一件事。
?
一個Java應(yīng)用程序java.exe,其實(shí)至少有三個線程:main()主線程,gc()垃圾回收線程,異常處理線程。當(dāng)然如果發(fā)生異常,會影響主線程。
?
使用多線程的優(yōu)點(diǎn):
1. 提高應(yīng)用程序的響應(yīng)。對圖形化界面更有意義,可增強(qiáng)用戶體驗(yàn)。
2. 提高計算機(jī)系統(tǒng)CPU的利用率
3. 改善程序結(jié)構(gòu)。將既長又復(fù)雜的進(jìn)程分為多個線程,獨(dú)立運(yùn)行,利于理解和修改
?
?
多線程的創(chuàng)建與使用
?
一、Thread類
構(gòu)造器 Thread()://創(chuàng)建新的Thread對象 Thread(String threadname)://創(chuàng)建線程并指定線程實(shí)例名 Thread(Runnable target)://指定創(chuàng)建線程的目標(biāo)對象,它實(shí)現(xiàn)了Runnable接口中的run方法 Thread(Runnable target, String name)://創(chuàng)建新的Thread對象
?
二、API中創(chuàng)建線程的兩種方式
JDK1.5之前創(chuàng)建新執(zhí)行線程有兩種方法:繼承Thread類的方式
?實(shí)現(xiàn)Runnable接口的方式
方式一:繼承Thread類
1) 定義子類繼承Thread類。
2) 子類中重寫Thread類中的run方法。
3) 創(chuàng)建Thread子類對象,即創(chuàng)建了線程對象。
4) 調(diào)用線程對象start方法:啟動線程,調(diào)用run方法。
/** * 多線程的創(chuàng)建,方式一:繼承于Thread類 * 1. 創(chuàng)建一個繼承于Thread類的子類 * 2. 重寫Thread類的run() --> 將此線程執(zhí)行的操作聲明在run()中 * 3. 創(chuàng)建Thread類的子類的對象 * 4. 通過此對象調(diào)用start() * <p> * 例子:遍歷100以內(nèi)的所有的偶數(shù) * */ //1. 創(chuàng)建一個繼承于Thread類的子類 class MyThread extends Thread { //2. 重寫Thread類的run() @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest { public static void main(String[] args) { //3. 創(chuàng)建Thread類的子類的對象 MyThread t1 = new MyThread(); //4.通過此對象調(diào)用start():①啟動當(dāng)前線程 ② 調(diào)用當(dāng)前線程的run() t1.start(); //問題一:我們不能通過直接調(diào)用run()的方式啟動線程。用run的時候,只會啟動一個線程(main主線程),只是體現(xiàn)了方法的調(diào)用,而不是多線程 // t1.run(); //問題二:再啟動一個線程,遍歷100以內(nèi)的偶數(shù)。不可以還讓已經(jīng)start()的線程去執(zhí)行。會報IllegalThreadStateException // t1.start(); //我們需要重新創(chuàng)建一個線程的對象 MyThread t2 = new MyThread(); t2.start(); //如下操作仍然是在main線程中執(zhí)行的。 for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************"); } } } }
?
方式二:實(shí)現(xiàn)Runnable接口
1) 定義子類,實(shí)現(xiàn)Runnable接口。
2) 子類中重寫Runnable接口中的run方法。
3) 通過Thread類含參構(gòu)造器創(chuàng)建線程對象。
4) 將Runnable接口的子類對象作為實(shí)際參數(shù)傳遞給Thread類的構(gòu)造器中。
5) 調(diào)用Thread類的start方法:開啟線程,調(diào)用Runnable子類接口的run方法。
/* * 創(chuàng)建多線程的方式二:實(shí)現(xiàn)Runnable接口 * 1. 創(chuàng)建一個實(shí)現(xiàn)了Runnable接口的類 * 2. 實(shí)現(xiàn)類去實(shí)現(xiàn)Runnable中的抽象方法:run() * 3. 創(chuàng)建實(shí)現(xiàn)類的對象 * 4. 將此對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread類的對象 * 5. 通過Thread類的對象調(diào)用start() * * * 比較創(chuàng)建線程的兩種方式。 * 開發(fā)中:優(yōu)先選擇:實(shí)現(xiàn)Runnable接口的方式 * 原因:1. 實(shí)現(xiàn)的方式?jīng)]有類的單繼承性的局限性 * 2. 實(shí)現(xiàn)的方式更適合來處理多個線程有共享數(shù)據(jù)的情況。 * * 聯(lián)系:public class Thread implements Runnable * 相同點(diǎn):兩種方式都需要重寫run(),將創(chuàng)建線程要執(zhí)行的邏輯聲明在run()中。 */ //1. 創(chuàng)建一個實(shí)現(xiàn)了Runnable接口的類 class MThread implements Runnable{ //2. 實(shí)現(xiàn)類去實(shí)現(xiàn)Runnable中的抽象方法:run() @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest1 { public static void main(String[] args) { //3. 創(chuàng)建實(shí)現(xiàn)類的對象 MThread mThread = new MThread(); //4. 將此對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread類的對象 Thread t1 = new Thread(mThread); t1.setName("線程1"); //5. 通過Thread類的對象調(diào)用start():① 啟動線程 ②調(diào)用當(dāng)前線程的run()-->調(diào)用了Runnable類型的target的run() t1.start(); //再啟動一個線程,遍歷100以內(nèi)的偶數(shù) Thread t2 = new Thread(mThread); t2.setName("線程2"); t2.start(); } }
?
public class ThreadDemo { public static void main(String[] args) { // MyThread1 m1 = new MyThread1(); // MyThread2 m2 = new MyThread2(); // // m1.start(); // m2.start(); //創(chuàng)建Thread類的匿名子類的方式 new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } }.start(); new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } }.start(); } } class MyThread1 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } class MyThread2 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } }
?
三、繼承方式和實(shí)現(xiàn)方式的聯(lián)系與區(qū)別
1.? 區(qū)別
繼承Thread:線程代碼存放Thread子類run方法中。
實(shí)現(xiàn)Runnable:線程代碼存在接口的子類的run方法。
2. 實(shí)現(xiàn)方式的好處:避免了單繼承的局限性
多個線程可以共享同一個接口實(shí)現(xiàn)類的對象,非常適合多個相同線程來處理同一份資源。
Thread中的常用方法
測試Thread中的常用方法:
1. start():啟動當(dāng)前線程;調(diào)用當(dāng)前線程的run()
2. run(): 通常需要重寫Thread類中的此方法,將創(chuàng)建的線程要執(zhí)行的操作聲明在此方法中
3. currentThread():靜態(tài)方法,返回執(zhí)行當(dāng)前代碼的線程
4. getName():獲取當(dāng)前線程的名字
5. setName():設(shè)置當(dāng)前線程的名字
6. yield():釋放當(dāng)前cpu的執(zhí)行權(quán)
7. join():在線程a中調(diào)用線程b的join(),此時線程a就進(jìn)入阻塞狀態(tài),直到線程b完全執(zhí)行完以后,線程a才結(jié)束阻塞狀態(tài)。
8. stop():已過時。當(dāng)執(zhí)行此方法時,強(qiáng)制結(jié)束當(dāng)前線程。
9. sleep(long millitime):讓當(dāng)前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內(nèi),當(dāng)前線程是阻塞狀態(tài)。
10. isAlive():判斷當(dāng)前線程是否存活
線程的優(yōu)先級:
1.
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 -->默認(rèn)優(yōu)先級
2.如何獲取和設(shè)置當(dāng)前線程的優(yōu)先級:
getPriority():獲取線程的優(yōu)先級
setPriority(int p):設(shè)置線程的優(yōu)先級
說明:高優(yōu)先級的線程要搶占低優(yōu)先級線程cpu的執(zhí)行權(quán)。但是只是從概率上講,高優(yōu)先級的線程高概率的情況下被執(zhí)行。并不意味著只有當(dāng)高優(yōu)先級的線程執(zhí)行完以后,低優(yōu)先級的線程才執(zhí)行。
class HelloThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ // try { // sleep(10); // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i); } // if(i % 20 == 0){ // yield(); // } } } public HelloThread(String name){ super(name); } } public class ThreadMethodTest { public static void main(String[] args) { HelloThread h1 = new HelloThread("Thread:1"); // h1.setName("線程一"); //設(shè)置分線程的優(yōu)先級 h1.setPriority(Thread.MAX_PRIORITY); h1.start(); //給主線程命名 Thread.currentThread().setName("主線程"); Thread.currentThread().setPriority(Thread.MIN_PRIORITY); for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i); } // if(i == 20){ // try { // h1.join(); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } } // System.out.println(h1.isAlive()); } }
?
?
?
線程的生命周期
?
?
?
?
?
線程的同步
在Java中,我們通過同步機(jī)制,來解決線程的安全問題。
方式一: 同步代碼塊:
//1. 同步代碼塊: synchronized (同步監(jiān)視器(對象)){ //操作共享數(shù)據(jù)的代碼; } //2. synchronized還可以放在方法聲明中,表示整個方法為同步方法。 例如: public synchronized void show (String name){ …. }
說明:1.操作共享數(shù)據(jù)的代碼,即為需要被同步的代碼。 -->不能包含代碼多了,也不能包含代碼少了。
2.共享數(shù)據(jù):多個線程共同操作的變量。比如:ticket就是共享數(shù)據(jù)。一個線程操作的時候,其他的線程不能操作。
3.同步監(jiān)視器,俗稱:鎖。任何一個類的對象,都可以充當(dāng)鎖。
要求:多個線程必須要共用同一把鎖。
補(bǔ)充:在實(shí)現(xiàn)Runnable接口創(chuàng)建多線程的方式中,我們可以考慮使用this充當(dāng)同步監(jiān)視器。但是慎用。
方式二:同步方法。
如果操作共享數(shù)據(jù)的代碼完整的聲明在一個方法中,我們不妨將此方法聲明同步的。
5.同步的方式,解決了線程的安全問題。---好處
操作同步代碼時,只能有一個線程參與,其他線程等待。相當(dāng)于是一個單線程的過程,效率低。 ---局限性
/** * 例子:創(chuàng)建三個窗口賣票,總票數(shù)為100張.使用實(shí)現(xiàn)Runnable接口的方式 * * 1.問題:賣票過程中,出現(xiàn)了重票、錯票 -->出現(xiàn)了線程的安全問題 * 2.問題出現(xiàn)的原因:當(dāng)某個線程操作車票的過程中,尚未操作完成時,其他線程參與進(jìn)來,也操作車票。 * 3.如何解決:當(dāng)一個線程a在操作ticket的時候,其他線程不能參與進(jìn)來。直到線程a操作完ticket時,其他 * 線程才可以開始操作ticket。這種情況即使線程a出現(xiàn)了阻塞,也不能被改變。 * */ class Window1 implements Runnable{ private int ticket = 100; // Object obj = new Object(); // Dog dog = new Dog(); @Override public void run() { // Object obj = new Object(); while(true){ synchronized (this){//此時的this:唯一的Window1的對象 //方式二:synchronized (dog) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket); ticket--; } else { break; } } } } } public class WindowTest1 { public static void main(String[] args) { Window1 w = new Window1(); 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(); } } class Dog{ }
?
package com.atguigu.java; /** * 使用同步方法解決實(shí)現(xiàn)Runnable接口的線程安全問題 * * * 關(guān)于同步方法的總結(jié): * 1. 同步方法仍然涉及到同步監(jiān)視器,只是不需要我們顯式的聲明。 * 2. 非靜態(tài)的同步方法,同步監(jiān)視器是:this * 靜態(tài)的同步方法,同步監(jiān)視器是:當(dāng)前類本身 * */ class Window3 implements Runnable { private int ticket = 100; @Override public void run() { while (true) { show(); } } private synchronized void show(){//同步監(jiān)視器:this //synchronized (this){ if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket); ticket--; } //} } } public class WindowTest3 { public static void main(String[] args) { Window3 w = new Window3(); 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(); } }
?
/** * 使用同步方法處理繼承Thread類的方式中的線程安全問題 */ class Window4 extends Thread { private static int ticket = 100; @Override public void run() { while (true) { show(); } } private static synchronized void show(){//同步監(jiān)視器:Window4.class //private synchronized void show(){ //同步監(jiān)視器:t1,t2,t3。此種解決方式是錯誤的 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket); ticket--; } } } public class WindowTest4 { public static void main(String[] args) { Window4 t1 = new Window4(); Window4 t2 = new Window4(); Window4 t3 = new Window4(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
?
/** * 使用同步機(jī)制將單例模式中的懶漢式改寫為線程安全的 */ public class BankTest { } class Bank{ private Bank(){} private static Bank instance = null; public static Bank getInstance(){ //方式一:效率稍差 // synchronized (Bank.class) { // if(instance == null){ // // instance = new Bank(); // } // return instance; // } //方式二:效率更高 if(instance == null){ synchronized (Bank.class) { if(instance == null){ instance = new Bank(); } } } return instance; } }
?
?
死鎖
死鎖:不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖。
?
解決方法:
專門的算法、原則
盡量減少同步資源的定義
盡量避免嵌套同步
/* * 演示線程的死鎖問題 * * 1.死鎖的理解:不同的線程分別占用對方需要的同步資源不放棄, * 都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖 * * 2.說明: * 1)出現(xiàn)死鎖后,不會出現(xiàn)異常,不會出現(xiàn)提示,只是所有的線程都處于阻塞狀態(tài),無法繼續(xù) * 2)我們使用同步時,要避免出現(xiàn)死鎖。 * */ public class ThreadTest { public static void main(String[] args) { StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer(); new Thread(){ @Override public void run() { synchronized (s1){ s1.append("a"); s2.append("1"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2){ s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } }.start(); new Thread(new Runnable() { @Override public void run() { synchronized (s2){ s1.append("c"); s2.append("3"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s1){ s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }).start(); } }
?
?
解決線程安全問題的方式三:Lock(鎖)
從JDK 5.0開始,Java提供了更強(qiáng)大的線程同步機(jī)制——通過顯式定義同步鎖對象來實(shí)現(xiàn)同步。同步鎖使用Lock對象充當(dāng)。
//java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進(jìn)行訪問的工具 class A{ private final ReentrantLock lock = new ReenTrantLock(); public void m(){ lock.lock(); try{ //保證線程安全的代碼; } finally{ lock.unlock(); } } } //注意:如果同步代碼有異常,要將unlock()寫入finally語句塊
?
?
/** * 解決線程安全問題的方式三:Lock鎖 --- JDK5.0新增 * * 1. 面試題:synchronized 與 Lock的異同? * 相同:二者都可以解決線程安全問題 * 不同:synchronized機(jī)制在執(zhí)行完相應(yīng)的同步代碼以后,自動的釋放同步監(jiān)視器 * Lock需要手動的啟動同步(lock()),同時結(jié)束同步也需要手動的實(shí)現(xiàn)(unlock()) * * 2.優(yōu)先使用順序: * Lock ? 同步代碼塊(已經(jīng)進(jìn)入了方法體,分配了相應(yīng)資源) ? 同步方法(在方法體之外) * * * 面試題:如何解決線程安全問題?有幾種方式? * 同步代碼塊、同步方法、lock鎖 */ class Window implements Runnable{ private int ticket = 100; //1.實(shí)例化ReentrantLock private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while(true){ try{ //2.調(diào)用鎖定方法lock() lock.lock(); if(ticket > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":售票,票號為:" + ticket); ticket--; }else{ break; } }finally { //3.調(diào)用解鎖方法:unlock() lock.unlock(); } } } } public class LockTest { public static void main(String[] args) { Window w = new Window(); 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(); } }
?
synchronized 與 Lock 的對比
1. Lock是顯式鎖(手動開啟和關(guān)閉鎖,別忘記關(guān)閉鎖),synchronized是隱式鎖,出了作用域自動釋放
2. Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
3. 使用Lock鎖,JVM將花費(fèi)較少的時間來調(diào)度線程,性能更好。并且具有更好的擴(kuò)展性(提供更多的子類)
優(yōu)先使用順序:
Lock --> 同步代碼塊(已經(jīng)進(jìn)入了方法體,分配了相應(yīng)資源)---> 同步方法(在方法體之外)
線程的通信
一、wait() 與 notify() 和 notifyAll()
一、wait() 與 notify() 和 notifyAll()
1.? wait():令當(dāng)前線程掛起并放棄CPU、同步資源并等待,使別的線程可訪問并修改共享資源,而當(dāng)前線程排隊等候其他線程調(diào)用notify()或notifyAll()方法喚醒,喚醒后等待重新獲得對監(jiān)視器的所有權(quán)后才能繼續(xù)執(zhí)行。
2.? notify():喚醒正在排隊等待同步資源的線程中優(yōu)先級最高者結(jié)束等待
3.? notifyAll ():喚醒正在排隊等待資源的所有線程結(jié)束等待.
這三個方法只有在synchronized方法或synchronized代碼塊中才能使用,否則會報java.lang.IllegalMonitorStateException異常。
因?yàn)檫@三個方法必須有鎖對象調(diào)用,而任意對象都可以作為synchronized的同步鎖,因此這三個方法只能在Object類中聲明。
wait() 方法
在當(dāng)前線程中調(diào)用方法: 對象名.wait()
使當(dāng)前線程進(jìn)入等待(某對象)狀態(tài) ,直到另一線程對該對象發(fā)出 notify(或notifyAll) 為止。
調(diào)用方法的必要條件:當(dāng)前線程必須具有對該對象的監(jiān)控權(quán)(加鎖)
調(diào)用此方法后,當(dāng)前線程將釋放對象監(jiān)控權(quán) ,然后進(jìn)入等待
在當(dāng)前線程被notify后,要重新獲得監(jiān)控權(quán),然后從斷點(diǎn)處繼續(xù)代碼的執(zhí)行。
notify()/notifyAll()
在當(dāng)前線程中調(diào)用方法: 對象名.notify()
功能:喚醒等待該對象監(jiān)控權(quán)的一個/所有線程。
調(diào)用方法的必要條件:當(dāng)前線程必須具有對該對象的監(jiān)控權(quán)(加鎖)
/** * 線程通信的例子:使用兩個線程打印 1-100。線程1, 線程2 交替打印 * * 涉及到的三個方法: * wait():一旦執(zhí)行此方法,當(dāng)前線程就進(jìn)入阻塞狀態(tài),并釋放同步監(jiān)視器。 * notify():一旦執(zhí)行此方法,就會喚醒被wait的一個線程。如果有多個線程被wait,就喚醒優(yōu)先級高的那個。 * notifyAll():一旦執(zhí)行此方法,就會喚醒所有被wait的線程。 * * 說明: * 1.wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中。 * 2.wait(),notify(),notifyAll()三個方法的調(diào)用者必須是同步代碼塊或同步方法中的同步監(jiān)視器。 * 否則,會出現(xiàn)IllegalMonitorStateException異常 * 3.wait(),notify(),notifyAll()三個方法是定義在java.lang.Object類中。 * * 面試題:sleep() 和 wait()的異同? * 1.相同點(diǎn):一旦執(zhí)行方法,都可以使得當(dāng)前的線程進(jìn)入阻塞狀態(tài)。 * 2.不同點(diǎn):1)兩個方法聲明的位置不同:Thread類中聲明sleep() , Object類中聲明wait() * 2)調(diào)用的要求不同:sleep()可以在任何需要的場景下調(diào)用。 wait()必須使用在同步代碼塊或同步方法中 * 3)關(guān)于是否釋放同步監(jiān)視器:如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。 */ class Number implements Runnable{ private int number = 1; private Object obj = new Object(); @Override public void run() { while(true){ synchronized (obj) { obj.notify(); if(number <= 100){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + number); number++; try { //使得調(diào)用如下wait()方法的線程進(jìn)入阻塞狀態(tài) obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ break; } } } } } public class CommunicationTest { public static void main(String[] args) { Number number = new Number(); Thread t1 = new Thread(number); Thread t2 = new Thread(number); t1.setName("線程1"); t2.setName("線程2"); t1.start(); t2.start(); } }
?
JDK5.0新增線程創(chuàng)建方式
新增方式一:實(shí)現(xiàn)Callable接口
與使用Runnable相比, Callable功能更強(qiáng)大些
相比run()方法,可以有返回值
方法可以拋出異常
支持泛型的返回值
需要借助FutureTask類,比如獲取返回結(jié)果
?Future接口
可以對具體Runnable、Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行取消、查詢是否完成、獲取結(jié)果等。
FutrueTask是Futrue接口的唯一的實(shí)現(xiàn)類
FutureTask 同時實(shí)現(xiàn)了Runnable, Future接口。它既可以作為Runnable被線程執(zhí)行,又可以作為Future得到Callable的返回值
package com.atguigu.java2; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * 創(chuàng)建線程的方式三:實(shí)現(xiàn)Callable接口。 --- JDK 5.0新增 * * * 如何理解實(shí)現(xiàn)Callable接口的方式創(chuàng)建多線程比實(shí)現(xiàn)Runnable接口創(chuàng)建多線程方式強(qiáng)大? * 1. call()可以有返回值的。 * 2. call()可以拋出異常,被外面的操作捕獲,獲取異常的信息 * 3. Callable是支持泛型的 * */ //1.創(chuàng)建一個實(shí)現(xiàn)Callable的實(shí)現(xiàn)類 class NumThread implements Callable{ //2.實(shí)現(xiàn)call方法,將此線程需要執(zhí)行的操作聲明在call()中 @Override public Object call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { if(i % 2 == 0){ System.out.println(i); sum += i; } } return sum; } } public class ThreadNew { public static void main(String[] args) { //3.創(chuàng)建Callable接口實(shí)現(xiàn)類的對象 NumThread numThread = new NumThread(); //4.將此Callable接口實(shí)現(xiàn)類的對象作為傳遞到FutureTask構(gòu)造器中,創(chuàng)建FutureTask的對象 FutureTask futureTask = new FutureTask(numThread); //5.將FutureTask的對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread對象,并調(diào)用start() new Thread(futureTask).start(); try { //6.獲取Callable中call方法的返回值 //get()返回值即為FutureTask構(gòu)造器參數(shù)Callable實(shí)現(xiàn)類重寫的call()的返回值。 Object sum = futureTask.get(); System.out.println("總和為:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
?
新增方式二:使用線程池
提前創(chuàng)建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中??梢员苊忸l繁創(chuàng)建銷毀、實(shí)現(xiàn)重復(fù)利用。類似生活中的公共交通工具。
?
好處:
提高響應(yīng)速度(減少了創(chuàng)建新線程的時間)
降低資源消耗(重復(fù)利用線程池中線程,不需要每次都創(chuàng)建)
便于線程管理
corePoolSize:核心池的大小
maximumPoolSize:最大線程數(shù)
keepAliveTime:線程沒有任務(wù)時最多保持多長時間后會終止
?
JDK 5.0起提供了線程池相關(guān)API:ExecutorService 和 Executors
ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor
void execute(Runnable command) :執(zhí)行任務(wù)/命令,沒有返回值,一般用來執(zhí)行Runnable
<T> Future<T> submit(Callable<T> task):執(zhí)行任務(wù),有返回值,一般又來執(zhí)行Callable
void shutdown() :關(guān)閉連接池
Executors:工具類、線程池的工廠類,用于創(chuàng)建并返回不同類型的線程池
Executors.newCachedThreadPool():創(chuàng)建一個可根據(jù)需要創(chuàng)建新線程的線程池
Executors.newFixedThreadPool(n); 創(chuàng)建一個可重用固定線程數(shù)的線程池
Executors.newSingleThreadExecutor() :創(chuàng)建一個只有一個線程的線程池
Executors.newScheduledThreadPool(n):創(chuàng)建一個線程池,它可安排在給定延遲后運(yùn)行命令或者定期地執(zhí)行。
/** * 創(chuàng)建線程的方式四:使用線程池 * * 好處: * 1.提高響應(yīng)速度(減少了創(chuàng)建新線程的時間) * 2.降低資源消耗(重復(fù)利用線程池中線程,不需要每次都創(chuàng)建) * 3.便于線程管理 * corePoolSize:核心池的大小 * maximumPoolSize:最大線程數(shù) * keepAliveTime:線程沒有任務(wù)時最多保持多長時間后會終止 * * * 面試題:創(chuàng)建多線程有幾種方式?四種! */ class NumberThread implements Runnable{ @Override public void run() { for(int i = 0;i <= 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ": " + i); } } } } class NumberThread1 implements Runnable{ @Override public void run() { for(int i = 0;i <= 100;i++){ if(i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ": " + i); } } } } public class ThreadPool { public static void main(String[] args) { //1. 提供指定線程數(shù)量的線程池 ExecutorService service = Executors.newFixedThreadPool(10); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //設(shè)置線程池的屬性 // System.out.println(service.getClass()); // service1.setCorePoolSize(15); // service1.setKeepAliveTime(); //2.執(zhí)行指定的線程的操作。需要提供實(shí)現(xiàn)Runnable接口或Callable接口實(shí)現(xiàn)類的對象 service.execute(new NumberThread());//適合適用于Runnable service.execute(new NumberThread1());//適合適用于Runnable // service.submit(Callable callable);//適合使用于Callable //3.關(guān)閉連接池 service.shutdown(); } }
?
本文摘自 :https://www.cnblogs.com/