一、等待唤醒机制
等待唤醒机制,可以完成线程间的通信。
相关的API(这些方法都是Object中, 而不是Thread中的)
void wait():让线程等待。 直到有其他线程调用notify或者notifyAll唤醒这个线程.
void wait(long timeout):让线程等待。 直到有其他线程调用notify或者notifyAll唤醒 这个线程.或者等待时间已到也会自己醒
void notify():唤醒一个线程
void notifyAll(): 唤醒所有线程
上面的方法虽然都是Object中的方法,但是并不能直接通过对象去调用。
这些方法要放在同步代码块中,并且使用锁对象去调用。
notify方法唤醒的是当前同步代码块中等待的线程(通过哪个锁对象调用的notify,那么唤醒的就是哪个同步代码块中的线程)
当线程调用wait方法后,会释放掉锁对象。
wait和sleep的区别:
wait会释放锁
sleep不会释放锁
·
package cn.itcast.demo01;
/*
包子铺
包子铺执行的任务是生产包子
*/
public class BaoZiPu implements Runnable{
//因为要保证让包子铺和吃货用的是同一个包子,那么我们可以让这个包子从外界传递过来
//如果从外界给包子铺和吃货传递同一个包子,那么他们用的就是同一个包子了
private BaoZi baoZi;
//提供构造方法,用来让外界传递BaoZi对象,给对应的成员变量赋值
public BaoZiPu(BaoZi baoZi) {
this.baoZi = baoZi;
}
//run方法中是线程要执行的任务,此时线程要执行的任务是生产包子
@Override
public void run() {
//包子铺要无限次的生产包子,所以可以使用死循环
while(true) {
//因为wait和notify要写在同步代码块中,所以定义同步代码块
synchronized(baoZi) { //因为吃货和包子铺用的是同一个包子对象,那么可以把包子对象当做锁
//如果此时有包子, 包子铺就不会生产, 会等着吃货去吃包子
if(baoZi.flag) {
//等待,让吃货吃包子。 wait方法一定要通过锁对象去调用
try {
baoZi.wait();
} catch (InterruptedException e) {
}
}
//如果没有包子,那么就要生产包子
System.out.println("包子铺生产了一个包子");
//把包子的flag改成true表示包子已经存在了
baoZi.flag = true;
baoZi.xianer = "五仁";
//通知吃货吃包子
baoZi.notify(); //唤醒吃货吃包子
}
}
}
}
package cn.itcast.demo01;
/*
包子铺
包子铺执行的任务是生产包子
*/
public class BaoZiPu implements Runnable{
//因为要保证让包子铺和吃货用的是同一个包子,那么我们可以让这个包子从外界传递过来
//如果从外界给包子铺和吃货传递同一个包子,那么他们用的就是同一个包子了
private BaoZi baoZi;
//提供构造方法,用来让外界传递BaoZi对象,给对应的成员变量赋值
public BaoZiPu(BaoZi baoZi) {
this.baoZi = baoZi;
}
//run方法中是线程要执行的任务,此时线程要执行的任务是生产包子
@Override
public void run() {
//包子铺要无限次的生产包子,所以可以使用死循环
while(true) {
//因为wait和notify要写在同步代码块中,所以定义同步代码块
synchronized(baoZi) { //因为吃货和包子铺用的是同一个包子对象,那么可以把包子对象当做锁
//如果此时有包子, 包子铺就不会生产, 会等着吃货去吃包子
if(baoZi.flag) {
//等待,让吃货吃包子。 wait方法一定要通过锁对象去调用
try {
baoZi.wait();
} catch (InterruptedException e) {
}
}
//如果没有包子,那么就要生产包子
System.out.println("包子铺生产了一个包子");
//把包子的flag改成true表示包子已经存在了
baoZi.flag = true;
baoZi.xianer = "五仁";
//通知吃货吃包子
baoZi.notify(); //唤醒吃货吃包子
}
}
}
}
package cn.itcast.demo01;
/*
吃货
吃货要执行的任务是吃包子。
*/
public class ChiHuo implements Runnable{
//因为吃货和包子铺用的必须是同一个包子对象,所以可以定义一个成员变量,然后从外界传递一个包子对象赋值给这个成员变量
private BaoZi baoZi;
public ChiHuo(BaoZi baoZi) {
this.baoZi = baoZi;
}
//在run方法中要定义线程执行的任务,线程要执行的任务是一直吃包子。
@Override
public void run() {
//因为吃货要不停的吃包子,所以可以使用死循环
while(true) {
//因为wait和notify要在同步代码块中调用,所以写同步代码块
synchronized (baoZi) { //锁对象是包子,因为两个线程公用同一个包子
//如果没有包子, 吃货应该等待
if(!baoZi.flag) {
try {
//让该线程等待,wait方法要通过锁对象去调用
baoZi.wait();
} catch (InterruptedException e) {
}
}
//如果有包子,吃货应该吃包子
System.out.println("吃货吃了一个包子," + baoZi.xianer + "的包子");
baoZi.flag = false;
//通知包子铺去生成包子
baoZi.notify(); //唤醒包子铺线程
}
}
}
}
package cn.itcast.demo01;
public class Demo02Thread {
public static void main(String[] args) {
//创建一个包子对象
BaoZi baoZi = new BaoZi();
//创建包子铺
BaoZiPu baoZiPu = new BaoZiPu(baoZi);
//创建吃货
ChiHuo chiHuo = new ChiHuo(baoZi);
//创建两个线程一个执行包子铺的任务,一个执行吃货的任务
new Thread(baoZiPu).start();
new Thread(chiHuo).start();
}
}
public class Demo01Thread {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
obj.wait();
}
}
二、线程池
线程池:线程池就是一个容器,里面存放了很多线程,并且这些线程具有复用性,可以多次的执行任务。
线程池相关API:
Executor: 线程池的顶层接口。 可以去执行线程任务。
ExecutorService: Executor的子接口, 可以去执行任务,以及对线程进行管理。
Executors: 操作线程池的工具类。 这个工具类中有一些方法可以获取到线程池对象。 (线程池对象不是我们new出来的,而是通过Executors工具类获取到的)
Executors 工具类中获取线程池的方法:
static ExecutorService newFixedThreadPool(int nThreads): 获取一个线程池对象,参数nThreads为线程池中线程的数量。
如果要使用线程池执行任务,可以调用ExecutorService中的submit
submit(Runnable task): 参数要传递Runnable接口的实现类,表示线程要执行的任务
线程池的使用步骤:
1. 通过线程池工具类获取一个线程池对象。
2. 定义Runnable接口的实现类,定义线程池要执行的任务。
3. 调用线程池的submit方法,提交(执行)任务
4. 销毁线程池(一般不做)
package cn.itcast.demo02;
public class MyRunnableImpl implements Runnable{
@Override
public void run() {
//打印100行HelloWorld
for(int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "正在打印HelloWorld:" + i);
}
}
}
public class Demo01ThreadPool {
public static void main(String[] args) {
//获取一个线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2); //2表示线程池中有两个线程
//让线程池去执行任务。
pool.submit(new MyRunnableImpl());
pool.submit(new MyRunnableImpl());
pool.submit(new MyRunnableImpl());
//销毁线程池
pool.shutdown();
}
}
三、Lambda表达式
1、 Lambda 表达式是匿名内部类的简化写法。
Lambda 标准格式:
(参数类型 参数名) -> {
方法体;
return 返回值;
}
省略规则:
1. 小括号中参数类型可以省略
2. 小括号中只有一个参数,小括号可以省略
3. 大括号中只有一条语句,可以省略大括号,return, 分号
Lambda表达式使用前提:
1. 必须要有接口(不能是抽象类), 并且接口中有且仅有一个需要被重写的抽象方法。 (函数式接口)
2. 必须支持上下文推导。 必须有接口作为参数,或者使用一个接口类型的变量接收一下Lambda表达式
Lambda表达式是匿名内部类的简化写法,能不能和匿名内部类无条件替换?? 不可以。
Lambda表达式能做的,匿名内部类一定能做。 匿名内部类能做的Lambda不一定能做。
1. 匿名内部类可以是接口,也可以是抽象类,也可以是普通父类。 但是Lambda表达式必须是接口。
2. 匿名内部类重写的方法可以是多个。 但是Lambda表达式要求接口中必须只有一个需要被重写的抽象方法。
Lambda表达式和匿名内部类原理完全不同。
不同的地方在于他们内部使用的字节码指令完全不同。
所以Lambda表达式并不是匿名内部类的语法糖。
Lambda表达式使用的字节码指令:invokedynamic 动态的字节码执行。
其他的像匿名内部类创建对象用的都是其他的静态字节码执行: invokeinterface, invokestatic ....
public class Demo08Lambda {
public static void main(String[] args) {
//使用MyInterface接收Lambda表达式,说明这个Lambda表示的是MyInterface接口中的抽象方法的内容.
/*
MyInterface m = (int a) -> {
System.out.println(a);
};
*/
//把Lambda表达式当做参数
//invokeMethod参数是一个MyInterface接口, 此时传递的Lambda表达式就可以推倒出来, 表示的是这个接口中的抽象方法的内容.
invokeMethod((int a) -> {
System.out.println(a);
});
}
public static void invokeMethod(MyInterface m) {
m.method(10);
}
}
2、 Lambda省略格式
Lambda标准格式写法:
(参数类型 参数名) -> {
方法体;
return 返回值;
}
1. 小括号中的参数类型可以省略
2. 如果小括号中只有一个参数,那么可以省略小括号
3. 如果大括号中只有一条语句,那么无论这个方法有没有返回值,都可以省略大括号, return, 分号.
public class Demo06SimpleLambda {
public static void main(String[] args) {
//匿名内部类
invokeMethod(new MyInterface() {
@Override
public void method(int a) {
System.out.println(a);
}
});
//使用Lambda表达式标准格式
invokeMethod((int a) -> {
System.out.println(a);
});
//小括号中的参数类型可以省略
invokeMethod((a) -> {
System.out.println(a);
});
//如果小括号中只有一个参数,那么可以省略小括号
invokeMethod(a -> {
System.out.println(a);
});
//如果大括号中只有一条语句,那么可以省略大括号,return,以及分号.
invokeMethod(a -> System.out.println(a));
}
public static void invokeMethod(MyInterface m) {
m.method(10);
}
}
3、 Lambda表达式有参有返回值的练习。
public class Demo05LambdaTest {
/*
调用invokeCalc,传递Lambda表达式,求10和20相加之后的结果
*/
public static void main(String[] args) {
invokeCalc(10, 20, (int a, int b) -> {
return a + b;
});
}
/*
定义方法传递Calculator接口对象,并且在方法中调用calc方法
*/
// a = 10, b = 20 c = Lambda表达式
public static void invokeCalc(int a, int b, Calculator c) {
//calc方法会对a和b进行计算
int result = c.calc(a, b);
System.out.println("result:" + result);
}
}