第一章 等待唤醒机制
1.1 线程间通信
- 概念:
- 多个线程在处理同一个资源(包子),但是处理的动作(线程的任务)却不相同。
- 比如:
- 线程A用来生产包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题
1.2 等待唤醒机制
- 等待与唤醒机制:线程之间的通信
- 【重点】:有效的利用资源[包子](生产一个包子,吃一个包子,再生产一个包子,再吃一个包子)
- 通信:对包子的状态进行判断
* ==没有==包子-->吃货线程唤醒包子铺线程-->吃货线程等待-->包子铺线程做包子-->做好包子-->修改包子的状态==有== * ==有==包子-->包子铺线程唤醒吃货线程-->包子铺线程等待-->吃货线程吃包子-->修改包子的状态==没有== * ==没有==包子-->吃货线程唤醒包子铺线程-->吃货线程等待-->包子铺线程做包子-->做好包子-->修改包子的状态==有== * ...
- 等待唤醒中的方法
- void wait()
- 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
- void notify()
- 唤醒在此对象监视器上等待的单个线程。
- 注:会继续执行wait方法之后的代码
- void notifyAll()
- 唤醒在此对象监视器上等待的所有线程。
- void wait()
- 注意事项:
- 哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步代码块内,而此刻它已经不持有锁,所以它需要再次去获取锁(++很有可能面临其他线程的竞争++),成功后才能在当初调用wait方法之后的地方恢复执行。
- 总结:
- 如果能获取锁,线程就从WAITING状态变成RUNNABLE状态
- 否则,从wait set出来,又进入entry set,线程又从WAITING状态变成BOLCKED状态
- 调用wait和notify方法需要注意的细节
- wait和notify必须要由同一个锁对象调用
- 对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程
- wait与notify是属于Object方法的
- 锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的
- wait和notify必须要在同步代码块或者是同步方法(同步函数)中使用
- 必须要通过锁对象调用这2个方法
- wait和notify必须要由同一个锁对象调用
1.3 等待唤醒机制案例分析
- 等待唤醒机制其实就是经典的“生产者与消费者”问题
- 1.1中包子案例分析:
1.4 等待唤醒机制代码实现
- 包子铺类的注意事项:
- 包子铺线程与吃货线程关系–>通信(互斥)
- 必须使用同步技术两个线程只能有一个在执行
- 锁对象必须保证唯一,可以使用包子对象作为锁对象
- 包子铺这个类和吃货这个类把包子对象作为参数传入进来
- 需要在成员位置创建一个变量
- 使用带参构造方法,为这个包子变量赋值
//定义一个包子类:BaoZi.java
/*
包子类
属性:
皮
馅
包子的有无状态:true,false
*/
public class BaoZi {
//皮
String pi;
//馅
String xian;
//包子的有无状态:true,false,初始为false
boolean flag = false;
}
//定义一个包子铺类来继承Thread:BaoZiPu.java
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让包子铺一直生产包子
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();
}
//包子铺生产好包子后,将包子状态修改为有
bz.flag = true;
//唤醒吃货线程,让吃货线程吃包子
bz.notify();
System.out.println("包子铺已经生产好了:" + bz.pi + bz.xian + "的包子,吃货可以开始吃了");
}
}
}
}
//定义一个吃货类来继承Thread:ChiHuo.java
public class ChiHuo extends Thread{
//1. 需要在成员位置创建一个变量
private BaoZi bz;
//2. 使用带参构造方法,为这个包子变量赋值
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
//重写线程任务run:吃包子
@Override
public void run() {
//while让吃货一直吃包子
while (true){
//同步技术,保证两个线程只有一个在执行
synchronized(bz) { //同步代码块的锁对象就用包子对象
//对包子的状态进行判断
if(bz.flag == false){
//吃货调用wait方法进入等待状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后执行的代码,吃包子
System.out.println("吃货正在吃" + bz.pi + bz.xian + "的包子");
//吃货吃完包子后,将包子状态修改为无
bz.flag = false;
//唤醒包子铺线程,让包子铺生产包子
bz.notify();
System.out.println("吃货已经把:" + bz.pi + bz.xian + "的包子吃完了,包子铺开始生产包子");
System.out.println("=======================");
}
}
}
}
//主方法(测试类):Demo.java
public static void main(String[] args) {
//创建包子对象
BaoZi bz = new BaoZi();
//创建包子铺线程,开启,生产包子
new BaoZiPu(bz).start();
//创建吃货线程,开启,吃包子
new ChiHuo(bz).start();
}
//结果:
包子铺正在生产薄皮三鲜馅的包子
包子铺已经生产好了:薄皮三鲜馅的包子,吃货可以开始吃了
吃货正在吃薄皮三鲜馅的包子
吃货已经把:薄皮三鲜馅的包子吃完了,包子铺开始生产包子
=======================
包子铺正在生产冰皮牛肉大葱馅的包子
包子铺已经生产好了:冰皮牛肉大葱馅的包子,吃货可以开始吃了
吃货正在吃冰皮牛肉大葱馅的包子
吃货已经把:冰皮牛肉大葱馅的包子吃完了,包子铺开始生产包子
=======================
包子铺正在生产薄皮三鲜馅的包子
...
第二章 线程池
2.1 线程池的概念和原理
- 线程池的思想概念
2.2 线程池的概念和原理
- 概念:
- 线程池:其实就是一个容纳多个线程的容器【!】,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源。
- 线程池的底层原理:
- 当程序第一次启动的时候,创建多个线程,保存到一个集合中
- 当我们想要使用线程的时候,就可以从集合中取出来线程使用
- Thread t = list.remove(0); //返回的是被移除的元素,(线程只能被一个任务使用)
- Thread t = linked.removeFirst(0);
- 当我们使用完线程,需要把线程归还给线程池
- list.add(t);
- linked.addLast(t);
- 常用LinkedList集合
- 注:JDK1.5之后,内置了线程池,可以直接使用,无需自己写2.3会讲【重点】
- 线程池工作原理:
- 注:如果有5个任务,3个线程,如果线程池中无空闲线程时
- 任务等待执行
- 等待其他某个任务执行完毕后,归还线程到线程池
- 再从线程池中获取线程,执行任务
- 注:如果有5个任务,3个线程,如果线程池中无空闲线程时
- 线程池的优点:
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
2.3 线程池的使用
- 线程池:JDK1.5之后提供的
- java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
- Executors类中的静态方法😦静态方法之间类名调用【1】)
- static ++ExecutorService++ newFixedThreadPool(int nThreads)
- 创建一个可重用固定线程数的线程池
- 参数:
- int nThreads:创建线程池中包含的线程数量
- 返回值:
- ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
- static ++ExecutorService++ newFixedThreadPool(int nThreads)
- java.util.concurrent.ExecutorService:线程池接口
- 用来从线程池中获取线程,调用start方法,执行线程任务【重点】
- submit(Runnable task)
- 提交一个Runnable 任务用于执行
- 关闭/销毁线程池的方法(不常用)
- void shutdown()
- submit(Runnable task)
- 用来从线程池中获取线程,调用start方法,执行线程任务【重点】
- 线程池的使用步骤:
- 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
- 创建一个类,实现Runnable接口,重写run方法,设置线程任务
- 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法(创建实现类的对象)
- 调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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-2创建了一个新的线程执行
es.submit(new RunnableImpl()); //pool-1-thread-2创建了一个新的线程执行
//4. 调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
es.shutdown();
es.submit(new RunnableImpl()); //抛出异常,线程池都没了,就不能获取线程了
}
}
//定义一个实现类,实现Runnable接口:RunnableImpl.java
//2. ==创建==一个==类==,==实现Runnable==接口,==重写run==方法,设置线程任务
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "创建了一个新的线程执行"); //Thread.currentThread().getName()-->获取线程名称
}
}
//结果:
pool-1-thread-2创建了一个新的线程执行
pool-1-thread-2创建了一个新的线程执行
pool-1-thread-1创建了一个新的线程执行
- 注: 线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
- 注:如果调用ExecutorService中的方法shutdown销毁线程池后,再次创建新的线程执行。
- 抛出异常,线程池都没了,就不能获取线程了
第三章 Lambda表达式
3.1 函数式编程思想概述
- 面向对象的思想:
- 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。
- 函数式编程思想:
- 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
3.2 冗余的Runnable代码
//实现接口Runnable的实现类:RunnableImpl.java
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "新线程创建了");
}
}
//主方法:Demo01Runnable.java
public static void main(String[] args) {
/*RunnableImpl run = new RunnableImpl();
Thread t = new Thread(run);
t.start();*/
//简化-使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "新线程创建了...");
}
}).start();
}
* 只有方法体才是关键所在
3.3 编程思想转换
- JDK1.8加入了Lambda表达式的重量级特性
- 传递一段代码–才是我们真正的目的
//主方法:Demo02Lambda.java
public static void main(String[] args) {
//简化-使用匿名内部类,实现多线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "新线程创建了...");
}
}).start();
//使用Lambda表达式,实现多线程
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "新线程创建了...");
}).start();
}
//结果:
Thread-0新线程创建了...
Thread-1新线程创建了...
3.4 Lambda标准格式
- 匿名内部类的好处与弊端
- 好处:帮我们省去实现类的定义
- 弊端:匿名内部类的语法—>确实太复杂了
- 语义分析
- 同样的语义体现在Lambda语法中,更加简单
() -> {System.out.println("多线程任务执行!")};
- Lambda表达式的标准格式:
- 由三部分组成:
a. 一些参数
b. 一个箭头
c. 一段代码
- 由三部分组成:
- 格式:
(参数列表) -> {一些重写方法的代码};
- 解释说明格式:
- ():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔
- ->:传递的意思,把参数传递给方法体{}
- {}:重写接口的抽象方法的方法体【重点】
3.5 Lambda标准格式练习(无参无返回)
//定义一个接口:Cook.java
public interface Cook {
public abstract void makeFood();
}
//主方法:Demo01Cook.java
public class Demo01Cook {
public static void main(String[] args) {
invokeCook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭了");
}
});
//使用Lambda表达式,简化匿名内部类
invokeCook(()->{
System.out.println("吃饭了");
});
}
public static void invokeCook(Cook cook){
cook.makeFood();
}
}
//结果:
吃饭了
吃饭了
3.6 Lambda标准格式练习(有参有返回)
//定义一个Person类:Person.java
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//主方法:Demo01Arrays
public static void main(String[] args) {
//使用数组来存储多个对象
Person[] arr = {
new Person("柳岩", 21),
new Person("程超", 18),
new Person("迪丽热巴", 33)
};
//使用Arrays中的sort方法对年龄进行升序(前面-后面)排列
/*Arrays.sort(arr, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge(); //前面年龄-后面年龄
}
});*/
//使用Lambda表达式,简化匿名内部类
Arrays.sort(arr, (Person o1, Person o2) -> {
return o1.getAge() - o2.getAge();
});
//遍历数组
for (Person person : arr) {
System.out.println(person);
}
}
//结果:
Person{name='程超', age=18}
Person{name='柳岩', age=21}
Person{name='迪丽热巴', age=33}
3.7 Lambda标准格式练习(有参有返回)-自定义接口
//定义一个自定义接口:Calculator.java
public interface Calculator {
//自定义接口:计算两个整数的和的方法并返回结果
public abstract int calc(int a, int b);
}
//主方法
public class Demo01Calculator {
public static void main(String[] args) {
//调用invokeCalc方法,方法参数是一个接口,使用匿名内部类
/*invokeCalc(10, 20, new Calculator() {
@Override
public int calc(int a, int b) {
return a + b;
}
});*/
//使用Lambda表达式,简化匿名内部类
invokeCalc(10, 20, (int a, int b) -> {
return a + b;
});
}
/*
定义一个方法
参数传递两个int类型的整数
参数传递calculator接口
方法内部调用calculator中的方法计算两个数的和
*/
public static void invokeCalc(int a, int b, Calculator c){
int sum = c.calc(a, b);
System.out.println(a + "+" + b + "和为" + sum);
}
}
3.8 Lambda省略格式
- Lambda表达式:是可推导,可以省略
- 凡是根据上下文可以推导出来的内容,都可以省略书写
- 可以省略的内容:
- (参数列表):括号中参数列表的数据类型,可以省略不写
- (参数列表):括号中的参数如果只有一个,那么类型和()都可以省略
- {一些代码}:如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)
- 注意:要省略{},return,分号必须一起省略
import cn.javaadvance.day07.demo04.Cook;
import cn.javaadvance.day07.demo05.Person;
import cn.javaadvance.day07.demo06.Calculator;
import java.util.ArrayList;
import java.util.Arrays;
//主方法:
public class Demo01ArrayList {
public static void main(String[] args) {
//JDK1.7之前,创建集合对象必须把前后泛型都写上
//ArrayList<String> listA = new ArrayList<String>();
//JDK1.7之后,=号后面的泛型可以省略,后面的泛型可以根据前面的泛型“推导出来”
//ArrayList<String> listA2 = new ArrayList<>();
//优化前面案例:
//demo03
/*
//只有一行方法体:所以{},;,return可以省略
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "新线程创建了...");
}).start();
//省略后
new Thread(()-> System.out.println(Thread.currentThread().getName() + "新线程创建了...")).start();
*/
//demo04
/*
invokeCook(()->{
System.out.println("吃饭了");
});
//省略后
//只有一行方法体:所以{},;,return可以省略
invokeCook(()-> System.out.println("吃饭了"));
*/
//demo05
/*
Person[] arr = {
new Person("柳岩", 21),
new Person("程超", 18),
new Person("迪丽热巴", 33)
};
//使用Lambda表达式,简化匿名内部类
Arrays.sort(arr, (Person o1, Person o2) -> {
return o1.getAge() - o2.getAge();
});
//省略后
//只有一行方法体:所以{},;,return可以省略
//括号中==参数列表的数据类型==,可以省略不写
Arrays.sort(arr, (o1, o2) -> o1.getAge()-o2.getAge());
//遍历数组
for (Person person : arr) {
System.out.println(person);
}
*/
//demo06
/*
//使用Lambda表达式,简化匿名内部类
invokeCalc(10, 20, (int a, int b) -> {
return a + b;
});
//简化后
//
invokeCalc(10, 20, (a, b) -> a + b);
*/
}
//demo04
public static void invokeCook(Cook cook){
cook.makeFood();
}
//demo06
public static void invokeCalc(int a, int b, Calculator c){
int sum = c.calc(a, b);
System.out.println(a + "+" + b + "和为" + sum);
}
}
3.9 Lambda的使用前提
- 使用Lambda必须具有接口,且要求“接口中有且只有一个抽象方法【重点】”
- 使用Lambda必须具有上下文推断【重点】
- 就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
- 注:有且仅有一个抽象方法的接口,称为“函数式接口【!】”
来源:CSDN
作者:狂野小白兔
链接:https://blog.csdn.net/qq_40572023/article/details/104682384