JavaEE初阶——计算机工作、多线程进程汇总

目录

一,冯诺依曼体系

二,CPU基本工作流程

三,进程

1.什么是进程/任务

2. PCB——进程控制块抽象(Process Control Block)

(1).pid 进程的身份标识

(2).内存指针

(3).文件描述符表

(4).进程状态

(5).进程优先级

(6).进程上下文

(7).进程信息

3.创建进程/销毁进程

4.进程的“属性”

(1).进程状态

(2).进程优先级

(3).进程上下文

(4).进程记账信息

(5).并发和并行

5.虚拟地址空间

 6.进程间通信

四,线程

1.线程的基本概念

2.使用多线程的好处

3.线程的属性及其获取方法

4.线程构造方法

5.线程的方法

(1).start()——启动一个线程

(2).join()——等待一个线程

(3).interrupt/interrupted——中断一个线程

(4).sleep——休眠一个线程

6.线程的状态 

7.线程安全(重难点)

(1).原因

(2).synchronized——解决方法

(3).java标准库中的线程安全类

(4).总结——HOW线程安全

8.volatile关键字——内存可见性

9.wait和notify

(1).wait()方法——线程等待

(2).notify()方法——线程唤醒

 (3).notifyAll方法——唤醒所有线程

五,进程和线程的区别

六,线程的使用

1.利用extends继承Thread类

2.利用implements实现Runnable接口

3.匿名内部类创建Thread子类对象

4.匿名内部类创建 Runnable 子类对象

5.lambda 表达式创建 Runnable 子类对象

七,多线程案例

1.单例模式

(1).饿汉模式

(2).懒汉模式

2.阻塞式队列——生产者消费者模型

3.计时器


一,冯诺依曼体系

JavaEE初阶——计算机工作、多线程进程汇总_第1张图片

CPU 中央处理器(center process unit): 进行算术运算和逻辑判断.(ALU+CU+寄存器+时钟)
存储器: 分为外存和内存, 用于存储数据(使用二进制方式存储)
输入设备: 用户给计算机发号施令的设备:网卡,手写笔
输出设备: 计算机个用户汇报结果的设备.

针对存储空间
硬盘 > 内存 >> CPU
针对数据访问速度
CPU >> 内存 > 硬盘

二,CPU基本工作流程

程序=指令+数据 / CPU=ALU+CU

ALU(算数逻辑单元):CPU的执行单元,是所有中央处理器的核心组成部分,由“与门”和“或门”构成,算术逻辑单元的运算主要是进行二位元算术运算,如加法、减法、乘法。

CU(控制单元):负责程序的流程管理

1.取指令

即将一条指令从主存储器中取到的指令寄存器过程。

2.指令译码阶段

取出指令后,指令译码器按照预定的指令格式,对取回的指令进行拆分和解释,识别区分出不同的指令类别以及各种获取操作数的方法。 

3.执行指令阶段

具体实现指令的功能。CPU的不同部分被连接起来,以执行所需的操作。

4.访存取数阶段

根据指令需要访问主存、读取操作数,CPU得到操作数在主存中的地址,并从主存中读取该操作数用于运算。部分指令不需要访问主存,则可以跳过该阶段。 

5.结果写回阶段

作为最后一个阶段,结果写回阶段把执行指令阶段的运行结果数据“写回”到某种存储形式。

在指令执行完毕、结果数据写回之后,若无意外事件(如结果溢出等)发生,计算机就从程序计数器中取得下一条指令地址,开始新一轮的循环,下一个指令周期将顺序取出下一条指令。 

1. CPU 中的 PC 寄存器,是决定 CPU 要执行哪条指令的关键;
2. 指令是由 动作 + 操作对象组成
3. CPU 眼中只有指令,没有其他的概念

三,进程

1.什么是进程/任务

简单来说,可以把进程看做程序的一次运行过程;

同时,在操作系统内部,进程又是操作系统进行资源分配的基本单位。

2. PCB——进程控制块抽象(Process Control Block)

每一个PCB对象,就代表一个正在运行的程序,即进程 ,其包含

(1).pid 进程的身份标识

一个主机,同一时刻,这些进程的pid是唯一的,通过pid来区分一个进程

(2).内存指针

一个可执行文件,双击运行后,操作系统把文件中的核心数据加载到内存中

内存空间分配:存放指令+存放数据+维护运行状态

(3).文件描述符表

用于记录当前进程中打开的文件

(4).进程状态

(5).进程优先级

(6).进程上下文

(7).进程信息

3.创建进程/销毁进程

创建一个进程,本质上就是创建PCB,并且加入到链表上

1.创建PCB

2.给进程分配资源(内存/文件),赋值到PCB中

3.把PCB插入到链表中

销毁一个进程,本质上就是从链表是删除对应的PCB节点

1.把PCB从链表上删除

2.把PCB中持有的资源释放

3.销毁PCB

4.进程的“属性”

(1).进程状态

分为就绪和阻塞,一旦进程阻塞,则无法被调度到CPU上执行

(2).进程优先级

系统调用的时候,就会根据优先级,来给进程进行安排时间,当创建进程的时候,可以通过一些系统调用来干预优先级

(3).进程上下文

相当于存档与读档,即将运行中的值存储在CPU的寄存器中,下次在由此进行

(4).进程记账信息

记录每个进程在CPU上执行了多久,统计信息

(5).并发和并行

并行:某一个时刻,多个程序同时运行
并发:某一个CPU,   cpu来回切换多个程序

但对于肉眼来说,无法看出差别

5.虚拟地址空间

虚拟地址空间会给每个进程安排一些内存资源,通过专门的设备MMU来完成虚拟地址到物理地址之间的映射,增加了隔离性,使进程稳定

JavaEE初阶——计算机工作、多线程进程汇总_第2张图片

 6.进程间通信

添加一个多个进程都能访问到的公共资源,然后基于公共资源来交换数据,使得进程间进行交互

四,线程

1.线程的基本概念

一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行着多份代码.

2.使用多线程的好处

1.能够充分利用上多核CPU,提高效率

2.节省了申请资源的开销(创建第一个线程的时候,需要申请资源,后续再创建新的进程,都是共用同一份资源 )      

3.并发编程成为一种刚需

4.线程在创建,销毁,调度上,比进程更轻量

5.cpu访问自身寄存器的速度以及高速缓存的速度远远超过访问内存的速度

注意:

 1. 销毁线程的时候,也是销毁到最后一个的时候,才真正释放资源

 2.一个进程里至少有一个线程,默认为main方法所在的线程

3.线程的属性及其获取方法

ID:getld()                              ID 是线程的唯一标识,不同线程不会重复 

名称:getName()                    名称为各种调试工具用到的

状态:getState()                     状态表示线程当前所处的一个情况

优先级:getPaemon()            优先级高的线程理论上来说更容易被调度到

是否后台线程:isDaemon()    JVM会在一个进程的所有非后台线程结束后,才会结束运行。

是否存活:isAlive()                 简单的理解为 run 方法是否运行结束了

是否被中断:isInterrupted()

public class ThreadDemo {
  public static void main(String[] args) {
    //运用lambda创建Thread类对象
    Thread thread = new Thread(() -> {
      for (int i = 0; i < 10; i++) {
        try {
          System.out.println(Thread.currentThread().getName() + ": 我还活着");
          Thread.sleep(1 * 1000);
       } catch (InterruptedException e) {
          e.printStackTrace();
       }
     }
      System.out.println(Thread.currentThread().getName() + ": 我即将死去");
   });

    //将属性的获取打印
 System.out.println(Thread.currentThread().getName()+":ID:  " + thread.getId());
 System.out.println(Thread.currentThread().getName()+":名称: " + thread.getName());
 System.out.println(Thread.currentThread().getName()+":状态: " + thread.getState());
 System.out.println(Thread.currentThread().getName()+":优先级:"+thread.getPriority());
 System.out.println(Thread.currentThread().getName()+":后台线程:"+ thread.isDaemon());
 System.out.println(Thread.currentThread().getName()+":活着: " + thread.isAlive());
 System.out.println(Thread.currentThread().getName()+":被中断:"+ thread.isInterrupted());
 System.out.println(Thread.currentThread().getName()+":状态: " + thread.getState());

    thread.start();
    while (thread.isAlive()) {}

 }
}

4.线程构造方法

Thread()

创建线程对象

Thread t1 = new Thread();

Thread(Runnable target)

使用Runnable对象创建线程对象

Thread t2 = new Thread(new MyRunnable());

Thread(String name)

创建线程对象,并命名

Thread t3 = new Thread("这是我的名字");

Thread(Runnable target , String name)

使用Runnable对象创建线程对象,并命名

Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

5.线程的方法

(1).start()——启动一个线程

覆写 run 方法是提供给线程要做的事情的指令清单

而调用 start 方法, 才真的在操作系统的底层创建出一个线程. 

run和start的区别

作用功能不同:

  1. run方法的作用是描述线程具体要执行的任务;
  2. start方法的作用是真正的去申请系统线程

运行结果不同:

  1. run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;
  2. start调用方法后, start方法内部会调用Java 本地方法(封装了对系统底层的调用)真正的启动线程,并执行run方法中的代码,run 方法执行完成后线程进入销毁阶段。

(2).join()——等待一个线程

t1.join();

t2.join();

此处t1和t2是并发执行,而不是t1执行完后才继续执行

(3).interrupt/interrupted——中断一个线程

public void interrupt() 

中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位

public static boolean interrupted()

判断当前线程的中断标志位是否设置,调用后清除标志位

public boolean isInterrupted()

判断对象关联的线程的标志位是否设置,调用后不清除标志位

使用 thread 对象的 interrupted() 方法通知线程结束.

(4).sleep——休眠一个线程

Thread.sleep(1000);//ms

6.线程的状态 

NEW

TERMINATED

RUNNABLE

WAITING

TIMED_WAITING

BLOCKED

7.线程安全(重难点)

有些代码在多线程环境下执行会出现bug,就称为“线程不安全”

(1).原因

1.抢占式执行(多个线程的调度可以视为是全随机的)

2.多个线程修改一个变量  解决方法:设计成不可变对象(例如String对象)

3.修改操作不是原子的(原子解释:类似单独性,每个线程使用代码时,保证其他线程无法进入)

4.程序可见性问题(可见性指的是,一个线程对共享变量值的修改,能够及时的被其他线程看到)

5.指令重排序(简单理解为当出现重复工作时一并处理,不来回转换)

。。。。。。

(2).synchronized——解决方法

1.synchronized特性

①互斥

synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待.

②刷新内存

synchronized工作过程

1. 获得互斥锁
2. 从主内存拷贝变量的最新副本到工作的内存
3. 执行代码
4. 将更改后的共享变量的值刷新到主内存
5. 释放互斥锁

③可重入

synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;

(把自己锁死可理解为,第一次成功加锁后,第二次再次加锁因为已经占用而出现即不执行,又不堵塞的矛盾状态)

但是java中的锁是可重入锁,所以没有上诉问题

syn  chro  nize  d  ['sɪŋkrənaɪzd]

目的:并发修改——>串行修改

用法1:修饰普通方法

public synchronized void increase(){

  count ++;

}

用法2:修饰静态方法
  public synchronized static void method() {


 }

用法3:修饰代码块

public void increase(){

sychronized(this){

count++;

}

}//谁调用了increase方法,谁就是this

用法4:修饰类对象

public class SynchronizedDemo {
  public void method() {
    synchronized (SynchronizedDemo.class) { }
 }
}

注意:

(1).并不是加锁后进程t1一鼓作气操作完,中间可能有调度切换,但另一个进程t2仍然被阻塞阻断,直到t1完成(类似占座位)

(2).只给一个线程加锁没用,不涉及到锁竞争,也就不会阻塞等待

(3).使用锁的时候一定要明确对哪个对象加锁,直接影响到后面锁操作是否会触发阻塞

static class Counter {

  public int count = 0;
  synchronized void increase() {
    count++;
 }
}

public static void main(String[] args) throws InterruptedException {

final Counter counter = new Counter();

  Thread t1 = new Thread(() -> {
    for (int i = 0; i < 50000; i++) {
      counter.increase();
   }
 });

  Thread t2 = new Thread(() -> {
    for (int i = 0; i < 50000; i++) {
      counter.increase();
   }

 });

  t1.start();
  t2.start();
  t1.join();
  t2.join();
  System.out.println(counter.count);
}

(3).java标准库中的线程安全类

线程不安全:无加锁措施
ArrayList
LinkedList
HashMap
TreeMap
HashSet
TreeSet
StringBuilder


线程安全:

使用锁机制控制
Vector (不推荐使用)
HashTable (不推荐使用)
ConcurrentHashMap
StringBuffer

不使用锁

String

(4).总结——HOW线程安全

1. 使用没有共享资源的模型
2. 适用共享资源只读,不写的模型
     1. 不需要写共享资源的模型
      2. 使用不可变对象
3. 直面线程安全(重点)
      1. 保证原子性
       2. 保证顺序性
3. 保证可见性

8.volatile关键字——内存可见性

1.volatile 修饰的变量, 能够保证 "内存可见性",通俗来讲,就是使数据更及时,准确

例如:

static class Counter {
  public int flag = 0;//bug:当用户输入非0值时, t1 线程循环不会结束.
  public volatile int flag = 0;//当用户输入非0值时, t1 线程循环能够立即结束.
}

public static void main(String[] args) {

  Counter counter = new Counter();

  Thread t1 = new Thread(() -> {
    while (counter.flag == 0) {}   
 });

  Thread t2 = new Thread(() -> {
    Scanner scanner = new Scanner(System.in);
    System.out.println("输入一个整数:");
    counter.flag = scanner.nextInt();
 });

  t1.start();
  t2.start();
}

2.volatile不保证原子性

9.wait和notify

由于线程之间是抢占式执行的,因此难以预料其先后顺序,为了协调多个线程之间的执行先后顺序

我们决定使用以下方法

wait() / wait(long timeout): 让当前线程进入等待状态.

notify():唤醒在当前对象上等待的线程.

notifyAll(): 唤醒在当前对象上等待的线程.

(1).wait()方法——线程等待

wait 做的事情:
①使当前执行代码的线程进行等待. (把线程放到等待队列中)
②释放当前的锁
③满足一定条件时被唤醒, 重新尝试获取这个锁.
④wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.

wait 结束等待的条件:

①其他线程调用该对象的 notify 方法.
②wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
③其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常

举例:

public static void main(String[] args) throws InterruptedException {
  Object object = new Object();
  synchronized (object) {
    System.out.println("等待中");
    object.wait();//无notify,将一直等待下去
    System.out.println("等待结束");
 }
}

wait和sleep的区别

1. wait 需要搭配 synchronized 使用. sleep 不需要.
2. wait 是 Object 的方法 sleep 是 Thread 的静态方法 

(2).notify()方法——线程唤醒

①方法notify()在同步方法/块中调用,该方法是用来通知那些正在等待的线程,对其发出通知,使它们重新获取该对象的对象锁。
②如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 "先来后到")
③在notify()方法后,需等到线程执行完,退出同步代码块之后才会释放对象锁。

 (3).notifyAll方法——唤醒所有线程

汇总:

static class WaitTask implements Runnable {
  private Object locker;

  public WaitTask(Object locker) {
    this.locker = locker;
}

  public void run() {
    synchronized (locker) {
      while (true) {
        try {
          System.out.println("wait 开始");
          locker.wait();
          System.out.println("wait 结束");
       } catch (InterruptedException e) {
          e.printStackTrace();
       }
     }
   }
 }
}

static class NotifyTask implements Runnable {
  private Object locker;
  public NotifyTask(Object locker) {
    this.locker = locker;
 }
  @Override
  public void run() {
    synchronized (locker) {
      System.out.println("notify 开始");
      locker.notify();//1.使用唤醒一个线程
      //locker.notifyall//2.使用唤醒全部线程
      System.out.println("notify 结束");
   }
 }
}

public static void main(String[] args) throws InterruptedException {
  Object locker = new Object();
  Thread t1 = new Thread(new WaitTask(locker));
  Thread t3 = new Thread(new WaitTask(locker));
  Thread t4 = new Thread(new WaitTask(locker));
  Thread t2 = new Thread(new NotifyTask(locker));
  t1.start();
  t3.start();
  t4.start();
  Thread.sleep(1000);
  t2.start();//这里调用1.notify只能唤醒一个线程,除非使用2.notifyall,才能全部唤醒

}

注意:全部唤醒时含先后顺序 

五,进程和线程的区别

1.进程是资源分配的基本单位 ,线程是调度执行的基本单位

2.进程包含线程

3.线程比进程更轻量,创建快,销毁也快

4.同一个进程的多个线程之间共用同一份内存/文件资源 ,而进程与进程间是独立的

一个线程对应一个PCB,一个进程对应一组PCB

5. 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈

六,线程的使用

1.利用extends继承Thread类

1) 继承 Thread 来创建一个线程类.

class MyThread extends Thread {
  @Override
  public void run() {
    System.out.println("这里是线程运行的代码");
 }

  try{

       Thread.sleep(1000);//休眠1000ms.即1s

       } catch(InterruptedException){

        e.printStackTrace();

       }

}

 2) 创建 MyThread 类的实例

MyThread t = new MyThread();//非进程

3) 调用 start 方法启动线程

t.start();//创建实例并不会产生进程,使用后才会产生 

抢占式执行:当启动另外一个线程,新的线程是一个单独的执行流,和现有线程的执行流不相关,并发+并行执行,但是操作系统调度现成的时候是一个“随机”的过程,所以执行的时候也会产生随机

2.利用implements实现Runnable接口

1) 实现 Runnable 接口

class MyRunnable implements Runnable{

    public void run(){

    System.out.println("这里是线程运行的代码");

    try{

       Thread.sleep(1000);//休眠1000ms.即1s

       } catch(InterruptedException){

        e.printStackTrace();

       }

   }

}

 2) 创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.

Thread t = new Thread(new MyRunnable());

3) 调用 start 方法

t.start(); 

1.把任务从线程提取出来,把线程要干的活和线程本身分开了

2.如果要搞多个线程干相同的事情,

3.匿名内部类创建Thread子类对象

Thread t1 = new Thread() {
  @Override
  public void run() {
    System.out.println("使用匿名类创建 Thread 子类对象");
 }
};

4.匿名内部类创建 Runnable 子类对象

Thread t2 = new Thread(

new Runnable() {
  @Override
  public void run() {
    System.out.println("使用匿名类创建 Runnable 子类对象");
  }
}

);

5.lambda 表达式创建 Runnable 子类对象

Thread t3 = new Thread(

() -> System.out.println("使用匿名类创建 Thread 子类对象")

);


Thread t4 = new Thread(

() -> {
  System.out.println("使用匿名类创建 Thread 子类对象");
}

);

七,多线程案例

1.单例模式

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.

(1).饿汉模式

类加载的同时,创建实例

class Singleton {
  private static Singleton instance = new Singleton(); //instance:例子
  private Singleton() {}
  public static Singleton getInstance() {
    return instance;
 }
}

(2).懒汉模式

①单线程版

class Singleton {
  private static Singleton instance = null;
  private Singleton() {}
  public static Singleton getInstance() {
    if (instance == null) {
      instance = new Singleton();
   }
    return instance;
 }
}

 ②多线程版

使用双重 if 判定, 降低锁竞争的频率.
给 instance 加上了 volatile.

class Singleton {
  private static volatile Singleton instance = null;//volatile避免instance出现偏差
  private Singleton() {}
  public static Singleton getInstance() {
    if (instance == null) { //判定当下instance是否已经存在
      synchronized (Singleton.class) {
     if (instance == null) {
       instance = new Singleton();
       }
     }
   }
    return instance;
 }
}

1) 有三个线程, 开始执行 getInstance , 通过外层的 if (instance == null) 知道了实例还没
有创建的消息. 于是开始竞争同一把锁.

2) 其中线程1 率先获取到锁, 此时线程1 通过里层的 if (instance == null) 进一步确认实例是
否已经创建. 如果没创建, 就把这个实例创建出来.

3) 当线程1 释放锁之后, 线程2 和 线程3 也拿到锁, 也通过里层的 if (instance == null) 来
确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了.

4) 后续的线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了, 从
而不再尝试获取锁了. 降低了开销.

2.阻塞式队列——生产者消费者模型

阻塞队列是一种特殊的队列. 也遵守 "先进先出" 的原则.

(1).特性:队满无法入,队空无法取,皆堵塞,直到取走满队元素,插入空队元素

(2).生产者消费者模型
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.

① 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.

② 阻塞队列也能使生产者和消费者之间 解耦

(3).标准库中的阻塞队列

①BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
②put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
③BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.

                                                       生产者消费者模型

public static void main(String[] args) throws InterruptedException {
  BlockingQueue blockingQueue = new LinkedBlockingQueue();

  Thread customer = new Thread(() -> {
    while (true) {
      try {
        int value = blockingQueue.take();
        System.out.println("消费元素: " + value);
     } catch (InterruptedException e) {
        e.printStackTrace();
     }
   }
 }, "消费者");

customer.start();

  Thread producer = new Thread(() -> {
    Random random = new Random();
    while (true) {
      try {
        int num = random.nextInt(1000);
        System.out.println("生产元素: " + num);
        blockingQueue.put(num);
        Thread.sleep(1000);
     } catch (InterruptedException e) {
        e.printStackTrace();
     }
   }
 }, "生产者");

  producer.start();
  customer.join();
  producer.join();
}

(4).阻塞式队列的实现

通过 "循环队列" 的方式来实现.
使用 synchronized 进行加锁控制.
put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一
定队列就不满了, 因为同时可能是唤醒了多个线程).
take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)

public class BlockingQueue {

  private int[] items = new int[1000];
  private volatile int size = 0;
  private int head = 0;
  private int tail = 0;

  public void put(int value) throws InterruptedException {
    synchronized (this) {
      // 此处最好使用 while.
      // 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,
      // 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能又已经队列满了
      // 就只能继续等待
      while (size == items.length) {
        wait();
     }
      items[tail] = value;
      tail = (tail + 1) % items.length;
      size++;
      notifyAll();
   }
 }

  public int take() throws InterruptedException {
    int ret = 0;
    synchronized (this) {

      while (size == 0) {
           wait();
     }

      ret = items[head];
      head = (head + 1) % items.length;
      size--;
      notifyAll();
   }
    return ret;

 }

  public synchronized int size() {
    return size;
 }

  // 测试代码
  public static void main(String[] args) throws InterruptedException {

    BlockingQueue blockingQueue = new BlockingQueue();

    Thread customer = new Thread(() -> {
      while (true) {
        try {
          int value = blockingQueue.take();
          System.out.println(value);
       } catch (InterruptedException e) {
          e.printStackTrace();
       }
     }
   }, "消费者");
    customer.start();

    Thread producer = new Thread(() -> {
      Random random = new Random();
      while (true) {
        try {
          blockingQueue.put(random.nextInt(10000));
       } catch (InterruptedException e) {
          e.printStackTrace();
       }
     }
   }, "生产者");

    producer.start();
    customer.join();
    producer.join();
 }
}

3.计时器

 (1).定义

一个带优先级的阻塞队列,队列中的每个元素是一个带有时间属性的 Task 对象,同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

(2).定时器代码

/**
* 定时器的构成:
* 一个带优先级的阻塞队列
* 队列中的每个元素是一个 Task 对象.
* Task 中带有一个时间属性
* 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
*/
public class Timer {
  static class Task implements Comparable {
    private Runnable command;
    private long time;
    public Task(Runnable command, long time) {
      this.command = command;
      // time 中存的是绝对时间, 超过这个时间的任务就应该被执行
      this.time = System.currentTimeMillis() + time;
   }
    public void run() {
      command.run();
   }
@Override
    public int compareTo(Task o) {
      // 谁的时间小谁排前面
      return (int)(time - o.time);
   }
 }
  // 核心结构
  private PriorityBlockingQueue queue = new PriorityBlockingQueue();
  // 存在的意义是避免 worker 线程出现忙等的情况
  private Object mailBox = new Object();
  class Worker extends Thread{
    @Override
    public void run() {
      while (true) {
        try {
          Task task = queue.take();
          long curTime = System.currentTimeMillis();
          if (task.time > curTime) {
            // 时间还没到, 就把任务再塞回去
            queue.put(task);
            synchronized (mailBox) {
              // 指定等待时间 wait
              mailBox.wait(task.time - curTime);
           }
         } else {
            // 时间到了, 可以执行任务
            task.run();
         }
       } catch (InterruptedException e) {
          e.printStackTrace();
          break;
       }
     }
   }
 }
  public Timer() {
    // 启动 worker 线程
    Worker worker = new Worker();
    worker.start();
 }
  // schedule 原意为 "安排"
  public void schedule(Runnable command, long after) {
    Task task = new Task(command, after);
    queue.offer(task);
    synchronized (mailBox) {
      mailBox.notify();
   }
 }
  public static void main(String[] args) {
    Timer timer = new Timer();
    Runnable command = new Runnable() {
      @Override
      public void run() {
System.out.println("我来了");
        timer.schedule(this, 3000);
     }
   };
    timer.schedule(command, 3000);
 }
}

你可能感兴趣的:(java-ee,java,servlet)