线程间通信
多个线程在处理同一资源,但处理的动作不同,如若线程之间没有通信,各自处理各自的,就会发生异常。比如一个做包子的和一个买包子的人,做包子的不能不管买包子的人数,卖包子的不能不管有没有包子,两者之间需要通信。理解这个后,我们就来说说为什么要处理线程间通信的问题。
为什么要处理线程间通信
多个线程并发执行时,在默认的情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协议通信,以此来帮助我们达到多线程共同操作一份数据,而这个协议通信就是等待与唤醒机制。
等待与唤醒机制
在前面我们谈到了锁,多个线程之间可以通过竞争锁来达到线程的有序执行,但这并不能满足所有需求。接下来介绍几种Object类中的方法。
wait()
线程进入无限等待状态
notify()
唤醒等待中的线程
notifyAll()
唤醒所有等待中的线程
注意:操作wait()和notify()的锁对象一定要相同、
下面的代码是用匿名内部内实现的,并没有创建单独的类去实现,因此在我不能更改
public class Main{
public static void main(String[] args) {
boolean judge = false;/*因为匿名内部类传的内部变量的值是不能变的
所以这里的判断其实就充当一个规范顺序的东西。*/
Object obj=new Object();//所对象
new Thread(new Runnable() {//吃包子线程。这里也可以写Lambda表达式,会更简单
@Override
public void run() {
System.out.println("我想吃包子");
synchronized (obj){
if (judge ==false){//false就是没有包子,需要等待做包子
try {
obj.wait();//进入等待
//注意这里可能会有异常抛出,在synchronized里只能用try...catch
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("好吃");
}
}
}).start();
new Thread(new Runnable() {//做包子线程
@Override
public void run() {
synchronized (obj){
if (judge ==true){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包子做完了,给你吃");
obj.notify();
}
}
}).start();
}
}
线程池
一个线程的创建与销毁是很费时间的,我们前面创建的线程并不能长久保存,当CPU处理完后就会对该线程进行销毁,占用资源。
线程池就是可以重复使用的多个线程的集合,可以一直存在。
注意:有线程池的代码在执行过程中不会自己直接结束。
线程池的使用:
线程池类Executors,与线程有关的类
创建线程池:
/*Executors中有一个静态方法newFixedThreadPool,返回值类型是
ExecutorService,而ExecutorService是线程池接口的实现类,
可以调用start方法开启线程
线程池的操作方法:
submit(Runnable task)提交一个Runnable任务用于执行
shutdown()销毁线程池,一般不建议操作
*/
ExecutorService executor=Executors.newFixedThreadPool(3);//创建3个线程
p.submit(()->{//注意这里是Runnable而不是Thread
System.out.println(Thread.currentThread().getName());
});
Lambda表达式
当需要重写方法时,需要写许多冗长的代码,用Lambda表达式可以简化这个步骤。注意:Lambda表达式是一个闭包,内部类中重写方法时可以用。大大简化了代码的书写。
Lambda表达式是函数式编程的一个重要体现,就是说我不用管它是怎么运行的,只要知道它可以完成某种功能就可以了。因此不需要访问内部的结构与变量。由此可知,外部类不可以不能访问Lambda表达式。
格式:
()->{方法体}()
new Thread(new Runnable() {//正常写的代码
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
new Thread(()->{//用Lambda表达式写的代码
System.out.println(Thread.currentThread().getName());
}).start();
这样一看就能够发现问题了,Lambda表达式确实省去了不少功夫。
在表达式中也可以有参数和返回值。
注意:Lambda表达式是可推导、可省略的。因此,必须有接口,且接口中只有一个抽象方法时才能使用Lambda表达式。
可省略的:凡是可以推导的都可以省略。
1、(参数列表)–>括号中参数列表的数据类型可以省略不写;如果参数列表只有一个,那么类型和()都可以不写。
2、{一些代码}–>如果{}中的代码只有一行,那么就可以省略{}、return和" ; "(注意要省略就一起省略)
public class Person {
public static void main(String[] args) {
int c=getSum(10,20,(int a,int b)->{
return a+b;
});
System.out.println(c);
int d=getSum(10,20,(a,b)->a+b);//要省就都省
System.out.println(d);
}
public static int getSum(int a,int b,P p){
return p.sum(a,b);
}
}
interface P{//接口中只有一个抽象方法时才能使用Lambda表达式
public int sum(int a,int b);
}