一、 等待唤醒机制
1.1 线程之间的通信:多个线程在处理一个资源,但是各自任务不同。
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程有效的利用资源。而这种手段即—— 等待唤醒机制。
1.2 等待唤醒机制
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
注意事项:
1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
1.3 实例 — 消费者与店铺消费包子资源
包子资源类:(个人习惯无论什么类都写满构造方法 ---- hhh)
public class BaoZi {
private String pi;
private String xian;
boolean Yn = false;
public BaoZi(String pi, String xian, boolean yn) {
this.pi = pi;
this.xian = xian;
Yn = yn;
}
public BaoZi() {
}
public String getPi() {
return pi;
}
public void setPi(String pi) {
this.pi = pi;
}
public String getXian() {
return xian;
}
public void setXian(String xian) {
this.xian = xian;
}
public boolean isYn() {
return Yn;
}
public void setYn(boolean yn) {
Yn = yn;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BaoZi baoZi = (BaoZi) o;
return Yn == baoZi.Yn &&
Objects.equals(pi, baoZi.pi) &&
Objects.equals(xian, baoZi.xian);
}
@Override
public int hashCode() {
return Objects.hash(pi, xian, Yn);
}
@Override
public String toString() {
return "BaoZi{" +
"pi='" + pi + '\'' +
", xian='" + xian + '\'' +
", Yn=" + Yn +
'}';
}
}
店铺类:
public class Store implements Runnable {
Random rd = new Random();
private BaoZi b;
public Store(BaoZi b) {
this.b = b;
}
@Override
public void run() {
while (true) {
synchronized (b) {
if (b.Yn == true) {
try {
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (b.Yn == false) {
System.out.println("包子铺开始做包子......");
int bz = rd.nextInt(2);
if (bz == 0) {
b.setPi("薄皮");
b.setXian("水晶虾仁馅");
} else {
b.setPi("厚皮");
b.setXian("菠萝香蕉馅");
}
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(b.getPi() + b.getXian() + "包子做好了。");
b.Yn = true;
b.notify();
}
}
}
}
消费者类:
public class Customer implements Runnable {
private BaoZi b;
public Customer(BaoZi b) {
this.b = b;
}
@Override
public void run() {
while (true) {
synchronized (b) {
if (b.Yn == false) {
System.out.println("告诉包子铺做包子。");
try {
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (b.Yn == true) {
System.out.println("开始吃包子......");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包子吃完了");
}
b.Yn = false;
b.notify();
}
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
BaoZi b = new BaoZi();
Store s = new Store(b);
Thread th1 = new Thread(s);
Customer p = new Customer(b);
Thread th2 = new Thread(p);
th2.start();
th1.start();
}
}
二、线程池
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
合理利用线程池的有点:
1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多 的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
如何创建并使用一个线程池:
1.创建线程池对象。
2.创建Runnable实现类
3.提交Runnable接口自类对象。
创建实现类:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一个教练");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教练回到了游泳池");
}
}
测试类:
public class ThreadPoolDemo {
public static void main(String[] args) {
ExcutorService es = Excutors.newFixedThreadPool(2);
MyRunnable r = new MyRunnable();
es.submit(r);
使用lambda:
es.submit(() -> System.out.println("开启新的线程池。") );
}
三、 Lambda表达式
函数式编程思想:强调做什么,而不是怎么做、让谁做,重视结果,不重视过程。
3.1 冗余的Runnable代码
因为要覆盖重写run方法,其中的方法名称方法参数方法返回值都要再写一遍,除了方法体,其余都是多余的。
3.2 使用lambda表达式进行优化:
标准格式:(参数类型 参数名称)->{ 代码语句 }
3.3 无参无返回值的lambda表达式
给定一个厨子 Cook 接口,内含唯一的抽象方法 makeFood ,且无参数、无返回值。如下:
invokeFood(()-> System.out.println(“吃饭啦!”));
调用invokeFood方法,传入Cook接口,调用makefood方法。
3.4 有参数有返回值
如定义一个Person类,然后根据年龄排序;
public class Demo07ComparatorLambda {
public static void main(String[] args) {
Person[] array = {
new Person("古力娜扎", 19),
new Person("迪丽热巴", 18),
new Person("马尔扎哈", 20) };
Arrays.sort(array, (a,b) ‐> return a.getAge() ‐ b.getAge()); //省略写法
for (Person person : array) {
System.out.println(person);
}
}
}
3.5 有参数有返回值
给定一个计算器 Calculator 接口,内含抽象方法 calc 可以将两个int数字相加得到和值:
创建一个接口含抽象方法:
int sum(int a,int b)
测试类:
调用一个方法:
method(c,d,(a.b) -> a+b );
public int method(a,b,Calculator c){
return a+b;
}
3.6 省略格式
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略;
- 如果小括号内有且仅有一个参,则小括号可以省略;
- 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
注意:以上三个要省略需要一起省略。
Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意: - 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。无论是JDK内置的 Runnable 、Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
- 使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。