1. 线程间的通信
包子铺线程生产包子,顾客线程消费包子。当包子没有时(包子状态为false),顾客线程等待,包子铺线程生产包子(即包子状态为true),并通知顾客线程(解除顾客的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。
接下来,顾客线程能否进一步执行则取决于锁的获取情况。如果顾客获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),顾客线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。
package com.itheima.demo;
class Baozi{
String size;
String xian;
boolean has=false;
}
class Guke extends Thread{
private Baozi bz;
public Guke(String name, Baozi bz) {
super(name);
this.bz = bz;
}
@Override
public void run(){
while (true){
synchronized (bz){
if (bz.has == false){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("顾客正在吃"+bz.size+bz.xian+"包子");
bz.has=false;
bz.notify();
System.out.println("顾客吃完了!");
}
}
}
}
class Baozipu extends Thread{
private Baozi bz;
public Baozipu(String name, Baozi bz) {
super(name);
this.bz = bz;
}
int count=0;
@Override
public void run(){
while (true){
synchronized (bz){
if (bz.has==true){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("没包子了,老板正在做...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count%2==0){
bz.size="大";
bz.xian="猪肉";
}
else {
bz.size="小";
bz.xian="韭菜";
}
count++;
bz.has=true;
System.out.println("老板做好了"+bz.size+bz.xian+"包子");
bz.notify();
}
}
}
}
public class HelloWorld {
public static void main(String[] args) {
Baozi bz=new Baozi();
Baozipu bzp = new Baozipu("老板",bz);
Guke gk=new Guke("顾客",bz);
bzp.start();
gk.start();
}
}
2. Lambda表达式
Lambda省去面向对象的条条框框,格式由3个部分组成:
- 一些参数
- 一个箭头
- 一段代码
Lambda表达式的标准格式为:
(参数类型 参数名称) -> { 代码语句 }
格式说明:
- 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
->
是Java 8新引入的语法格式,代表指向动作。- 大括号内的语法与传统方法体要求基本一致。
之前我们知道线程的匿名化简模式,如:
package com.itheima.demo;
public class HelloWorld {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我还要化简");
}
}).start();
}
}
结果:
我还要化简
经过Lambda化简后:
package com.itheima.demo;
public class HelloWorld {
public static void main(String[] args) {
new Thread(() ->{
System.out.println("我还要化简");
}).start();
}
}
结果:
我还要化简
此时如果run方法里只有一个语句时,还可再化简:
package com.itheima.demo;
public class HelloWorld {
public static void main(String[] args) {
new Thread(() -> System.out.println("我还要化简")).start();
}
}
结果:
我还要化简
除此之外,Lambda不仅局限在线程这儿,他在其他地方也可用到。如:
1》 接口
package com.itheima.demo;
interface Cook{
void makefood();
}
public class HelloWorld {
public static void main(String[] args) {
invokeCook(()-> System.out.println("吃饭了!"));
}
private static void invokeCook(Cook cook) {
cook.makefood();
}
}
或者分析:
package com.itheima.demo;
interface Calculator {
int calc(int a, int b);
}
public class HelloWorld {
public static void main(String[] args) {
invokeCalc(120, 130, (int a, int b) -> {
return a + b;
});
}
private static void invokeCalc(int a, int b, Calculator calculator) {
int result = calculator.calc(a, b);
System.out.println("结果是:" + result);
}
}
结果:
结果是:250
2》对象
package com.itheima.demo;
import java.util.Arrays;
import java.util.Comparator;
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class HelloWorld {
public static void main(String[] args) {
Person[] array = {
new Person("古力娜扎", 19),
new Person("迪丽热巴", 18),
new Person("马尔扎哈", 20) };
Arrays.sort(array, (Person o1,Person o2)->{
return o2.getAge() - o1.getAge();
});
for (Person person : array) {
System.out.println(person);
}
}
}
结果:
Person{name='马尔扎哈', age=20}
Person{name='古力娜扎', age=19}
Person{name='迪丽热巴', age=18}
3. Lambda省略格式
Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以根据上下文推导得知的信息,都可以省略。例如上例还可以使用Lambda的省略写法:
如之前的Calculator加法接口,还可化简为:
invokeCalc(120, 130, (a,b) -> a + b);
省略规则
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略;
- 如果小括号内有且仅有一个参,则小括号可以省略;
- 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
Lambda的使用前提
Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable
、Comparator
接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。 - 使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
4.线程池
线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
笔直向前,说到做到。
4.1 线程池的三个好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
4.2 线程池的使用
Java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
-
public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
使用线程池中线程对象的步骤:
- 创建线程池对象。
- 创建Runnable接口子类对象。(task)
- 提交Runnable接口子类对象。(take task)
- 关闭线程池(一般不做)。
Runnable实现类代码:
package com.itheima.demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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 HelloWorld {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
// 创建Runnable实例对象
MyRunnable r = new MyRunnable();
// 从线程池中获取线程对象,然后调用MyRunnable中的run()
service.submit(r);
// 再获取个线程对象,调用MyRunnable中的run()
service.submit(r);
service.submit(r);
// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
// 将使用完的线程又归还到了线程池中
// 关闭线程池
service.shutdown();
}
}
结果:
我要一个教练
我要一个教练
教练来了: pool-1-thread-2
教我游泳,交完后,教练回到了游泳池
教练来了: pool-1-thread-1
我要一个教练
教我游泳,交完后,教练回到了游泳池
教练来了: pool-1-thread-2
教我游泳,交完后,教练回到了游泳池