@[toc]java
是程序的一次執行過程,或是正在運行的一個程序。是一個動態的過程:有它自身的產生、存在和消亡的過程。面試
- 進程做爲資源分配的單位,系統在運行時會爲每一個進程分配不一樣的內存區域
- 程序是靜態的,進程是動態的
進程可進一步細化爲線程,是一個程序內部的一條執行路徑算法
- 若一個進程同一時間 並行執行多個線程,就是支持多線程的
- 線程做爲調度和執行的單位,每一個線程擁有獨立的運行棧和程序計數器(pc),線程切換的開銷小
- 一個進程中的多個線程共享相同的內存單元/內存地址空間---->它們從同一堆中分配對象,能夠訪問相同的變量和對象。這就使得線程間通訊更簡便、高效。但多個線程操做共享的系統資源可能就會帶來安全的隱患。
區別:設計模式
並行:多個CPU同時執行多個任務。好比:多我的同時作不一樣的事。安全
併發:一個CPU(採用時間片)同時執行多個任務。好比:秒殺、多我的作同一件事。多線程
Thread()
:建立新的Thread對象Thread(String threadname)
:建立線程並指定線程實例名Thread(Runnable target)
:指定建立線程的目標對象,它實現了Runnable接口中的run方法Thread(Runnable target, String name)
:建立新的Thread對象JDK1.5以前建立新執行線程有兩種方法:併發
繼承Thread類的方式app
實現Runnable接口的方式ide
繼承Thread類工具
定義子類繼承Thread類。
子類中重寫Thread類中的run方法。
建立Thread子類對象,即建立了線程對象。
調用線程對象start方法:啓動線程,調用run方法。
mt子線程的建立和啓動過程
IllegalThreadStateException
建立線程代碼以下:
package com.shsxt.thread;
/** * 多線程的建立,方式一:繼承於Thread類 */
//1. 建立一個繼承於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. 建立Thread類的子類的對象
MyThread mt = new MyThread();
//4.經過此對象調用start():①啓動當前線程 ② 調用當前線程的run()
mt.start();
//問題一:咱們不能經過直接調用run()的方式啓動線程。
//mt.run();
//問題二:再啓動一個線程,遍歷100之內的偶數。不能夠還讓已經start()的線程去執行。會報IllegalThreadStateException
//t1.start();
//咱們須要從新建立一個線程的對象
MyThread mt2 = new MyThread();
mt2.start();
//以下操做仍然是在main線程中執行的。
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i + "******main*******");
}
}
}
}
複製代碼
public class ThreadDemo {
public static void main(String[] args) {
//建立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();
}
}
複製代碼
void start()
: 啓動線程,並執行對象的run()方法
run()
: 線程在被調度時執行的操做
String getName()
: 返回線程的名稱
void setName(String name)
:設置該線程名稱
static Thread currentThread()
: 返回當前線程。在Thread子類中就是this,一般用於主線程和Runnable實現類
static void yield()
:線程讓步
暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程
join()
:在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,直到線程b徹底執行完之後,線程a才結束阻塞狀態。
static void sleep(long millis)
:(指定時間:毫秒);讓當前線程「睡眠」指定的millitime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態。
boolean isAlive()
:返回boolean,判斷線程是否還活着
調度策略
- 時間片:
![]()
- 搶佔式: 高優先級的線程搶佔CPU
Java的調度方法
- 同優先級線程組成先進先出隊列(先到先服務),使用時間片策略
- 對高優先級,使用優先調度的搶佔式策略
線程的優先級等級:
MAX_PRIORITY :10 MIN _PRIORITY :1 NORM_PRIORITY :5
涉及的方法
getPriority() :返回線程優先值 setPriority(int newPriority) :改變線程的優先級
注意點:
線程建立時繼承父線程的優先級
低優先級只是得到調度的機率低,並不是必定是在高優先級線程以後才被調用
關於方法的一些使用,代碼以下:
package com.shsxt.thread;
class HelloThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":" + getPriority() + ":" + i);
}
//yield()方法的使用(禮讓線程)
// if (i % 20 == 0) {
// yield();
// }
}
}
public HelloThread(String name) {
//給子線程賦名字
super(name);
}
}
public class ThreadMethod {
public static void main(String[] args) {
//第一種方式:給子線程賦名字
HelloThread h1 = new HelloThread("Thread:1");
//第二種方式:給子線程賦名字
//h1.setName("線程一");
//給子線程設置優先級
//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);
}
//調用join()方法,當i==20時主線程阻塞,子線程運行完後,主線程才運行
if (i == 20){
try {
h1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//判斷線程是否還存活着
System.out.println(h1.isAlive());
}
}
複製代碼
使用繼承Thread類,寫一個窗口賣票的練習
class Window extends Thread{
//使用static關鍵字是防止new Window()每一個線程都有100張票
//不使用static關鍵字的話,須要用到建立線程的第二種方式實現Runnable接口
private static int tickets = 100;
@Override
public void run() {
while (true){
if (tickets>0){
System.out.println(getName()+":賣票,票號爲"+tickets);
tickets--;
}else {
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window w = new Window();
Window w1 = new Window();
Window w2 = new Window();
w.setName("窗口一");
w1.setName("窗口二");
w2.setName("窗口三");
w.start();
w1.start();
w2.start();
}
}
複製代碼
實現Runnable接口
- 建立一個實現了Runnable接口的類
- 實現類去實現Runnable中的抽象方法:run()
- 建立實現類的對象
- 將此對象做爲參數傳遞到Thread類的構造器中,建立Thread類的對象
- 經過Thread類的對象調用start()
線程建立,代碼以下:
package com.shsxt.thread;
//1. 建立一個實現了Runnable接口的類
class MyThread1 implements Runnable {
//二、實現類去實現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. 建立實現類的對象
MyThread1 myThread1 = new MyThread1();
//4. 將此對象做爲參數傳遞到Thread類的構造器中,建立Thread類的對象
Thread t1 = new Thread(myThread1);
t1.setName("線程1");
//5. 經過Thread類的對象調用start():① 啓動線程 ②調用當前線程的run()-->調用了Runnable類型的target的run()
t1.start();
//再啓動一個線程,遍歷100之內的偶數
Thread t2 = new Thread(myThread1);
t2.setName("線程2");
t2.start();
}
}
複製代碼
相同點:
兩種方式都須要重寫run(),將線程要執行的邏輯聲明在run()中。
不一樣點:
開發中:優先選擇:實現Runnable接口的方式
緣由:一、實現了Runnable接口的方式解決了類的單繼承性的侷限性
二、實現Runnable接口的方式更適合來處理多個線程有共享數據的狀況。
使用繼承Thread類,寫一個窗口賣票的練習:
package com.shsxt.thread;
/** * 建立三個窗口賣票,總票數爲100張.使用實現Runnable接口的方式 * @author Rainbow * @date 2020/7/15 10:31 */
class Window1 implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + tickets);
tickets--;
} else {
break;
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
複製代碼
要想實現多線程,必須在主線程中建立新的線程對象。Java語言使用Thread類及其子類的對象來表示線程,在它的一個完整的生命週期中一般要經歷以下的 五種狀態:
首先舉個例子看下:
package com.shsxt.day;
/** * @author Rainbow * @date 2020/7/15 9:15 */
class Window extends Thread {
private static int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(getName() + ":賣票,票號爲" + tickets);
tickets--;
} else {
break;
}
}
}
}
/** * @author Rainbow */
public class WindowTest {
public static void main(String[] args) {
Window w = new Window();
Window w1 = new Window();
Window w2 = new Window();
w.setName("窗口一");
w1.setName("窗口二");
w2.setName("窗口三");
w.start();
w1.start();
w2.start();
}
}
複製代碼
由此代碼看出,發現有線程安全問題:理想狀態下
極端狀態:
由上述代碼能夠看出出現了線程安全問題
問題的緣由:
當多條語句在操做同一個線程共享數據時,一個線程對多條語句只執行了一部分,尚未執行完,另外一個線程參與進來執行。致使共享數據的錯誤。
解決辦法:
對多條操做共享數據的語句,只能讓一個線程都執行完,在執行過程當中,其餘線程不能夠參與執行。即便有條線程發生了阻塞,也不能改變,也得等這條線程執行完畢,其餘線程才能執行
Java 對於多線程的安全問題提供了專業的解決方式 : 同步機制
同步代碼塊:
synchronized(同步監視器){
//須要被同步的代碼
}
synchronized 還能夠放在方法聲明中,表示整個方法爲同步方法 。
public synchronized void show (){ …. }
關於以上名詞的說明:
同步的代碼:操做共享數據的代碼 ----------->(同步的範圍)同步的代碼不能被同步代碼塊包含多了,也不能包含少了
***同步監視器(俗稱:鎖)***:任何一個類的對象,均可以充當鎖。要求:多個線程必須共同擁有一把鎖
同步機制中的鎖和注意事項:
一、任意對象均可以做爲同步鎖。全部對象都自動含有單一的鎖(監視器)
二、同步方法的鎖:靜態方法(類名.class)、非靜態方法(this)
三、同步代碼塊:本身指定,不少時候也是指定爲this或類名.class
注意事項:
一、必須確保使用同一個資源的 多個線程共用一把鎖,這個很是重要,不然就沒法保證共享資源的安全
二、 一個線程類中的全部靜態方法共用同一把鎖(類名.class),全部非靜態方法共用同一把鎖(this),同步代碼塊(指定需謹慎)
使用同步方式的優缺點:
優勢:解決了線程的安全問題。
缺點:操做同步代碼時,只能有一個線程參與,其餘線程等待。至關因而一個單線程的過程,效率低。
package com.shsxt.thread;
/** * 在實現Runnable接口建立多線程的方式中,咱們能夠考慮使用this充當同步監視器。 * @author Rainbow * @date 2020/7/15 16:22 */
class Window1 implements Runnable {
private int ticket = 100;
//使用同步代碼塊的第一種解決方式
Object obj = new Object();
@Override
public void run() {
while (true) {
// synchronized (obj) {
//使用同步代碼塊的第二種解決方式
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(10);
} 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();
}
}
複製代碼
package com.shsxt.thread;
/** * 在繼承Thread類建立多線程的方式中,慎用this充當同步監視器,考慮使用當前類充當同步監視器。 * * @author Rainbow * @date 2020/7/15 16:33 */
class Window2 extends Thread {
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true) {
//正確的方式:
// synchronized (obj) {
synchronized (Window2.class) { //Class clazz = Window2.class,Window2.class只會加載一次
//錯誤的方式:
// synchronized (this) {//此時的this表明着t1,t2,t3三個對象
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":賣票,票號爲:" + ticket);
ticket--;
} else {
break;
}
// }
}
// }
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 t1 = new Window2();
Window2 t2 = new Window2();
Window2 t3 = new Window2();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
複製代碼
package com.shsxt.thread;
/** * @author Rainbow * @date 2020/7/15 16:40 */
class Window3 implements Runnable {
private int ticket = 100;
boolean flag = true;
@Override
public void run() {
while (flag) {
show();
}
}
private synchronized void show() {//同步監視器:this
//至關於下面的方式
// synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);
ticket--;
} else {
flag = false;
}
// }
}
}
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();
}
}
複製代碼
package com.shsxt.thread;
/** * 使用同步方法處理繼承Thread類的方式中的線程安全問題 * @author Rainbow * @date 2020/7/15 16:59 */
class Window4 extends Thread {
static boolean flag = true;
private static int ticket = 100;
@Override
public void run() {
while (flag) {
show();
}
}
private static synchronized void show() {//同步監視器:Window4.class
//沒有static關鍵字修飾,此時的同步監視器爲:t1,t2,t3;此種解決方式是錯誤的
// private synchronized void show(){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":賣票,票號爲:" + ticket);
ticket--;
} else {
flag = false;
}
// }
}
}
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();
}
}
複製代碼
package com.shsxt.thread;
/** * 單例線程安全的懶漢模式 * @author Rainbow * @date 2020/7/15 17:08 */
class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
//方式1、效率低
// synchronized (Singleton.class) {
// if (instance == null) {
// instance = new Singleton();
// }
// return instance;
// }
//方式2、效率高
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
public class SingletonTest {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
Singleton instance3 = Singleton.getInstance();
System.out.println(instance);
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance3);
}
}
複製代碼
死鎖問題的產生
- 不一樣的線程分別佔用對方須要的同步資源不放棄,都在等待對方放棄本身須要的同步資源,就造成了線程的死鎖
- 出現死鎖後,不會出現異常,不會出現提示,只是全部的線程都處於阻塞狀態,沒法繼續
解決方法
用專門的算法、原則
儘可能減小同步資源的定義
儘可能避免嵌套同步
案例
package com.shnsxt.thread;
/** * @author Rainbow * @date 2020/7/15 21:28 */
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 {
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(){
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
}
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}.start();
}
}
複製代碼
java.util.concurrent.locks.Lock
接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨佔訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源以前應先得到Lock對象相同點:
兩者均可以解決線程安全問題
不一樣點:
一、synchronized機制在執行完相應的同步代碼之後,自動的釋放同步監視器
二、Lock須要手動的啓動同步(lock()方法)緊跟try代碼塊,同時結束同步也須要手動的實現(unlock()方法)且必須放在finally的首行
優先使用順序
Lock ---> 同步代碼塊(已經進入了方法體,分配了相應資源)----> 同步方法(在方法體以外)
使用Lock鎖的案例:
package com.shnsxt.thread;
import java.util.concurrent.locks.ReentrantLock;
/** * @author Rainbow * @date 2020/7/15 21:41 */
class Window implements Runnable {
private int ticket = 100;
//1.實例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//2.調用鎖定方法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.調用解鎖方法:unlock(),且必須放在finally的首行
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();
}
}
複製代碼
package com.shnsxt.thread;
/** * 銀行有一個帳戶。 * 有兩個儲戶分別向同一個帳戶存3000元,每次存1000,存3次。每次存完打印帳戶餘額。 * @author Rainbow * @date 2020/7/15 21:52 */
class Account{
private double balance;
public Account(double balance) {
this.balance = balance;
}
//存錢
public synchronized void deposit(double AMB) {//同步監視器:this;雖說使用繼承Thread類慎用this,可是此處的this不表明Customer,而是Account
if (AMB>0){
balance+=AMB;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":存錢成功。餘額爲:" + balance);
}
}
}
//儲戶
class Customer extends Thread{
private Account account;
public Customer(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 3 ; i++) {
account.deposit(1000);
}
}
}
public class AccountTest {
public static void main(String[] args) {
Account account = new Account(0);
Customer customer = new Customer(account);
Customer customer1 = new Customer(account);
customer.setName("甲");
customer1.setName("已");
customer.start();
customer1.start();
}
}
複製代碼
package com.shnsxt.thread;
/** * @author Rainbow * @date 2020/7/16 9:37 */
class Blank {
private String accountId;
private double balance;
public Blank(String accountId, double balance) {
this.accountId = accountId;
this.balance = balance;
}
public double getBalance() {
return balance;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Blank{" +
"accountId='" + accountId + '\'' +
", balance=" + balance +
'}';
}
}
class DrawThread extends Thread {
private Blank blank;
//取款額度
private double money;
public DrawThread(String name, Blank blank, double money) {
super(name);
this.blank = blank;
this.money = money;
}
@Override
public void run() {
synchronized (blank) {
if (blank.getBalance() > money) {
System.out.println(Thread.currentThread().getName() + ":取款成功," + "取現的金額爲" + money);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
blank.setBalance(blank.getBalance() - money);
} else {
System.out.println("取現額度超過帳戶餘額,取款失敗");
}
}
System.out.println(blank.getAccountId() + "帳戶的餘額爲:" + blank.getBalance());
}
}
public class DrawThreadTest {
public static void main(String[] args) {
Blank blank = new Blank("中國銀行", 100000.00);
DrawThread d1 = new DrawThread("張三", blank, 4000);
DrawThread d2 = new DrawThread("李四", blank, 5000);
DrawThread d3 = new DrawThread("王五", blank, 8000);
d1.start();
d2.start();
d3.start();
}
}
複製代碼
package com.shnsxt.thread;
/** * @author Rainbow * @date 2020/7/15 22:07 */
class Number implements Runnable {
private int number = 1;
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (number <= 100) {
//喚醒被wait的一個線程。
obj.notify();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得調用以下wait()方法的線程進入阻塞狀態
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();
}
}
複製代碼
wait()
:一旦執行此方法,當前線程就進入阻塞狀態,並釋放同步監視器(鎖)。notify()
:一旦執行此方法,就會喚醒被wait的一個線程,若是有多個線程被wait,就喚醒優先級高的那個。notifyAll()
:一旦執行此方法,就會喚醒全部被wait的線程。wait()
,notify()
,notifyAll()
三個方法必須使用在同步代碼塊或同步方法中。wait()
,notify()
,notifyAll()
三個方法的調用者必須是同步代碼塊或同步方法中的同步監視器(鎖)。不然,會出現IllegalMonitorStateException異常wait()
,notify()
,notifyAll()
三個方法是定義在java.lang.Object類中。相同點:一旦執行方法,均可以使得當前的線程進入阻塞狀態。
不一樣點:
一、兩個方法聲明的位置不一樣:Thread類中聲明sleep() , Object類中聲明wait()
二、調用的要求(範圍)不一樣:sleep()能夠在任何須要的場景下調用,wait()必須使用在同步代碼塊或同步方法中
三、關因而否釋放同步監視器(鎖):若是兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。
package com.shnsxt.thread;
/** * 經典例題:生產者/消費者的問題 * * @author Rainbow * @date 2020/7/16 9:07 */
class Clerk {
private int productCount = 0;
/** * 生產產品 */
public synchronized void produceProduct() {
if (productCount < 20) {
productCount++;
System.out.println(Thread.currentThread().getName() + ":開始生產第" + productCount + "產品");
//線程喚醒
this.notify();
} else {
//線程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/** * 消費產品 */
public synchronized void consumerProduct() {
if (productCount > 0) {
System.out.println(Thread.currentThread().getName() + ":開始消費第" + productCount + "產品");
productCount--;
//線程喚醒
this.notify();
} else {
//線程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/** * 生產者 */
class Producer extends Thread {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ": 開始生產產品......");
while (true) {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//生產產品
clerk.produceProduct();
}
}
}
/** * 消費者 */
class Consumer extends Thread {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ": 開始消費產品......");
while (true) {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//消費產品
clerk.consumerProduct();
}
}
}
public class ProducetTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
//生產者
Producer p1 = new Producer(clerk);
p1.setName("生產者");
//消費者
Consumer c1 = new Consumer(clerk);
c1.setName("消費者1");
Consumer c2 = new Consumer(clerk);
c2.setName("消費者2");
p1.start();
c1.start();
c2.start();
}
}
複製代碼
與使用Runnable相比, Callable功能更強大些
一、 相比run()方法,能夠有返回值
二、 方法能夠拋出異常
三、支持泛型的返回值
四、須要藉助FutureTask類,好比獲取返回結果
Future接口
一、 能夠對具體Runnable、Callable任務的執行結果進行取消、查詢是否完成、獲取結果等。
二、FutrueTask是Futrue接口的惟一的實現類
三、 FutureTask 同時實現了Runnable, Future接口。它既能夠做爲Runnable被線程執行,又能夠做爲Future獲得Callable的返回值
代碼理解,以下所示:
package com.shnsxt.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/** * 建立線程的方式三:實現Callable接口。 --- JDK 5.0新增 * 1. call()能夠有返回值的。 * 2. call()能夠拋出異常,被外面的操做捕獲,獲取異常的信息 * 3. Callable是支持泛型的 * * @author Rainbow * @date 2020/7/16 10:31 */
//1.建立一個實現Callable的實現類
class NumThread implements Callable<Integer> {
//2.實現call方法,將此線程須要執行的操做聲明在call()方法中
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
sum += i;
}
}
return sum;
}
}
public class ThreadCallable {
public static void main(String[] args) {
//3.建立Callable接口實現類的對象
NumThread numThread = new NumThread();
//4.將此Callable接口實現類的對象做爲傳遞到FutureTask構造器中,建立FutureTask的對象
FutureTask<Integer> futureTask = new FutureTask(numThread);
//5.將FutureTask的對象做爲參數傳遞到Thread類的構造器中,建立Thread對象,並調用start()
new Thread(futureTask).start();
try {
//6.獲取Callable中call方法的返回值
//get()返回值即爲FutureTask構造器參數Callable實現類重寫的call()的返回值。
Integer sum = futureTask.get();
System.out.println("總和爲:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
複製代碼
爲何要用線程池?
常常建立和銷燬、使用量特別大的資源,好比並髮狀況下的線程,對性能影響很大。
使用線程池的好處
一、提升響應速度(減小了建立新線程的時間) 二、下降資源消耗(重複利用線程池中線程,不須要每次都建立)
便於線程的管理:
一、
corePoolSize
:核心池的大小二、
maximumPoolSize
:最大線程數三、
keepAliveTime
:線程沒有任務時最多保持多長時間後會終止
線程池相關的API
JDK 5.0起提供了線程池相關API:
ExecutorService
和Executors
ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor
void execute(Runnable command)
:執行任務/命令,沒有返回值,通常用來執行Runnable<T> Future <T> submit(Callable<T> task)
:執行任務,有返回值,通常又來執行Callablevoid shutdown()
:關閉鏈接池Executors:工具類、線程池的工廠類,用於建立並返回不一樣類型的線程池
Executors.newCachedThreadPool()
:建立一個可根據須要建立新線程的線程池,主要的問題是線程數最大數Integer.MAX_VALUE,可能會建立數量很是多的線程Executors.newFixedThreadPool(n)
: 建立一個可重用固定線程數的線程池,主要問題是堆積的請求處理隊列可能會損耗很是大的內存Executors.newSingleThreadExecutor()
:建立一個只有一個線程的線程池,主要問題是堆積的請求處理隊列可能會損耗很是大的內存Executors.newScheduledThreadPool(n)
:建立一個線程池,它可安排在給定延遲後運行命令或者按期地執行。主要的問題是線程數最大數Integer.MAX_VALUE,可能會建立數量很是多的線程
代碼理解,以下所示:
package com.shnsxt.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/** * 建立線程的方式四之二:使用線程池 * 好處: * 1.提升響應速度(減小了建立新線程的時間) * 2.下降資源消耗(重複利用線程池中線程,不須要每次都建立) * 3.便於線程管理 * corePoolSize:核心池的大小 * maximumPoolSize:最大線程數 * keepAliveTime:線程沒有任務時最多保持多長時間後會終止 * * @author Rainbow * @date 2020/7/16 11:08 */
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. 提供指定線程數量的線程池
ExecutorService service = Executors.newFixedThreadPool(10);//這裏可能會出現上面寫出的問題
ThreadPoolExecutor pool = (ThreadPoolExecutor) service;
//設置線程池的屬性
// pool.setCorePoolSize(15);
// pool.setKeepAliveTime(60,MINUTES);
//2.執行指定的線程的操做。須要提供實現Runnable接口或Callable接口實現類的對象
service.execute(new NumberThread());//適合適用於Runnable
service.execute(new NumberThread1());//適合適用於Runnable
// service.submit(Callable callable);//適合使用於Callable
//3.關閉鏈接池
service.shutdown();
}
}
複製代碼
若是,此文章對你有所幫助,請幫忙點個讚唄! 謝謝!!!