多執行緒、等待喚醒機制、經典案例【總結】
阿新 • • 發佈:2019-01-26
一、多執行緒
1.等待喚醒機制案例
//包子類
public class BaoZi {
//皮
String pi;
//陷
String xian;
//包子的狀態: 有 true,沒有 false,設定初始值為false沒有包子
boolean flag = false;
}
//生產者
public class BaoZiPu extends Thread{
//1.需要在成員位置建立一個包子變數
private BaoZi bz;
//2.使用帶引數構造方法,為這個包子變數賦值
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
//設定執行緒任務(run):生產包子
@Override
public void run() {
//定義一個變數
int count = 0;
//讓包子鋪一直生產包子
while(true){
//必須同時同步技術保證兩個執行緒只能有一個在執行
synchronized (bz){
//對包子的狀態進行判斷
if(bz.flag==true){
//包子鋪呼叫wait方法進入等待狀態
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被喚醒之後執行,包子鋪生產包子
//增加一些趣味性:交替生產兩種包子
if(count%2==0){
//生產 薄皮三鮮餡包子
bz.pi = "薄皮";
bz.xian = "三鮮餡";
}else{
//生產 冰皮 牛肉大蔥陷
bz.pi = "冰皮";
bz.xian = "牛肉大蔥陷";
}
count++;
System.out.println("包子鋪正在生產:"+bz.pi+bz.xian+"包子");
//生產包子需要3秒鐘
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//包子鋪生產好了包子
//修改包子的狀態為true有
bz.flag = true;
//喚醒吃貨執行緒,讓吃貨執行緒吃包子
bz.notify();
System.out.println("包子鋪已經生產好了:"+bz.pi+bz.xian+"包子,吃貨可以開始吃了");
}
}
}
}
//消費者
public class ChiHuo extends Thread{
//1.需要在成員位置建立一個包子變數
private BaoZi bz;
//2.使用帶引數構造方法,為這個包子變數賦值
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
//設定執行緒任務(run):吃包子
@Override
public void run() {
//使用死迴圈,讓吃貨一直吃包子
while (true){
//必須同時同步技術保證兩個執行緒只能有一個在執行
synchronized (bz){
//對包子的狀態進行判斷
if(bz.flag==false){
//吃貨呼叫wait方法進入等待狀態
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被喚醒之後執行的程式碼,吃包子
System.out.println("吃貨正在吃:"+bz.pi+bz.xian+"的包子");
//吃貨吃完包子
//修改包子的狀態為false沒有
bz.flag = false;
//吃貨喚醒包子鋪執行緒,生產包子
bz.notify();
System.out.println("吃貨已經把:"+bz.pi+bz.xian+"的包子吃完了,包子鋪開始生產包子");
System.out.println("----------------------------------------------------");
}
}
}
}
//測試類
public class Demo {
public static void main(String[] args) {
//建立包子物件;
BaoZi bz =new BaoZi();
//建立包子鋪執行緒,開啟,生產包子;
new BaoZiPu(bz).start();
//建立吃貨執行緒,開啟,吃包子;
new ChiHuo(bz).start();
}
}
案例二:有100個包,分別在官網和實體店賣出,使用等待喚醒機制
//Bag類
public class Bag {
private int num;
public Bag(int num) {
this.num = num;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
//實體店類實現Runnable介面
public class ReallyStore implements Runnable{
private Bag b;
public ReallyStore(Bag b) {
this.b = b;
}
@Override
public void run() {
while(true) {
synchronized (b)
{
if(b.getNum() == 0)
{
b.notifyAll();
break;
}
b.notify();
int num = b.getNum(); //獲取包的數量
System.out.println(Thread.currentThread().getName() + "正在賣出第" + (100 - num + 1) + "個包包,還剩餘" + ( --num));
b.setNum(num); //將變化後的寶的數量重新set
try {
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//網點類實現Runnable 其實程式碼和上面實體店類裡面的程式碼是一樣的
public class Web implements Runnable{
private Bag b;
public Web(Bag b) {
this.b = b;
}
@Override
public void run() {
while (true)
{
synchronized (b)
{
if(b.getNum() == 0) //當包包的數量為0的時候就結束迴圈
{
b.notifyAll();
break;
}
b.notify();
int num = b.getNum(); //獲取包的數量
System.out.println(Thread.currentThread().getName() + "正在賣出第" + (100 - num + 1) + "個包包,還剩餘" + ( --num));
b.setNum(num); //將變化後的寶的數量重新set
try {
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//測試類
public class Test {
public static void main(String[] args) {
Bag b = new Bag(100);
new Thread(new Web(b),"官網").start();
new Thread(new Web(b),"時地點").start();
}
}
2.執行緒池
執行緒池就是一個包含多個執行緒的一個容器
當有任務需要用到執行緒的時候,從容器取出來進行使用。用完以後再把執行緒歸還給池中
//執行緒類
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"建立了一個新的執行緒執行");
}
}
//測試類
public class Demo01ThreadPool {
public static void main(String[] args) {
//1.使用執行緒池的工廠類Executors裡邊提供的靜態方法newFixedThreadPool生產一個指定執行緒數量的執行緒池
ExecutorService es = Executors.newFixedThreadPool(2);
//3.呼叫ExecutorService中的方法submit,傳遞執行緒任務(實現類),開啟執行緒,執行run方法
es.submit(new RunnableImpl());//pool-1-thread-1建立了一個新的執行緒執行
//執行緒池會一直開啟,使用完了執行緒,會自動把執行緒歸還給執行緒池,執行緒可以繼續使用
es.submit(new RunnableImpl());//pool-1-thread-1建立了一個新的執行緒執行
es.submit(new RunnableImpl());//pool-1-thread-2建立了一個新的執行緒執行
//4.呼叫ExecutorService中的方法shutdown銷燬執行緒池(不建議執行)
//es.shutdown();
//es.submit(new RunnableImpl());//拋異常,執行緒池都沒有了,就不能獲取執行緒了
}
}
二、Lambda表示式
1.定義格式
(引數):代表要執行的介面中的某個方法,如果有引數就傳遞。如果沒有小括號中空著
->:將上面的小括號中的引數傳遞給方法內部
{}:重寫方法後要執行的程式碼
2.Lambda表示式的練習-無引數無返回值
//介面
public interface Cook {
//定義無引數無返回值的方法makeFood
public abstract void makeFood();
}
//測試類
public class Demo01Cook {
public static void main(String[] args) {
//使用Lambda表示式,簡化匿名內部類的書寫
invokeCook(()->{
System.out.println("吃飯了");
});
}
//定義一個方法,引數傳遞Cook介面,方法內部呼叫Cook介面中的方法makeFood
public static void invokeCook(Cook cook){
cook.makeFood();
}
}
3.Lambda表示式的練習-有引數有返回值
public class Demo01Arrays {
public static void main(String[] args) {
//使用陣列儲存多個Person物件
Person[] arr = {
new Person("柳巖",38),
new Person("迪麗熱巴",18),
new Person("古力娜扎",19)
};
//使用Lambda表示式,簡化匿名內部類
Arrays.sort(arr,(Person o1, Person o2)->{
return o1.getAge()-o2.getAge();
});
//優化省略Lambda
//Arrays.sort(arr,(o1, o2)->o1.getAge()-o2.getAge());
//遍歷陣列
for (Person p : arr) {
System.out.println(p);
}
}
}
4.Lambda表示式的練習-自定義介面
//介面
public interface Calculator {
//定義一個計算兩個int整數和的方法並返回結果
public abstract int calc(int a,int b);
}
//測試類
public class Demo01Calculator {
public static void main(String[] args) {
//使用Lambda表示式簡化匿名內部類的書寫
invokeCalc(120,130,(int a,int b)->{
return a + b;
});
//優化省略Lambda
//invokeCalc(120,130,(a,b)-> a + b);
}
/*
定義一個方法
引數傳遞兩個int型別的整數
引數傳遞Calculator介面
方法內部呼叫Calculator中的方法calc計算兩個整數的和
*/
public static void invokeCalc(int a,int b,Calculator c){
int sum = c.calc(a,b);
System.out.println(sum);
}
}
5.使用Lambda表示式的前提和省略規則
使用前提:
A:必須是介面
B:介面中只能有一個抽象方法
省略格式:
Lambda表示式:是可推導,可以省略
凡是根據上下文推匯出來的內容,都可以省略書寫
可以省略的內容:
1.(引數列表):括號中引數列表的資料型別,可以省略不寫
2.(引數列表):括號中的引數如果只有一個,那麼型別和()都可以省略
3.{一些程式碼}:如果{}中的程式碼只有一行,無論是否有返回值,都可以省略({},return,分號)
注意:要省略{},return,分號必須一起省略
6.約束介面中只能有一個抽象方法的註解
@FunctionalInterface 將一個介面規範為函式式介面