Catalog
  1. 1. Java多线程与IO流学习笔记(三)
    1. 1.1. 线程状态
      1. 1.1.1. 线程状态概述
      2. 1.1.2. 等待与唤醒案例
      3. 1.1.3. Timewaiting(计时等待)
    2. 1.2. 等待与唤醒机制
      1. 1.2.1. 线程间通信
      2. 1.2.2. 等待唤醒机制
    3. 1.3. 线程池
      1. 1.3.1. 概念
      2. 1.3.2. 线程池的使用
    4. 1.4. Lambda表达式
      1. 1.4.1. 函数式编程思想概述
      2. 1.4.2. 冗余的Runnable代码
      3. 1.4.3. 编程思想转换
      4. 1.4.4. lambda更优写法
      5. 1.4.5. Lambda标准格式
      6. 1.4.6. Lambda的使用前提
线程池和Lambda表达式

Java多线程与IO流学习笔记(三)

线程状态

线程状态概述

图示:

等待与唤醒案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 等待唤醒案例:线程之间的通信
* 创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到Waiting状态(无限等待)
* 创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
* 注意:
* 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
* 同步使用的锁对象必须保证唯一
* 只有锁对象才能调用wait和notify方法
*/
public class Demo01WaitAndNotify {
public static void main(String[] args) {
Object obj = new Object();

new Thread(){
@Override
public void run() {
while(true){
synchronized (obj){
System.out.println("告知老板要的包子的种类和数量");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子已经做好了");
System.out.println("------------");
}
}
}
}.start();

new Thread(){
@Override
public void run() {
while(true){
//花5秒做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("老板5秒之后做好包子,告知顾客,可以吃包子了");
//唤醒顾客
obj.notify();
}
}
}
}.start();
}
}

Timewaiting(计时等待)

进入到timewaiting有两种方式:

  • 使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
  • 使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Block状态

唤醒的方法:

  • void notify(),随机唤醒
  • void notifyAll(),全部唤醒

等待与唤醒机制

线程间通信

概念:多个线程处理同一个资源,但是处理的动作不同。

为什么要处理线程见通信:

多个线程并发执行时,默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协调通信,一次来帮我们达到多线程共同操作一份数据。

如何保证线程间通信有效利用资源:

多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段就是:等待唤醒机制

等待唤醒机制

什么是等待与唤醒机制:

这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是 故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时 候你们更多是一起合作以完成某些任务。

就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将 其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。

wait/notify 就是线程间的一种协作机制。

线程池

概念

其实就是一个容纳多个线程的容器,其中的线程可以反复调用,省去了频繁创建线程对象的操作,无须反复创建线程而消耗过多的资源。

线程池的使用

线程池:JDK1.5之后提供的

java.util.concurrent.Executors:线程池的工厂类,用来生成线程池。

Executors类中的静态方法:

static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池。

参数:int nThreads:创建线程池中包含的线程数量

返回值:ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)。

java.util.concurrent.ExecutorService:线程池接口,用来从线程池中获取线程,调用start方法,执行线程任务

submit(Runnable task)提交一个Runnable任务用于执行

线程池的使用步骤:

  1. 使用线程池的工厂类里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
  2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
  3. 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法。

Runnable类实现代码:

1
2
3
4
5
6
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"创建一个新的线程");
}
}

线程池测试类:

1
2
3
4
5
6
7
8
9
10
11
public class Demo01ThreadPool {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new RunnableImpl());
//线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());

es.shutdown();//销毁线程池,不建议执行
}
}

Lambda表达式

函数式编程思想概述

在数学中,函数就是有输入两,输出量的一套计算方案,也就是“拿什么东西做什么事情“。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式的思想则尽量忽略面向对象的复杂语法–强调做什么,而不是以什么形式做。

冗余的Runnable代码

传统写法

1
2
3
4
5
6
public class RunnableImpl implements Runnable{
@Override
public void run(){
System.out.println(Thread.currentThread.getName());
}
}
1
2
3
4
5
6
7
public class Demo01Runnable{
public static void main(String[] args){
RunnableImpl run = new RunnableImpl();
Thread t = new Thread(run);
t.start();
}
}

代码分析:

  • Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心;
  • 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类;
  • 为了省去定义一个 RunnableImpl 实现类的麻烦,不得不使用匿名内部类;
  • 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
  • 而实际上,似乎只有方法体才是关键所在。

编程思想转换

我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做 的事情是:将 run 方法体内的代码传递给 Thread 类知晓。

传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。 那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达 到目的,过程与形式其实并不重要。

lambda更优写法

1
2
3
4
5
6
7
8
9
public class Demo02Lambda {
public static void main(String[] args) {
new Thread(()-> {
System.out.println(Thread.currentThread().getName()+"新线程创建了");
}
).start();
//使用lambda表达式实现多线程
}
}

Lambda标准格式

Lambda省去面向对象的条条框框,格式由三个部分组成:

  • 一些参数
  • 一个箭头
  • 一段代码

Lambda表达式的标准格式为:

(参数类型 参数名称)->(代码语句)

格式说明:

  • 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用都好分割
  • ->是新引入的语法格式,代表指向动作
  • 大括号内的语法与传统方法体要求基本一致。

Lambda的使用前提

Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:

  1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
    无论是JDK内置的 Runnable 、 Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一 时,才可以使用Lambda。
  2. 使用Lambda必须具有上下文推断。 也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个抽象方法的接口,称为“函数式接口”。

Author: zycode1561
Link: https://zycode1561.github.io/2020/01/16/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%92%8CLambda%E8%A1%A8%E8%BE%BE%E5%BC%8F/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付宝

Comment