2020-11-15
一、多執行緒的常用方法及API
java.lang.Thread 類中提供了大量的相關的方法:
new Thread();
new Thread(name);
new Thread(Runnable,name);
new Thread(Runnable)
常用方法:
getId() :獲取執行緒的唯一標識
getName() :獲取執行緒名
getPriority():獲取執行緒的優先順序: 優先順序從1-10 , min-priority:1 max-priority:10 norm- priority:5 注意說明優先順序高的獲取到cpu資源的概率越大,並不是一定會優先執行完成。
currentThread():獲取當前執行緒的物件引用
getState():獲取執行緒的狀態 (這是返回執行緒狀態的列舉, NEW:未啟動,RUNABLE:可執行 BLOCK:阻塞狀態, WAITING:等待狀態TIMED-WAITIN: 等待另一個執行緒執行完)
interrupt():中斷這個執行緒
isInterrupted(): 返回boolean 測試當前執行緒是否中斷
isAlive():該執行緒是否處於活動狀態
isDaemon():判斷該執行緒是否是守護執行緒
setDaemon():設定該執行緒是否是守護執行緒
join() :合併執行緒,使它變為單執行緒
sleep(ms) :讓當前執行緒休眠 ,休眠時間到了,自動喚醒
yield(): 讓出cpu資源,使當前執行緒處理可執行狀態(可執行狀態也隨時可以獲取cpu資源)
案例1: 測試執行緒的基本屬性
System.out.println("當前主執行緒:"+Thread.currentThread().getName());
System.out.println("主執行緒id:"+Thread.currentThread().getId());
//設定主執行緒的執行緒級別
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
System.out.println("主執行緒的執行緒級別:"+Thread.currentThread().getPriority());
// 建立執行緒物件
MyThread my = new MyThread();
//設定執行緒名
my.setName("執行緒A");
//設定優先順序
my.setPriority(10);
my.start();
MyThread my1 = new MyThread();
my1.setName("執行緒B");
//設定優先順序 執行緒A 獲取到資源的概率 大於執行緒B (大概率執行緒A優先執行完)
my1.setPriority(1);
//新生態
System.out.println("執行緒"+my1.getName()+"狀態-----"+my1.getState());
my1.start();
//可執行狀態(就緒)
System.out.println("執行緒"+my1.getName()+"狀態-----"+my1.getState());
for(int i = 0;i<100;i++){
System.out.println("主執行緒------"+i);
}
守護執行緒
案例2: 守護執行緒
執行緒型別分為兩種 一種是使用者執行緒一種是守護執行緒,使用者執行緒是執行某一個任務的獨立程式碼 ,守護執行緒是用於守護使用者執行緒的執行緒, 它的特點是 當使用者執行緒執行完畢後守護現在自動結束,當使用者執行緒沒有執行完, 守護執行緒也不會停止
作業系統中有守護程式 ,用於作業系統的執行,只有關機程式自動結束,這裡守護執行緒和守護程式類似。
//建立執行緒物件
DaemonThread daemonThread = new DaemonThread();
//設定該執行緒為守護執行緒 守護的是與它並行的執行緒類 ,當主執行緒或其他執行緒執行完畢,守護執行緒自動結束
// daemonThread.setDaemon(true);
System.out.println("是否是守護執行緒:"+daemonThread.isDaemon());
daemonThread.start();
for(int i=0;i<100;i++){
System.out.println("主執行緒i------"+i);
}
活動的執行緒總數: Thread.activeCount()
執行緒中斷
案例3: 關於終止執行緒
執行緒中止就是當執行緒執行時由於滿足特定的條件需要停止執行,此時我們需要考慮如何安全的中止執行緒這裡中止執行緒提供幾個方法
方法1 : 打標記中斷法
執行緒執行1000,當程式達到500時,中止程式
public class ThreadEnd extends Thread {
@Override
public void run() {
boolean isOver=false;
for(int i = 0 ;i<1000;i++){
if(i>=500){
isOver= true;
return ;
}
System.out.println("執行緒結果i-----------"+i);
}
System.out.println("正常結束");
}
public static void main(String[] args) {
ThreadEnd th = new ThreadEnd();
th.start();
}
}
方法2: 異常中斷法
- interrupt() :給執行緒打一箇中斷標記,不會立馬中斷
- interrupted() : 檢測執行緒是否中斷,並清除中斷標記,返回boolean ,如果執行緒打標記了,就返回true
- isInterrupted() : 檢測執行緒是否中斷,但不清除中斷標記, 返回boolean
注意用法: interrupted() : 它所處於的位置,對應於它作用的位置 ,通過執行緒類名呼叫
interrupt() 和 isInterrupted() : 使用執行緒物件呼叫。
public class Thread1 extends Thread {
@Override
public void run() {
int i =0;
while(true){
System.out.println("執行緒--------------"+i);
//判斷當前執行緒是否有中斷標記 ,但是不清除中斷標記
if(this.isInterrupted()){
// 通過丟擲異常或 break
System.out.println("當前執行緒打中斷標記,可以停止了");
break;
}
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 th = new Thread1();
th.start();
// 休眠一會兒
Thread.sleep(2000);
//給th打中斷標記
System.out.println("打標記");
th.interrupt(); //給th打標記
}
3個方法的用法
// Thread.currentThread().interrupt();
// System.out.println("判斷當前執行緒是否打標記 (清除標記):"+ Thread.interrupted());
System.out.println("判斷執行緒是否打標記(不清除標記)"+ Thread.currentThread().isInterrupted());
System.out.println("判斷當前執行緒是否打標記 (清除標記):"+ Thread.interrupted()); // 靜態方法
join用法
案例四: join的用法: 合併當前執行緒 ,使其變為單執行緒 ,哪個執行緒呼叫join方法,就立即將該執行緒剩下的部分執行完成,再執行其他執行緒
public class ThreadJoin extends Thread {
@Override
public void run() {
ThreadJoin2 th = new ThreadJoin2();
th.setName("執行緒C");
th.start();
for(int i=0;i<100;i++){
try {
th.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-----"+i);
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadJoin threadJoin = new ThreadJoin();
threadJoin.setName("執行緒A");
threadJoin.start();
// ThreadJoin threadJoin2 = new ThreadJoin();
// threadJoin2.setName("執行緒B");
// threadJoin2.start();
for(int i=0;i<100 ;i++){
if(i==50){
// 合併執行緒 (threadJoin執行緒的所有程式碼合併到 主執行緒中,先執行threadJoin執行緒)
threadJoin.join();
}
// if(i==70){
// threadJoin2.join();
// }
System.out.println("main---"+i);
}
}
sleep用法
案例五: sleep的用法: 用於休閒當前執行緒 ,休眠時間結束後自動喚醒繼續執行,如果同時有多個執行緒執行 ,如果執行緒沒有同步的情況下,相互休眠不影響,資源被公用。
public static void main(String[] args) {
for(int i =0;i<10;i++){
try {
//讓當前執行緒休眠200毫秒 200毫秒後自動喚醒執行緒 繼續執行
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
class ThreadSleep implements Runnable{
@Override
public void run() {
for(int i =0;i<100;i++){
try {
Thread.sleep(1000); // 當前執行緒休眠時 不影響其他執行緒執行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
ThreadSleep obj = new ThreadSleep();
Thread th1 = new Thread(obj , "執行緒A");
Thread th2 = new Thread(obj , "執行緒B");
th1.start();
th2.start();
yield用法
案例六:yield的用法 : 出讓cpu, 讓當先執行緒變為可執行狀態 ,並也可以繼續搶佔cpu資源
public static void main(String[] args) {
ThreadYiled th = new ThreadYiled();
th.start();
// yield 讓出cpu資源
for(int i = 0;i<100;i++){
if(i==50){
//主執行緒讓cpu
System.out.println("讓出cpu");
Thread.currentThread().yield();
}
System.out.println("主執行緒----"+i);
}
}
二、執行緒同步
同步的解決辦法:
1、將需要操作公共資源的程式碼增加 “同步鎖” (也叫互斥鎖)
語法:
synchronized(物件鎖){
程式碼塊
}
注意這裡的物件鎖必須滿足 兩個執行緒是同一個物件(同一把鎖)
public void run() {
System.out.println("開始取錢了");
// 增加互斥鎖,協同步伐 ,這個鎖必須是公有的物件
synchronized(account) {
//先判斷賬戶餘額是否足夠
if (account.getMoney() >= 1000) {
System.out.println(Thread.currentThread().getName() + "可以取錢");
System.out.println(Thread.currentThread().getName() + "正在取錢");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新賬戶餘額
account.setMoney(account.getMoney() - 1000);
System.out.println(Thread.currentThread().getName() +
"取到了錢,卡里餘額還剩:" + account.getMoney());
} else {
System.out.println("抱歉,卡里餘額不足,不能取1000元");
}
}
// 以上程式碼需要將操作通過資源的程式碼塊 增加同步關鍵字
}
2、 同步方法
在方法的返回值前面增加 “synchronize” , 此時的鎖代表的是當前this物件
public synchronized void get(){
//先判斷賬戶餘額是否足夠
if (account.getMoney() >= 1000) {
System.out.println(Thread.currentThread().getName() + "可以取錢");
System.out.println(Thread.currentThread().getName() + "正在取錢");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新賬戶餘額
account.setMoney(account.getMoney() - 1000);
System.out.println(Thread.currentThread().getName() +
"取到了錢,卡里餘額還剩:" + account.getMoney());
} else {
System.out.println("抱歉,卡里餘額不足,不能取1000元");
}
}
同步程式碼塊和同步方法的區別:
1、語法不同,同步程式碼塊更靈活,可以自定義鎖物件 ,而同步方法不可以指定鎖物件
一、執行緒死鎖
執行緒死鎖的產生
執行緒同步可以幫助我們解決多個執行緒操作同一個資源而導致資料不安全的問題,但執行緒同步也有可能產生隱患,假如一個執行緒中出現多個鎖物件時,可能出現鎖使用不當,導致鎖與鎖之前相互等待對方釋放資源,從而形成一種 “相互等待”的僵局,這就是執行緒死鎖。 例如哲學家吃飯
模擬執行緒死鎖
public class DeadThread implements Runnable {
Object obj1 = new Object();
Object obj2 = new Object();
@Override
public void run() {
// 模擬執行緒死鎖
// 如果當前執行緒為執行緒A 先拿到obj1鎖 ,等待obj2鎖資源
// 如果當前執行緒為執行緒B 先拿到obj2鎖 ,等待obj1鎖的資源
if(Thread.currentThread().getName().equals("執行緒A")){
synchronized (obj1){
System.out.println(Thread.currentThread().getName()+"拿到了obj1的鎖");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我在等待obj2鎖。。。。。");
synchronized (obj2){
System.out.println("我已經拿到了obj2鎖。。。。");
}
}
}else{
//執行緒B
synchronized (obj2){
System.out.println(Thread.currentThread().getName()+"拿到了obj2的鎖");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我在等待obj1鎖。。。。");
synchronized (obj1){
System.out.println("我已經拿到了obj1鎖,我和開心");
}
}
}
}
}
public static void main(String[] args) {
DeadThread dead = new DeadThread();
Thread th1 = new Thread(dead,"執行緒A");
Thread th2 = new Thread(dead,"執行緒B");
th1.start();
th2.start();
}
之所以會發生死鎖,因為物件鎖直接沒有良好的“溝通”,導致互相獲取對方的鎖 ,進入等待中 ,可以通過執行緒類的幾個方法 解決執行緒之間通訊問題
二、執行緒通訊的幾個方法
wait() : 讓當前執行緒處於等待中,會釋放物件鎖 ,但是不會自動喚醒,需要其他執行緒喚醒
notify() : 喚醒等待中的一個執行緒
notifyAll: 喚醒所有等待中的執行緒
他們都屬性Object的方法,需要相同的物件 ,使用時 通過Object的物件呼叫
注意: 以上方法的呼叫必須滿足兩個條件: a、他們必須在同步程式碼塊中執行, b、呼叫該方法的物件是鎖物件
案例1:模擬3個人,張飛、李逵和劉備,來買電影票,售票員只有一張5元的錢,電影票5元錢一張。
-
張飛拿20元一張的人民幣排在李逵和劉備的前面,李逵和劉備各拿了一張5元的人民幣買票。
package com.j2008.waitnotify; /** * ClassName: TicketThread * Description: * date: 2020/11/10 11:27 * * @author wuyafeng * @version 1.0 softeem.com */ public class TicketThread implements Runnable{ //公共資源: int fiveCount=1; int twentyCount =0; @Override public void run() { //開始買票 如果是張飛,他是20元面值,其他都是5元 if(Thread.currentThread().getName().equals("張飛")){ //20元面值買票 takeTicket(20); }else{ // 5元面值買票 takeTicket(5); } } /** * 買票過程 給方法加同步 ,鎖物件預設是 方法 * @param money */ public synchronized void takeTicket(int money){ if(money ==20){ // 驗證 當前公共資源 中是否有3張5元 while(fiveCount<3){ //等待 System.out.println(Thread.currentThread().getName()+"不能買到票,要繼續等待"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 程式執行這裡,說明 fiveCount >=3 System.out.println(Thread.currentThread().getName()+"正在買票。。"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } fiveCount-=3; twentyCount++; System.out.println(Thread.currentThread().getName()+"已經買到了票。。"); }else{ System.out.println(Thread.currentThread().getName()+"正在買票。。"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } fiveCount++; System.out.println(Thread.currentThread().getName()+"已經買到了票。。"); // 喚醒等待的執行緒 this.notify(); } } }
public static void main(String[] args) { TicketThread ticketThread = new TicketThread(); Thread th1 = new Thread(ticketThread,"張飛"); Thread th2 = new Thread(ticketThread,"李逵"); Thread th3 = new Thread(ticketThread,"劉備"); //開啟執行緒 th1.start(); th2.start(); th3.start(); }
三、執行緒的生產者和消費者模式
多個執行緒同時執行時,會產生執行緒併發可使用同步操作確保資料的安全性,如果需要各執行緒之間互動,可是使用執行緒等待和喚醒模式,在這裡常用的等待喚醒中經典的模式為“生產者和消費者模式”
生產者和消費者由兩類執行緒組成: 若干個生產者執行緒 負責提交使用者的請求,若干個消費者執行緒負責處理生成出來的任務。 他們操作一塊共享記憶體區進行資料通訊。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-v6DtAFB0-1605448559613)(assets/1604992093615.png)]
生成/消費的產品(資料): Mobile (手機編號)
生成者執行緒類: Provider : 無限制的生成手機
消費者執行緒類:Customer : 無限制的消費手機
共享儲存區: Storage ( push 、pop) 儲存手機的物件陣列
測試類
public class Mobile {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Mobile(int id) {
this.id = id;
}
}
儲存區
package com.j2008.provider_customer;
/**
* ClassName: Storage
* Description:
* date: 2020/11/10 15:32
* 儲存區,它是生產者和消費者共享的空間 ( 生產者和消費者將該物件作為公有鎖)
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class Storage {
// 定義儲存手機的物件資料
Mobile [] mobiles = new Mobile[10];
int index=0; // 個數
static int n=1000;
/**
* 存放手機
* @param mobile
*/
public synchronized void push(Mobile mobile){
//考慮容器上限已滿,必須等待
while(index == mobiles.length){
System.out.println("容器已滿,需等待");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//通知消費者取消費 ,將其他執行緒全部喚醒
this.notifyAll();
mobiles[index]=mobile;
index++;
}
/**
* 取出手機 1 2 3 4
* 1 2 3
* index--;
* mobile[index] =null
* @return 取出的手機物件
*/
public synchronized Mobile pop(){
Mobile m = null;
// 判斷index是否小於0
while(index<=0){
//等待
System.out.println("容器中沒有手機,需要等待");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
m = mobiles[index];
//將容器中的這個位置 設定為空
mobiles[index]=null;
// 通知生產者去生產
this.notifyAll();
return m;
}
public synchronized int getSize(){
return index;
}
}
生產者:
package com.j2008.provider_customer;
/**
* ClassName: Provider
* Description:
* date: 2020/11/10 15:54
*
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class Provider implements Runnable {
//共享儲存區
Storage storage =null;
public Provider(Storage storage){
this.storage = storage;
}
@Override
public void run() {
//手機編號
int n=1000;
//一直生產
while(true){
Mobile m = new Mobile(n);
storage.push(m);
System.out.println(Thread.currentThread().getName()+
"生產了一部手機,其編號:"+m.getId()+" 其庫存:"+storage.getSize());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
n++;
}
}
}
消費者
package com.j2008.provider_customer;
/**
* ClassName: Customer
* Description:
* date: 2020/11/10 15:58
*
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class Customer implements Runnable {
Storage storage=null;
public Customer(Storage storage){
this.storage = storage;
}
@Override
public void run() {
while(true){
Mobile mobile = storage.pop();
System.out.println(Thread.currentThread().getName()+
"消費了一部手機,編號》》"+mobile.getId()+" 庫存:"+storage.getSize());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
測試類
package com.j2008.provider_customer;
/**
* ClassName: TestProviderCustomer
* Description:
* date: 2020/11/10 16:01
*
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class TestProviderCustomer {
public static void main(String[] args) {
//建立公有的儲存空間
Storage storage = new Storage();
Provider provider1 = new Provider(storage);
Provider provider2 = new Provider(storage);
Provider provider3 = new Provider(storage);
Thread th1 = new Thread(provider1,"張飛");
Thread th2 = new Thread(provider2,"劉備");
Thread th3 = new Thread(provider3,"關羽");
th1.start();
th2.start();
th3.start();
//消費者上場
Customer customer1 = new Customer(storage);
Customer customer2 = new Customer(storage);
Customer customer3 = new Customer(storage);
Thread th4 = new Thread(customer1,"張飛的老婆");
Thread th5 = new Thread(customer2,"劉備的老婆");
Thread th6 = new Thread(customer3,"關羽的老婆");
th4.start();
th5.start();
th6.start();
}
}
測試結果
關羽生產了一部手機,其編號:1000 其庫存:1
張飛的老婆消費了一部手機,編號》》1000 庫存:0
張飛生產了一部手機,其編號:1000 其庫存:1
劉備生產了一部手機,其編號:1000 其庫存:2
劉備的老婆消費了一部手機,編號》》1000 庫存:1
關羽的老婆消費了一部手機,編號》》1000 庫存:0
容器中沒有手機,需要等待
關羽生產了一部手機,其編號:1001 其庫存:0
張飛的老婆消費了一部手機,編號》》1001 庫存:0
張飛生產了一部手機,其編號:1001 其庫存:2
劉備生產了一部手機,其編號:1001 其庫存:2
關羽的老婆消費了一部手機,編號》》1001 庫存:0
劉備的老婆消費了一部手機,編號》》1001 庫存:0
容器中沒有手機,需要等待
關羽生產了一部手機,其編號:1002 其庫存:0
張飛的老婆消費了一部手機,編號》》1002 庫存:0
劉備生產了一部手機,其編號:1002 其庫存:2
張飛生產了一部手機,其編號:1002 其庫存:2
劉備的老婆消費了一部手機,編號》》1002 庫存:1
關羽的老婆消費了一部手機,編號》》1002 庫存:0
四、執行緒池
1、定義
用於建立和管理執行緒的容器就是執行緒池 (Thread Pool) ,線上程池中的執行緒執行完任務後不會立馬進入銷燬狀態,而是重置到執行緒池中變為“空閒執行緒” 。 有利於避免頻繁建立執行緒消耗資源,提供執行緒複用率,有限管理該執行緒。
2、使用執行緒池的原因:
在多執行緒環境下,對於不斷建立和銷燬效率非常消耗系統資源,對於多執行緒之間的切換存線上程安全問題, 這是使用統一的管理類管理一些執行緒是比較好的解決辦法
3、執行緒的執行機制:
- 線上程池模式下,任務是提交給執行緒池,由執行緒池根據當前空閒執行緒進行分配任務,如果沒有空閒執行緒,由管理類建立執行緒或者進入任務等待佇列中。
- 一個執行緒同時只能執行一個任務,但多個任務可以同時提交給這個執行緒池。
執行緒池的常用類 (ExecutedService)
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
引數1: corePoolSize:核心執行緒數
引數2:maximumPoolSize :最大執行緒數
引數3: keepAliveTime : 執行緒活動時長
引數4: 對於引數3的單位
1、可快取的執行緒池 newCacheThreadPool(n);如果執行緒池中沒有空閒執行緒,則建立新執行緒並放入執行緒池中,無上限執行緒數,如果有空閒執行緒則直接使用該執行緒
public static void main(String[] args) {
// 建立可快取執行緒
ExecutorService service =Executors.newCachedThreadPool();
// 建立10個執行緒
int n=0;
for(int i = 0 ;i < 10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override //你們內部類
public void run() {
//任務
System.out.println(Thread.currentThread().getName()+"---"+n);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//由於以上的執行緒 每次執行完成只需要500毫秒 ,會釋放執行緒物件到執行緒池中
// 1000毫秒後建立的執行緒 就會複用上一次的執行緒物件
}
//關閉執行緒池
service.shutdown();
}
}
2、可重用的固定執行緒池: newFixedThreadPool(n) ,執行緒數量固定,如果沒有空閒執行緒,則存放無界佇列中等待
public static void main(String[] args) {
//建立執行緒池
ExecutorService service = Executors.newFixedThreadPool(3);
// 連續建立10個執行緒, 由於只有3個執行緒,所有執行緒只能等待
// 2秒後再執行3個
for(int i =0;i<10;i++){
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在執行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// 結果: 每2秒執行3個執行緒
}
3、 固定長度的可執行定時任務的執行緒池 newScheduledThreadPool , 類似於定時器執行緒
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("延遲1秒後每3秒執行一次");
}
}, 1, 3, TimeUnit.SECONDS);
4、單執行緒的執行緒池 newSingleThreadExecutor : 執行緒池中只有一個執行緒數,所有的任務都通過該執行緒執行,它可以保證所有的任務是FIFO模式, 滿足佇列結構