多线程案例(单例模式、阻塞式队列、定时器及线程池)

206阅读模式

一、单例模式

单例模式是常见的设计模式之一。文章源自懂站帝-http://www.sfdkj.com/12953.html

什么是设计模式?文章源自懂站帝-http://www.sfdkj.com/12953.html

设计模式,就相当于“棋谱"中一些固定的代码套路,按照棋谱来下,一般就不会下的很差。软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏。文章源自懂站帝-http://www.sfdkj.com/12953.html

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.文章源自懂站帝-http://www.sfdkj.com/12953.html

这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个。文章源自懂站帝-http://www.sfdkj.com/12953.html

单例模式具体的实现方式, 分成 “饿汉” 和 “懒汉” 两种。文章源自懂站帝-http://www.sfdkj.com/12953.html

这里举一个例子:洗碗文章源自懂站帝-http://www.sfdkj.com/12953.html

  1. 中午这顿饭,使用了4个碗,吃完之后,立即把这4个碗给洗了 [饿汉]
  2. 中午这顿饭,使用了4个碗.吃完之后,先不洗,晚上这顿,只需要2个碗,然后就只洗2个即可 [懒汉]―>是一种更加高效的操作

饿汉的单例模式,是比较着急的去进行创建实例的.文章源自懂站帝-http://www.sfdkj.com/12953.html

懒汉的单例模式,是不太着急的去创建实例,只是在用的时候才真正创建.文章源自懂站帝-http://www.sfdkj.com/12953.html

1.1 饿汉模式

类加载的同时,创建实例。文章源自懂站帝-http://www.sfdkj.com/12953.html

一个Java程序中,一个类对象只存在一份(JVM保证的)进—步的也就保证了类的static成员也是只有一份的。文章源自懂站帝-http://www.sfdkj.com/12953.html

//用过Singleton来实现单例模式,保证Singleton这个类有唯一实例文章源自懂站帝-http://www.sfdkj.com/12953.html

//饿汉模式文章源自懂站帝-http://www.sfdkj.com/12953.html

class Singleton{文章源自懂站帝-http://www.sfdkj.com/12953.html

//static修饰的成员---“类成员”-》“类属性/方法”文章源自懂站帝-http://www.sfdkj.com/12953.html

//1.使用static来创建一个实例,并且立即进行实例化文章源自懂站帝-http://www.sfdkj.com/12953.html

//这个instance对应的实例,就是该类的唯一实例文章源自懂站帝-http://www.sfdkj.com/12953.html

private static Singleton instance = new Singleton();文章源自懂站帝-http://www.sfdkj.com/12953.html

//2.为了防止在其他地方new这个Singleton个Singleton设为私有的文章源自懂站帝-http://www.sfdkj.com/12953.html

private Singleton(){};文章源自懂站帝-http://www.sfdkj.com/12953.html

//构造一个方法,让外面能够拿到唯一实例文章源自懂站帝-http://www.sfdkj.com/12953.html

public static Singleton getInstance(){文章源自懂站帝-http://www.sfdkj.com/12953.html

return instance;文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

public class Test06 {文章源自懂站帝-http://www.sfdkj.com/12953.html

public static void main(String[] args) {文章源自懂站帝-http://www.sfdkj.com/12953.html

Singleton instance = Singleton.getInstance();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

饿汉模式中getlnstance,仅仅是读取了变量的内容。如果多个线程只是读同一个变量,不修改,此时仍然是线程安全的。文章源自懂站帝-http://www.sfdkj.com/12953.html

1.2 懒汉模式

类加载的时候不创建实例. 第一次使用的时候才创建实例文章源自懂站帝-http://www.sfdkj.com/12953.html

  • 懒汉模式-单线程版

class Singleton1{文章源自懂站帝-http://www.sfdkj.com/12953.html

private static Singleton1 in = null;文章源自懂站帝-http://www.sfdkj.com/12953.html

private Singleton1(){};文章源自懂站帝-http://www.sfdkj.com/12953.html

public static Singleton1 getInstance(){文章源自懂站帝-http://www.sfdkj.com/12953.html

//不是原子的,既包含读,又包含修改文章源自懂站帝-http://www.sfdkj.com/12953.html

if(in == null){文章源自懂站帝-http://www.sfdkj.com/12953.html

in = new Singleton1();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

return in;文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

懒汉模式中,既包含了读,又包含了修改.而且这里的读和修改,还是分成两个步骤的(不是原子的)存在线程安全问题。文章源自懂站帝-http://www.sfdkj.com/12953.html

  • 懒汉模式-多线程版

线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致创建出多个实例.文章源自懂站帝-http://www.sfdkj.com/12953.html

加锁操作,可以改变这里的线程安全问题。使用这里的类对象作为锁对象(类对象在一个程序中只有唯一一份,就能保证多个线程调用getInstance的时候都是针对同一个对象进行的加锁)。文章源自懂站帝-http://www.sfdkj.com/12953.html

class Singleton1{文章源自懂站帝-http://www.sfdkj.com/12953.html

private static Singleton1 instance= null;文章源自懂站帝-http://www.sfdkj.com/12953.html

private Singleton1(){};文章源自懂站帝-http://www.sfdkj.com/12953.html

public static Singleton1 getInstance(){文章源自懂站帝-http://www.sfdkj.com/12953.html

synchronized (Singleton1.class){文章源自懂站帝-http://www.sfdkj.com/12953.html

if(instance== null){文章源自懂站帝-http://www.sfdkj.com/12953.html

instance= new Singleton1();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

return instance;文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

  • 懒汉模式-多线程版(改进)

当前虽然加锁之后,线程安全问题得到解决了,但是又有了新的问题 :对于刚才这个懒汉模式的代码来说。线程不安全是发生在instance被初始化之前的.未初始化的时候,多线程调用getinstance,就可能同时涉及到读和修改.但是一旦instance被初始化之后(一定不是nul, if条件一定不成立了),getInstance操作就只剩下两个读操作也就线程安全了。文章源自懂站帝-http://www.sfdkj.com/12953.html

而按照上述的加锁方式,无论代码是初始化之后,还是初始化之前,每次调用 getinstance方法都会进行加锁.也就意味着即使是初始化之后(已经线程安全了),仍然存在大量的锁竞争文章源自懂站帝-http://www.sfdkj.com/12953.html

以下代码在加锁的基础上, 做出了进一步改动:文章源自懂站帝-http://www.sfdkj.com/12953.html

1.使用双重 if 判定, 降低锁竞争的频率。文章源自懂站帝-http://www.sfdkj.com/12953.html

改进方案: 让getInstance初始化之前,才进行加锁,初始化之后,就不再加锁了。在加锁这里再加上一层条件判定即可.条件就是当前是否已经初始化完成 (instance == null)。文章源自懂站帝-http://www.sfdkj.com/12953.html

在使用了双重if判定之后,当前这个代码中还存在一个重要的问题:如果多个线程,都去调用这里的getlnstance 方法,就会造成大量的读instance内存的操作,这样可能会让编译器把这个读内存操作优化成读寄存器操作文章源自懂站帝-http://www.sfdkj.com/12953.html

—旦这里触发了优化,后续如果第一个线程已经完成了针对instance的修改,那么紧接着后面的线程都感知不到这个修改,仍然把 instance当成null 。所以这里需要给 instance 加上了 volatile。文章源自懂站帝-http://www.sfdkj.com/12953.html

2.给 instance 加上了 volatile文章源自懂站帝-http://www.sfdkj.com/12953.html

class Singleton2{文章源自懂站帝-http://www.sfdkj.com/12953.html

//不是立即初始化实例文章源自懂站帝-http://www.sfdkj.com/12953.html

//volatile 保证内存可见性文章源自懂站帝-http://www.sfdkj.com/12953.html

private static volatile Singleton2 instance = null;文章源自懂站帝-http://www.sfdkj.com/12953.html

private Singleton2(){};文章源自懂站帝-http://www.sfdkj.com/12953.html

//只有在真正使用这个实例的时候,才会真正的去创建这个实例文章源自懂站帝-http://www.sfdkj.com/12953.html

public static Singleton2 getInstance(){文章源自懂站帝-http://www.sfdkj.com/12953.html

//使用这里的类对象作为锁对象,类对象在一个程序中只有一份,文章源自懂站帝-http://www.sfdkj.com/12953.html

//判定的是是否要加锁。降低了锁竞争文章源自懂站帝-http://www.sfdkj.com/12953.html

if(instance == null){文章源自懂站帝-http://www.sfdkj.com/12953.html

//加锁操作,保证了线程安全文章源自懂站帝-http://www.sfdkj.com/12953.html

synchronized (Singleton2.class){文章源自懂站帝-http://www.sfdkj.com/12953.html

//判定的是是否要创建实例文章源自懂站帝-http://www.sfdkj.com/12953.html

if(instance == null){文章源自懂站帝-http://www.sfdkj.com/12953.html

instance = new Singleton2();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

return instance;文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

public class Test07 {文章源自懂站帝-http://www.sfdkj.com/12953.html

public static void main(String[] args) {文章源自懂站帝-http://www.sfdkj.com/12953.html

Singleton2 instance = Singleton2.getInstance();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

二、阻塞式队列

阻塞队列是什么?文章源自懂站帝-http://www.sfdkj.com/12953.html

阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.文章源自懂站帝-http://www.sfdkj.com/12953.html

阻塞队列是一种线程安全的数据结构, 并且具有以下特性 : 产生阻塞效果文章源自懂站帝-http://www.sfdkj.com/12953.html

  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
  • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

阻塞队列的一个典型应用场景就是 “生产者消费者模型”. 这是一种非常典型的开发模型。文章源自懂站帝-http://www.sfdkj.com/12953.html

2.1 生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题文章源自懂站帝-http://www.sfdkj.com/12953.html

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.文章源自懂站帝-http://www.sfdkj.com/12953.html

1.阻塞队列也能使生产者和消费者之间解耦文章源自懂站帝-http://www.sfdkj.com/12953.html

生产者消费者模型,是实际开发中非常有用的一种多线程开发手段。尤其是在服务器开发的场景中:文章源自懂站帝-http://www.sfdkj.com/12953.html

假设有两个服务器AB,A作为入口服务器直接接收用户的网络请求,B作为应用服务器,来给A提供一些数据。文章源自懂站帝-http://www.sfdkj.com/12953.html

多线程案例(单例模式、阻塞式队列、定时器及线程池)

文章源自懂站帝-http://www.sfdkj.com/12953.html

如果不使用生产者消费者模型。此时A和B的耦合性是比较强的:在开发A代码的时候就得充分了解到B提供的一些接口;开发B代码的时候也得充分了解到A是怎么调用的;—旦想把B换成C,A的代码就需要较大的改动,而且如果B挂了,也可能直接导致A也顺带挂了。文章源自懂站帝-http://www.sfdkj.com/12953.html

使用生产者消费者模型,就可以降低这里的耦合.文章源自懂站帝-http://www.sfdkj.com/12953.html

多线程案例(单例模式、阻塞式队列、定时器及线程池)

文章源自懂站帝-http://www.sfdkj.com/12953.html

对于请求:A是生产者,B是消费者.对于响应:A是消费者,B是生产者.阻塞队列都是作为交易场所 ,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。文章源自懂站帝-http://www.sfdkj.com/12953.html

A只需要关注如何和队列交互,不需要认识B;文章源自懂站帝-http://www.sfdkj.com/12953.html

B也只需要关注如何和队列交互,也不需要认识A;文章源自懂站帝-http://www.sfdkj.com/12953.html

队列是不变的,如果B挂了,对于A没啥影响;如果把B换成C,A也完全感知不到。文章源自懂站帝-http://www.sfdkj.com/12953.html

2.能够对于请求进行“削峰填谷文章源自懂站帝-http://www.sfdkj.com/12953.html

未使用生产者消费者模型的时候,如果请求量突然暴涨(不可控)文章源自懂站帝-http://www.sfdkj.com/12953.html

多线程案例(单例模式、阻塞式队列、定时器及线程池)

文章源自懂站帝-http://www.sfdkj.com/12953.html

A暴涨导致B暴涨;文章源自懂站帝-http://www.sfdkj.com/12953.html

A作为入口服务器,计算量很轻,请求暴涨,问题不大.B作为应用服务器,计算量可能很大,需要的系统资源也更多.如果请求更多了,需要的资源进—步增加,如果主机的硬件不够,可能程序就挂了。文章源自懂站帝-http://www.sfdkj.com/12953.html

多线程案例(单例模式、阻塞式队列、定时器及线程池)

文章源自懂站帝-http://www.sfdkj.com/12953.html

A请求暴涨=>阻塞队列的请求暴涨,由于阻塞队列没啥计算量,就只是单纯的存个数据,就能抗住更大的压力.文章源自懂站帝-http://www.sfdkj.com/12953.html

B这边仍然按照原来的速度来消费数据,不会因为A的暴涨而引起暴涨.B就被保护的很好,就不会因为这种请求的波动而引起崩溃 。文章源自懂站帝-http://www.sfdkj.com/12953.html

"削峰”这种峰值很多时候不是持续的,就一阵过去了(比如双十一秒杀活动)就又恢复了 。文章源自懂站帝-http://www.sfdkj.com/12953.html

"填谷"B仍然是按照原有的频率来处理之前积压的数据。文章源自懂站帝-http://www.sfdkj.com/12953.html

实际开发中使用到的"阻塞队列"并不是一个简单的数据结构了,而是一个/一组专门的服务器程序。并且它提供的功能也不仅仅是阻塞队列的功能,还会在这基础之上提供更多的功能(对于数据持久化存储,支持多个数据通道,支持多节点容灾冗余备份,支持管理面板,方便配置参数…),这样的队列又起了个新的名字,"消息队列”(未来开发中广泛使用到的组件)。文章源自懂站帝-http://www.sfdkj.com/12953.html

2.2 标准库中的阻塞队列

在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.文章源自懂站帝-http://www.sfdkj.com/12953.html

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

import java.util.concurrent.BlockingQueue;文章源自懂站帝-http://www.sfdkj.com/12953.html

import java.util.concurrent.LinkedBlockingQueue;文章源自懂站帝-http://www.sfdkj.com/12953.html

public class Demo21 {文章源自懂站帝-http://www.sfdkj.com/12953.html

public static void main(String[] args) throws InterruptedException {文章源自懂站帝-http://www.sfdkj.com/12953.html

BlockingDeque<String> queue = new LinkedBlockingDeque<>();文章源自懂站帝-http://www.sfdkj.com/12953.html

//入队列文章源自懂站帝-http://www.sfdkj.com/12953.html

queue.put("hello");文章源自懂站帝-http://www.sfdkj.com/12953.html

//出队列文章源自懂站帝-http://www.sfdkj.com/12953.html

String s = queue.take();文章源自懂站帝-http://www.sfdkj.com/12953.html

System.out.println(s);//hello文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

2.3 阻塞队列实现

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

先实现一个普通的队列,再加上线程安全,再加上阻塞(使用wait和notify机制).文章源自懂站帝-http://www.sfdkj.com/12953.html

对于put来说,阻塞条件,就是队列为满。put 中的wait要由take来唤醒.只要take成功了一个元素,不就队列不满了,就可以进行唤醒了.文章源自懂站帝-http://www.sfdkj.com/12953.html

对于take来说,阻塞条件,就是队列为空。对于take 中的等待,条件是队列为空.队列不为空,也就是put成功之后,就来唤醒.文章源自懂站帝-http://www.sfdkj.com/12953.html

import java.util.concurrent.BlockingDeque;文章源自懂站帝-http://www.sfdkj.com/12953.html

import java.util.concurrent.LinkedBlockingDeque;文章源自懂站帝-http://www.sfdkj.com/12953.html

class MyBlocking{文章源自懂站帝-http://www.sfdkj.com/12953.html

//基于数组实现阻塞队列文章源自懂站帝-http://www.sfdkj.com/12953.html

private int[] data = new int[1000];文章源自懂站帝-http://www.sfdkj.com/12953.html

//队列长度文章源自懂站帝-http://www.sfdkj.com/12953.html

private int size = 0;文章源自懂站帝-http://www.sfdkj.com/12953.html

//队首下标文章源自懂站帝-http://www.sfdkj.com/12953.html

private int head = 0;文章源自懂站帝-http://www.sfdkj.com/12953.html

//队尾下标文章源自懂站帝-http://www.sfdkj.com/12953.html

private int tail = 0;文章源自懂站帝-http://www.sfdkj.com/12953.html

private static Object locker = new Object();文章源自懂站帝-http://www.sfdkj.com/12953.html

public void put(int value) throws InterruptedException {文章源自懂站帝-http://www.sfdkj.com/12953.html

synchronized (locker){文章源自懂站帝-http://www.sfdkj.com/12953.html

if(size == data.length){文章源自懂站帝-http://www.sfdkj.com/12953.html

//开始时站实现返回return文章源自懂站帝-http://www.sfdkj.com/12953.html

// return;文章源自懂站帝-http://www.sfdkj.com/12953.html

//针对哪个对象加锁,就返回哪个对象的wait 如果是针对this加锁,就this.wait文章源自懂站帝-http://www.sfdkj.com/12953.html

locker.wait();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

//把新的元素方法tail位置上文章源自懂站帝-http://www.sfdkj.com/12953.html

data[tail] = value;文章源自懂站帝-http://www.sfdkj.com/12953.html

tail++;文章源自懂站帝-http://www.sfdkj.com/12953.html

//处理tail到达元素末尾的情况,需要从头开始,重新循环文章源自懂站帝-http://www.sfdkj.com/12953.html

//第1种写法文章源自懂站帝-http://www.sfdkj.com/12953.html

if(tail >= data.length){文章源自懂站帝-http://www.sfdkj.com/12953.html

tail = 0;文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

//第2种写法文章源自懂站帝-http://www.sfdkj.com/12953.html

// tail = tail % data.length;文章源自懂站帝-http://www.sfdkj.com/12953.html

size++;文章源自懂站帝-http://www.sfdkj.com/12953.html

//如果入队列成功,则队列非空,就唤醒take中的阻塞等待文章源自懂站帝-http://www.sfdkj.com/12953.html

locker.notify();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

//出队列文章源自懂站帝-http://www.sfdkj.com/12953.html

//使用包装类文章源自懂站帝-http://www.sfdkj.com/12953.html

public Integer take() throws InterruptedException {文章源自懂站帝-http://www.sfdkj.com/12953.html

synchronized (locker){文章源自懂站帝-http://www.sfdkj.com/12953.html

if(size == 0){文章源自懂站帝-http://www.sfdkj.com/12953.html

// return null;文章源自懂站帝-http://www.sfdkj.com/12953.html

locker.wait();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

int ret = data[head];文章源自懂站帝-http://www.sfdkj.com/12953.html

head++;文章源自懂站帝-http://www.sfdkj.com/12953.html

if(head >= data.length){文章源自懂站帝-http://www.sfdkj.com/12953.html

head = 0;文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

size--;文章源自懂站帝-http://www.sfdkj.com/12953.html

// take成功之后,就唤醒put中的等待.文章源自懂站帝-http://www.sfdkj.com/12953.html

locker.notify();文章源自懂站帝-http://www.sfdkj.com/12953.html

return ret;文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

public class Test08 {文章源自懂站帝-http://www.sfdkj.com/12953.html

public static void main(String[] args) {文章源自懂站帝-http://www.sfdkj.com/12953.html

MyBlocking queue = new MyBlocking();文章源自懂站帝-http://www.sfdkj.com/12953.html

//实现一个生产者消费者模式文章源自懂站帝-http://www.sfdkj.com/12953.html

Thread t = new Thread(()->{文章源自懂站帝-http://www.sfdkj.com/12953.html

int num = 0;文章源自懂站帝-http://www.sfdkj.com/12953.html

while (true){文章源自懂站帝-http://www.sfdkj.com/12953.html

System.out.println("生产了:" + num);文章源自懂站帝-http://www.sfdkj.com/12953.html

try {文章源自懂站帝-http://www.sfdkj.com/12953.html

queue.put(num);文章源自懂站帝-http://www.sfdkj.com/12953.html

// 当生产者生产的慢一些的时候, 消费者就得跟着生产者的步伐走.生产一个消费一个文章源自懂站帝-http://www.sfdkj.com/12953.html

// Thread.sleep(500);文章源自懂站帝-http://www.sfdkj.com/12953.html

} catch (InterruptedException e) {文章源自懂站帝-http://www.sfdkj.com/12953.html

e.printStackTrace();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

num++;文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

});文章源自懂站帝-http://www.sfdkj.com/12953.html

t.start();文章源自懂站帝-http://www.sfdkj.com/12953.html

Thread t2 = new Thread(()->{文章源自懂站帝-http://www.sfdkj.com/12953.html

int num = 0;文章源自懂站帝-http://www.sfdkj.com/12953.html

while (true){文章源自懂站帝-http://www.sfdkj.com/12953.html

System.out.println("消费了:" + num);文章源自懂站帝-http://www.sfdkj.com/12953.html

try {文章源自懂站帝-http://www.sfdkj.com/12953.html

num = queue.take();文章源自懂站帝-http://www.sfdkj.com/12953.html

Thread.sleep(500);文章源自懂站帝-http://www.sfdkj.com/12953.html

} catch (InterruptedException e) {文章源自懂站帝-http://www.sfdkj.com/12953.html

e.printStackTrace();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

});文章源自懂站帝-http://www.sfdkj.com/12953.html

t2.start();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

上述代码输出结果:文章源自懂站帝-http://www.sfdkj.com/12953.html

多线程案例(单例模式、阻塞式队列、定时器及线程池)

文章源自懂站帝-http://www.sfdkj.com/12953.html

当生产者生产的慢一些的时候, 消费者就得跟着生产者的步伐走,生产一个消费一个:文章源自懂站帝-http://www.sfdkj.com/12953.html

多线程案例(单例模式、阻塞式队列、定时器及线程池)

文章源自懂站帝-http://www.sfdkj.com/12953.html

三、定时器

定时器也是软件开发中的一个重要组件。类似于一个 “闹钟”: 达到一个设定的时间之后, 就执行某个指定好的代码。文章源自懂站帝-http://www.sfdkj.com/12953.html

3.1 标准库中的定时器

  • 标准库中提供了一个 Timer 类. Timer 内部是有专门的线程,来负责执行注册的任务的,Timer 类的核心方法为 schedule .
  • schedule 包含两个参数:第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)。

import java.util.Timer;文章源自懂站帝-http://www.sfdkj.com/12953.html

import java.util.TimerTask;文章源自懂站帝-http://www.sfdkj.com/12953.html

public class Test09 {文章源自懂站帝-http://www.sfdkj.com/12953.html

public static void main(String[] args) {文章源自懂站帝-http://www.sfdkj.com/12953.html

Timer timer = new Timer();文章源自懂站帝-http://www.sfdkj.com/12953.html

timer.schedule(new TimerTask() {文章源自懂站帝-http://www.sfdkj.com/12953.html

@Override文章源自懂站帝-http://www.sfdkj.com/12953.html

public void run() {文章源自懂站帝-http://www.sfdkj.com/12953.html

System.out.println("hello");文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

},3000);文章源自懂站帝-http://www.sfdkj.com/12953.html

System.out.println("main");文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

先执行main,三秒之后执行hello。文章源自懂站帝-http://www.sfdkj.com/12953.html

输出结果:文章源自懂站帝-http://www.sfdkj.com/12953.html

多线程案例(单例模式、阻塞式队列、定时器及线程池)

文章源自懂站帝-http://www.sfdkj.com/12953.html

3.2 实现定时器

Timer内部需要什么?文章源自懂站帝-http://www.sfdkj.com/12953.html

1.描述任务文章源自懂站帝-http://www.sfdkj.com/12953.html

创建一个专门的类来表示一个定时器中的任务.(TimerTask)文章源自懂站帝-http://www.sfdkj.com/12953.html

2.组织任务(使用一定的数据结构把一些任务给放到一起)文章源自懂站帝-http://www.sfdkj.com/12953.html

通过—定的数据结构(一个带优先级的阻塞队列)来组织.文章源自懂站帝-http://www.sfdkj.com/12953.html

为啥要带优先级呢?文章源自懂站帝-http://www.sfdkj.com/12953.html

因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.文章源自懂站帝-http://www.sfdkj.com/12953.html

3.执行时间到了的任务文章源自懂站帝-http://www.sfdkj.com/12953.html

需要先执行时间最靠前的任务,就需要有一个线程,不停的去检查当前优先队列的队首元素,看看当前最靠前的这个任务时间是否到了。文章源自懂站帝-http://www.sfdkj.com/12953.html

import java.util.concurrent.PriorityBlockingQueue;文章源自懂站帝-http://www.sfdkj.com/12953.html

//创建一个类,表示一个任务文章源自懂站帝-http://www.sfdkj.com/12953.html

class MyTask implements Comparable<MyTask>{ //实现Comparable接口,设定比较规则文章源自懂站帝-http://www.sfdkj.com/12953.html

//任务具体要干什么文章源自懂站帝-http://www.sfdkj.com/12953.html

private Runnable runnable;文章源自懂站帝-http://www.sfdkj.com/12953.html

//任务具体啥时候干,保存任务要执行的毫秒级时间戳文章源自懂站帝-http://www.sfdkj.com/12953.html

private long time;文章源自懂站帝-http://www.sfdkj.com/12953.html

//提供一个构造方法文章源自懂站帝-http://www.sfdkj.com/12953.html

public MyTask(Runnable runnable, long delay) { //delay是一个时间间隔,不是绝对的时间戳的值文章源自懂站帝-http://www.sfdkj.com/12953.html

this.runnable = runnable;文章源自懂站帝-http://www.sfdkj.com/12953.html

this.time = System.currentTimeMillis() + delay;文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

public void run(){文章源自懂站帝-http://www.sfdkj.com/12953.html

菠萝的博客();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

public long getTime() {文章源自懂站帝-http://www.sfdkj.com/12953.html

return time;文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

@Override文章源自懂站帝-http://www.sfdkj.com/12953.html

public int compareTo(MyTask o) {文章源自懂站帝-http://www.sfdkj.com/12953.html

//让时间小的在前,时间大的在后文章源自懂站帝-http://www.sfdkj.com/12953.html

return (int)(this.time - o.time);文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

//定时器文章源自懂站帝-http://www.sfdkj.com/12953.html

class MyTimer{文章源自懂站帝-http://www.sfdkj.com/12953.html

//定时器内部能够存放多个任务文章源自懂站帝-http://www.sfdkj.com/12953.html

//此处的队列要考虑到线程安全问题 可能在多个线程里进行注册任务.文章源自懂站帝-http://www.sfdkj.com/12953.html

// 同时还有一个专门的线程来取任务执行.此处的队列就需要注意线程安全问题.文章源自懂站帝-http://www.sfdkj.com/12953.html

private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();文章源自懂站帝-http://www.sfdkj.com/12953.html

//使用schedule方法来注册任务到队列中文章源自懂站帝-http://www.sfdkj.com/12953.html

public void schedule(Runnable runnable,long delay){文章源自懂站帝-http://www.sfdkj.com/12953.html

MyTask task = new MyTask(runnable,delay);文章源自懂站帝-http://www.sfdkj.com/12953.html

queue.put(task);文章源自懂站帝-http://www.sfdkj.com/12953.html

//每次任务插入成功之后,都唤醒一下扫描线程,让线程重新检查一下队首的任务,看是否时间到了要执行文章源自懂站帝-http://www.sfdkj.com/12953.html

synchronized (locker){文章源自懂站帝-http://www.sfdkj.com/12953.html

locker.notify();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

private Object locker = new Object();文章源自懂站帝-http://www.sfdkj.com/12953.html

//创建一个扫描线程文章源自懂站帝-http://www.sfdkj.com/12953.html

public MyTimer(){文章源自懂站帝-http://www.sfdkj.com/12953.html

Thread t = new Thread(()->{文章源自懂站帝-http://www.sfdkj.com/12953.html

while (true){文章源自懂站帝-http://www.sfdkj.com/12953.html

try {文章源自懂站帝-http://www.sfdkj.com/12953.html

//先取出队首元素文章源自懂站帝-http://www.sfdkj.com/12953.html

MyTask task = queue.take();文章源自懂站帝-http://www.sfdkj.com/12953.html

long curTime = System.currentTimeMillis();文章源自懂站帝-http://www.sfdkj.com/12953.html

//比较一下看当前时间到了吗文章源自懂站帝-http://www.sfdkj.com/12953.html

if(curTime < task.getTime()){文章源自懂站帝-http://www.sfdkj.com/12953.html

//时间没到,把任务塞回到队列中文章源自懂站帝-http://www.sfdkj.com/12953.html

queue.put(task);文章源自懂站帝-http://www.sfdkj.com/12953.html

//指定一个等待时间文章源自懂站帝-http://www.sfdkj.com/12953.html

synchronized (locker){文章源自懂站帝-http://www.sfdkj.com/12953.html

//wait可以被中途唤醒 sleep不能被中途唤醒文章源自懂站帝-http://www.sfdkj.com/12953.html

locker.wait(task.getTime() - curTime);文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}else {文章源自懂站帝-http://www.sfdkj.com/12953.html

//时间到了,执行任务文章源自懂站帝-http://www.sfdkj.com/12953.html

task.run();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

} catch (InterruptedException e) {文章源自懂站帝-http://www.sfdkj.com/12953.html

e.printStackTrace();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

});文章源自懂站帝-http://www.sfdkj.com/12953.html

t.start();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

public class Test10 {文章源自懂站帝-http://www.sfdkj.com/12953.html

public static void main(String[] args) {文章源自懂站帝-http://www.sfdkj.com/12953.html

MyTimer myTimer = new MyTimer();文章源自懂站帝-http://www.sfdkj.com/12953.html

myTimer.schedule(new Runnable() {文章源自懂站帝-http://www.sfdkj.com/12953.html

@Override文章源自懂站帝-http://www.sfdkj.com/12953.html

public void run() {文章源自懂站帝-http://www.sfdkj.com/12953.html

System.out.println("hello");文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

},3000);文章源自懂站帝-http://www.sfdkj.com/12953.html

System.out.println("main");文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

1.描述—个任务: runnable + time文章源自懂站帝-http://www.sfdkj.com/12953.html

2.使用优先队列来组织若干个任务. PriorityBlockingQueue文章源自懂站帝-http://www.sfdkj.com/12953.html

3.实现schedule方法来注册任务到队列中.文章源自懂站帝-http://www.sfdkj.com/12953.html

4.创建一个扫描线程,这个扫描线程不停的获取到队首元素,并且判定时间是否到达.文章源自懂站帝-http://www.sfdkj.com/12953.html

另外要注意,让MyTask 类能够支持比较:实现Comparable接口,并设定比较规则文章源自懂站帝-http://www.sfdkj.com/12953.html

注意解决这里的忙等问题:文章源自懂站帝-http://www.sfdkj.com/12953.html

在扫描线程当中,如果队列中的任务是空着的,就还好,这个线程就在这里阻塞了 (没问题)就怕队列中的任务不空,并且任务时间还没到,此时就称为"忙等"(等确实是等了,但是又没闲着,既没有实质性的工作产出,同时又没有进行休息)文章源自懂站帝-http://www.sfdkj.com/12953.html

忙等这种操作是非常浪费CPU的,可以基于wait这样的机制解决忙等问题:文章源自懂站帝-http://www.sfdkj.com/12953.html

wait有一个版本,指定等待时间.(不需要notify,时间到了自然唤醒)文章源自懂站帝-http://www.sfdkj.com/12953.html

计算出当前时间和任务的目标之间的时间差,就等待这么长时间即可。文章源自懂站帝-http://www.sfdkj.com/12953.html

locker.wait(task.getTime() - curTime);文章源自懂站帝-http://www.sfdkj.com/12953.html

在等待过程中,可能要插入新的任务。新的任务是可能出现在之前所有任务的最前面的在schedule操作中,就需要加上一个notify操作。文章源自懂站帝-http://www.sfdkj.com/12953.html

四、线程池

进程比较重,若果频繁创建销毁,会导致开销大 。文章源自懂站帝-http://www.sfdkj.com/12953.html

线程虽然比进程轻了,但是如果创建销毁的频率进一步增加,仍然会发现开销还是有的。文章源自懂站帝-http://www.sfdkj.com/12953.html

解决方案:线程池or协程文章源自懂站帝-http://www.sfdkj.com/12953.html

把线程提前创建好,放到池子里,后面需要用线程,直接从池子里取,就不必从系统这边申请了;线程用完了,也不是还给系统,而是放回池子里,以备下次再用 ,这回创建销毁过程,速度就更快了 。线程池最大的好处就是减少每次创建、销毁线程的损耗文章源自懂站帝-http://www.sfdkj.com/12953.html

为什么线程放在池子里,就比从系统这边申请释放来的更快呢?文章源自懂站帝-http://www.sfdkj.com/12953.html

操作系统分为两种状态:用户态和内核态文章源自懂站帝-http://www.sfdkj.com/12953.html

多线程案例(单例模式、阻塞式队列、定时器及线程池)

文章源自懂站帝-http://www.sfdkj.com/12953.html

咱们自己写的代码,就是在最上面的应用程序这一层来运行的,这里的代码都称为“用户态"运行的代码 。文章源自懂站帝-http://www.sfdkj.com/12953.html

有些代码,需要调用操作系统的API,进—步的逻辑就会在内核中执行。文章源自懂站帝-http://www.sfdkj.com/12953.html

例如,调用一个System.out.println,本质上要经过write系统调用,进入到内核中,内核执行一堆逻辑,控制显示器输出字符串。文章源自懂站帝-http://www.sfdkj.com/12953.html

在内核中运行的代码,称为"内核态"运行的代码文章源自懂站帝-http://www.sfdkj.com/12953.html

创建线程,本身就需要内核的支持.(创建线程本质是在内核中搞个PCB,加到链表里)文章源自懂站帝-http://www.sfdkj.com/12953.html

调用的 Thread.start其实归根结底,也是要进入内核态来运行 ;而把创建好的线程放到"池子里",由于池子就是用户态实现的,这个放到池子/从池子取的过程不需要涉及到内核态,就是纯粹的用户态代码就能完成.文章源自懂站帝-http://www.sfdkj.com/12953.html

一般认为,纯用户态的操作,效率要比经过内核态处理的操作,要效率更高文章源自懂站帝-http://www.sfdkj.com/12953.html

认为内核态效率低,倒不是说一定就真的低,而是代码进入了内核态,就不可控了。内核啥时候给你把活干完,把结果给你(有的时候快,有的时候慢).文章源自懂站帝-http://www.sfdkj.com/12953.html

4.1 标准库中的线程池

标准库中的线程池叫做:ThreadPoolExecutor文章源自懂站帝-http://www.sfdkj.com/12953.html

juc(java.util.concurrent): concurrent并发的意思.Java中很多和多线程相关的组件都在这个concurrent包里.文章源自懂站帝-http://www.sfdkj.com/12953.html

ThreadPoolExecutor中的第4个构造方法:文章源自懂站帝-http://www.sfdkj.com/12953.html

多线程案例(单例模式、阻塞式队列、定时器及线程池)

文章源自懂站帝-http://www.sfdkj.com/12953.html

ThreadPoolExecutor(文章源自懂站帝-http://www.sfdkj.com/12953.html

int corePoolSize,文章源自懂站帝-http://www.sfdkj.com/12953.html

int maximumPoolSize,文章源自懂站帝-http://www.sfdkj.com/12953.html

long keepAliveTime,文章源自懂站帝-http://www.sfdkj.com/12953.html

TimeUnit unit,文章源自懂站帝-http://www.sfdkj.com/12953.html

BlockingQueue<Runnable> workQueue,文章源自懂站帝-http://www.sfdkj.com/12953.html

ThreadFactory threadFactory,文章源自懂站帝-http://www.sfdkj.com/12953.html

RejectedExecutionHandler handler)文章源自懂站帝-http://www.sfdkj.com/12953.html

int corePoolSize :核心线程(正式员工的数量)文章源自懂站帝-http://www.sfdkj.com/12953.html

int maximumPoolSize:最大线程(正式员工+临时工)文章源自懂站帝-http://www.sfdkj.com/12953.html

long keepAliveTime:允许临时工摸鱼的时间文章源自懂站帝-http://www.sfdkj.com/12953.html

TimeUnit unit:时间的单位(s, ms, us…)文章源自懂站帝-http://www.sfdkj.com/12953.html

BlockingQueue<Runnable> workQueue:任务队列.线程池会提供一个submit方法让程序猿把任务注册到线程池中,即加到这个任务队列中.文章源自懂站帝-http://www.sfdkj.com/12953.html

ThreadFactory threadFactory:线程工厂.线程是怎么创建出来的文章源自懂站帝-http://www.sfdkj.com/12953.html

RejectedExecutionHandler handler:拒绝策略 ,当任务队列满了,怎么做? 1.直接忽略最新的任务 2.阻塞等待3.直接丢弃最老的任务…文章源自懂站帝-http://www.sfdkj.com/12953.html

虽然线程池的参数这么多,但是使用的时候最重要的参数,还是第一组参数:线程池中线程的个数文章源自懂站帝-http://www.sfdkj.com/12953.html

面试题:有一个程序,这个程序要并发的/多线程的来完成一些任务,如果使用线程池的话,这里的线程数设为多少合适?文章源自懂站帝-http://www.sfdkj.com/12953.html

没有一个具体的数字,这要通过性能测试的方式,找到合适的值。文章源自懂站帝-http://www.sfdkj.com/12953.html

例如,写一个服务器程序,服务器里通过线程池,多线程的处理用户请求,就可以对这个服务器进行性能测试,比如构造一些请求,发送给服务器,这里的请求就需要构造很多,比如每秒发送50/100/20. .…根据实际的业务场景,构造一个合适的值 。文章源自懂站帝-http://www.sfdkj.com/12953.html

根据这里不同的线程池的线程数,来观察程序处理任务的速度,程序持有的CPU的占用率。文章源自懂站帝-http://www.sfdkj.com/12953.html

当线程数多了,整体的速度是会变快,但是CPU占用率也会高.文章源自懂站帝-http://www.sfdkj.com/12953.html

当线程数少了,整体的速度是会变慢,但是CPU占用率也会下降.文章源自懂站帝-http://www.sfdkj.com/12953.html

需要找到一个让程序速度能接受,并且CPU占用也合理这样的平衡点文章源自懂站帝-http://www.sfdkj.com/12953.html

不同类型的程序,因为单个任务,里面CPU上计算的时间和阻塞的时间是分布不相同的.文章源自懂站帝-http://www.sfdkj.com/12953.html

因此这里指定一个具体的数字往往是不靠谱。文章源自懂站帝-http://www.sfdkj.com/12953.html

标准库中还提供了一个简化版本的线程池–Executors,本质是针对ThreadPoolExecutor进行了封装,提供了—些默认参数。文章源自懂站帝-http://www.sfdkj.com/12953.html

import java.util.concurrent.ExecutorService;文章源自懂站帝-http://www.sfdkj.com/12953.html

import java.util.concurrent.Executors;文章源自懂站帝-http://www.sfdkj.com/12953.html

public class Test11 {文章源自懂站帝-http://www.sfdkj.com/12953.html

public static void main(String[] args) {文章源自懂站帝-http://www.sfdkj.com/12953.html

//创建一个固定线程数目的线程池,参数指定了线程的个数文章源自懂站帝-http://www.sfdkj.com/12953.html

ExecutorService pool = Executors.newFixedThreadPool(10);文章源自懂站帝-http://www.sfdkj.com/12953.html

//创建一个自动扩容的线程池,会根据任务量来进行自动扩容文章源自懂站帝-http://www.sfdkj.com/12953.html

// Executors.newCachedThreadPool();文章源自懂站帝-http://www.sfdkj.com/12953.html

//创建一个只有一个线程的线程池文章源自懂站帝-http://www.sfdkj.com/12953.html

// Executors.newSingleThreadExecutor();文章源自懂站帝-http://www.sfdkj.com/12953.html

//创建一个带有定时器功能的线程池,类似于Timer文章源自懂站帝-http://www.sfdkj.com/12953.html

// Executors.newScheduledThreadPool();文章源自懂站帝-http://www.sfdkj.com/12953.html

for (int i = 0; i < 100; i++) {文章源自懂站帝-http://www.sfdkj.com/12953.html

pool.submit(new Runnable() {文章源自懂站帝-http://www.sfdkj.com/12953.html

@Override文章源自懂站帝-http://www.sfdkj.com/12953.html

public void run() {文章源自懂站帝-http://www.sfdkj.com/12953.html

System.out.println("hello");文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

});文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

4.2 实现线程池

线程池里面有什么?文章源自懂站帝-http://www.sfdkj.com/12953.html

  1. 先能够描述任务(直接使用Runnable)
  2. 需要组织任务(直接使用BlockingQueue)
  3. 能够描述工作线程.
  4. 还需要组织这些线程.
  5. 需要实现,往线程池里添加任务

import java.util.ArrayList;文章源自懂站帝-http://www.sfdkj.com/12953.html

import java.util.List;文章源自懂站帝-http://www.sfdkj.com/12953.html

import java.util.concurrent.BlockingDeque;文章源自懂站帝-http://www.sfdkj.com/12953.html

import java.util.concurrent.LinkedBlockingDeque;文章源自懂站帝-http://www.sfdkj.com/12953.html

class MyThreadPool{文章源自懂站帝-http://www.sfdkj.com/12953.html

//1.描述一个任务,直接使用Runnable文章源自懂站帝-http://www.sfdkj.com/12953.html

//2.使用一个数据结构来组织任务文章源自懂站帝-http://www.sfdkj.com/12953.html

private BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();文章源自懂站帝-http://www.sfdkj.com/12953.html

//3.描述一个线程,工作线程的功能就是从任务队列中取任务并执行文章源自懂站帝-http://www.sfdkj.com/12953.html

static class Worker extends Thread{文章源自懂站帝-http://www.sfdkj.com/12953.html

//当前线程池中有若干个Worker线程,这些线程内部都持有上述的任务队列文章源自懂站帝-http://www.sfdkj.com/12953.html

private BlockingDeque<Runnable> queue = null;文章源自懂站帝-http://www.sfdkj.com/12953.html

public Worker( BlockingDeque<Runnable> queue) {文章源自懂站帝-http://www.sfdkj.com/12953.html

this.queue = queue;文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

@Override文章源自懂站帝-http://www.sfdkj.com/12953.html

public void run() {文章源自懂站帝-http://www.sfdkj.com/12953.html

while (true){文章源自懂站帝-http://www.sfdkj.com/12953.html

try {文章源自懂站帝-http://www.sfdkj.com/12953.html

//循环的去获取任务队列的任务,文章源自懂站帝-http://www.sfdkj.com/12953.html

//如果队列为空就直接阻塞,如果队列非空,就获取到里面的内容文章源自懂站帝-http://www.sfdkj.com/12953.html

Runnable runnable = queue.take();文章源自懂站帝-http://www.sfdkj.com/12953.html

//获取到之后,就执行任务文章源自懂站帝-http://www.sfdkj.com/12953.html

菠萝的博客();文章源自懂站帝-http://www.sfdkj.com/12953.html

} catch (InterruptedException e) {文章源自懂站帝-http://www.sfdkj.com/12953.html

e.printStackTrace();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

//4.创建一个数据结构来组织若干个线程文章源自懂站帝-http://www.sfdkj.com/12953.html

private List<Thread> workers = new ArrayList<>();文章源自懂站帝-http://www.sfdkj.com/12953.html

public MyThreadPool(int n){文章源自懂站帝-http://www.sfdkj.com/12953.html

//构造方法中创建出若干个线程,放到上述的数组中文章源自懂站帝-http://www.sfdkj.com/12953.html

for (int i = 0; i < n; i++) {文章源自懂站帝-http://www.sfdkj.com/12953.html

Worker worker = new Worker(queue);文章源自懂站帝-http://www.sfdkj.com/12953.html

worker.start();文章源自懂站帝-http://www.sfdkj.com/12953.html

workers.add(worker);文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

//5.创建一个方法,允许程序员放任务到线程池当中文章源自懂站帝-http://www.sfdkj.com/12953.html

public void submit(Runnable runnable){文章源自懂站帝-http://www.sfdkj.com/12953.html

try {文章源自懂站帝-http://www.sfdkj.com/12953.html

queue.put(runnable);文章源自懂站帝-http://www.sfdkj.com/12953.html

} catch (InterruptedException e) {文章源自懂站帝-http://www.sfdkj.com/12953.html

e.printStackTrace();文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

public class Test12 {文章源自懂站帝-http://www.sfdkj.com/12953.html

public static void main(String[] args) {文章源自懂站帝-http://www.sfdkj.com/12953.html

MyThreadPool myThreadPool = new MyThreadPool(10);文章源自懂站帝-http://www.sfdkj.com/12953.html

for (int i = 0; i < 100; i++) {文章源自懂站帝-http://www.sfdkj.com/12953.html

myThreadPool.submit(new Runnable() {文章源自懂站帝-http://www.sfdkj.com/12953.html

@Override文章源自懂站帝-http://www.sfdkj.com/12953.html

public void run() {文章源自懂站帝-http://www.sfdkj.com/12953.html

System.out.println("hello myThreadPool");文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

});文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

}文章源自懂站帝-http://www.sfdkj.com/12953.html

懂站帝
  • 本文由 发表于 2022年5月14日 12:31:08
  • 版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至395045033@qq.com举报,一经查实,本站将立刻删除。