线程池/Lambda表达式
一、等待唤醒机制
1.1、线程间通信
概念:
多个线程处理同一资源,但是处理的动作(线程任务)却不相同。
为什么要有线程间的通信?
当多线程并发执行,默认情况下CPU是随机切换线程的,当我们需要多个线程来完成同一个任务,并且希望它们有规律地执行,那么多线程之间需要一些协调通信,以此来帮助我们达到多线程共同操作一份资源数据。
如何保证线程间通信有效利用资源?
多个线程在处理同一资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一变量的使用或操作。就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。需要通过一定的手段使各个线程能有效 的利用资源。而这种手段即—— 等待唤醒机制。
1.2、等待唤醒机制
是多线程间的一种协作机制。谈到线程的时候我们的是竞争(race),但是线程也有协作机制。
是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完后 再将其唤醒(notify());
wait/notify-->是线程间的一种协作机制。
等待唤醒中的方法:
1、wait:线程不再活动,不参与调度,进入wait set()等待队列中,因此不会浪费CPU资源,也不会去竞争锁。这时的线程状态是WAITING。等待其他线程的通知"notify()"动作,将等待的线程从wait set中释放出来。重新拿进入(ready queue)中。
2、notify:选取所通知对象的wait set中的一个线程释放。
3、notifyAll:释放所通知对象中wait set上的全部线程。
注意:
被通知的线程并不能立即恢复执行。因为此刻已经不再拥有锁对象。需要在此尝试去获取锁,才能在当初调用wait方法之后的地方恢复执行。
总结:
如果能够获取带锁,线程从WAITING状态变成RUNNABLE状态。
否则,从wait set出来又进入了entry set,从WAITING变成BLOCKED状态。
调用wait和notify方法需要注意的细节:
1、wait/notify方法必须要由同一个锁对象调用。因为:对应的所对象可以通过notify唤醒同一个所对象调用的wait方法后的线程。
2、wait/notify方法属于Object类的方法。因为:锁对象可以是任意对象,而任意对象都继承自Object。
3、wait/notify方法必须要用在同步代码块或者同步方法中。因为:必须通过锁对象调用这两个方法。-->静态同步方法锁对象Xxx.class,非静态同步方法锁对象this。
二、线程池
2.1、线程池思想概述
并发的数量多时,频繁的创建线程,会大大降低系统的效率。
有没有什么办法可以使线程可以复用,就是执行完一个任务,并不被销毁 ,而是继续执行其他的任务?
2.2、线程池概念
线程池:
容纳多个线程的容器。其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。
工作原理:
合理利用线程池的好处:
1、降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2、 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3、提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内 存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
2.3、线程池的使用
线程池的顶级接口是 java.util.concurrent.Executor ,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 java.util.concurrent.ExecutorService 。



此在 java.util.concurrent.Executors 线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。
Executors类中有个创建线程池的方法
public static ExecutorService newFixedThreadPool(innt nThreads):返回线程池对象。(创建的是有界线 程池,也就是池中的线程个数可以指定最大数量)。
获取到了一个线程池ExecutorService对象,那么怎么使用呢?使用线程池的方法:
public Future<?> submit(Runnable task):获取到线程池中的某一个对象,并执行。
Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
使用线程池对象的步骤:
1、创建线程池对象。 ExecutorService es = Executors.newFixedThreadPool(3);
2、创建Runnable接口子类对象(任务task)。
3、提交Runnable接口子类对象。 es.submit(task);
4、关闭线程池。es.shutdown(); 一般不做。
三、Lambda表达式
3.1、函数式编程思想概述
在数学中,函数是有输入量、输出量的计算方案。也就是“拿什么东西做什事情”。相对而言,面向对象过分情调“必须通过对象的形式来做事情”。
而函数式思想则是尽量忽略面向对象的复杂语法--强调做什么,而不是以什么形式做。
3.2、匿名内部类的写法
public class Demo01Runnale{ public static void main(String args[]){ Runnable task = new Runnable(){ @override public void run(){ System.out.println("多线程执行。"); } } };
new Thread(tsak).start; //启动线程 }
代码分析:
1、Thread类需要Runnable接口作为参数,其中的抽象方法run方法是用来知道那个线程任务的核心。
2、为了指定run方法的方法体,不得不需要Runnable接口的实现类。
3、为了省去定义一个实现类的麻烦,不得不使用匿名内部类。
4、必须覆盖重写抽象run方法,所以方法名称,方法参数,方法返回值不得不重写一遍。
5、事实上,只有方法体才是关键。
3.3、使用Lambda表达式(JDK1.8)
public class Demo02LambdaRunnable { public static void main(String[] args) { new Thread(() ‐> System.out.println("多线程任务执行!")).start(); // 启动线程 } }
3.4、回顾匿名内部类
//使用实现类 public class RunnableImpl implements Runnable { @Override public void run() { System.out.println("多线程任务执行!"); } } public class Demo03ThreadInitParam { public static void main(String[] args) { Runnable task = new RunnableImpl(); new Thread(task).start(); } } //使用匿名内部类 public class Demo04ThreadNameless { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("多线程任务执行!"); } }).start(); } }
使用匿名内部类省去了实现类的定义,但是语法较为麻烦。
3.5、Lambda标准格式
语法:
(参数类型 参数名称) ‐> { 代码语句 }
本质上:{....}中写的就是接口中抽象函数的实现。有返回值就return返回值类型,没有就不用return。
3.6、Lambda省略格式
可推导可省略
凡是可以根据上下文推导得知的信息,都可以省略。
省略规则:
1、小括号内的参数可以省略。
2、如果小括号内有且仅有一个参数,小括号可以省略。
3、如果大括号有且仅有一个语句,则无论是否有返回值,都可以省略大括号,return关键字及语句分号。
3.7、Lambda的使用前提:
Lambda的语法非常简洁,完全没有面向对象复杂的舒服。但是使用时需要注意:
1、使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
2、使用lambda必须具有上下文推断。
也就是方法的参数或局部变量必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
附注:
有且仅有一个抽象方法的接口,称为函数式接口,@Functional Interface

浙公网安备 33010602011771号