timer是一个单独的线程吗,timer是什么意思翻译成中文

  

  1.单一模式单一模式是常见的设计模式之一。   

  

  什么是设计模式?   

  

  设计模式相当于“棋谱”中的一些固定代码套路。如果按照棋谱走,一般不会下的很差。软件开发中也有很多常见的“问题场景”。针对这些问题场景,大佬们总结了一些固定套路。如果你按照这个程序来实现代码,你不会吃亏。   

  

  能保证某个类在程序中只存在唯一一份实例,而无需创建多个实例。   

  

  这在许多情况下都是必需的。例如,在JDBC只需要一个数据源实例。   

  

  单体模式可以通过“饿汉”和“懒汉”两种方式实现。   

  

  下面举个例子:洗碗。   

  

  中午用了四碗。吃完后,这四碗马上洗了。吃完了,这顿午饭用了四碗。对于这份晚餐,你只需要两碗,然后只洗两碗就可以偷懒了——这是一种更高效的饿单模式操作方式,你急着做出一个榜样。   

  

  懒惰的单例模式不需要太多的时间来创建例子,只有当他使用它们的时候。   

  

  1.1饿汉模式类加载的同时,创建实例。   

  

  在Java程序中,类对象只有一个步骤(由JVM保证),这保证了类只有一个静态成员。   

  

  //Singleton已经被用来实现Singleton模式,并且这个类有一个唯一的实例。   

  

  //饥饿中文模式   

  

  类Singleton{   

  

  //静态修饰成员-“类成员”-“类属性/方法”   

  

  //1.使用static创建一个实例,并立即实例化它。   

  

  //与该实例对应的实例是该类的唯一实例。   

  

  私有静态Singleton实例=new Singleton();   

  

  //2.为了防止new this Singleton在其他地方被设置为私有。   

  

  私有Singleton(){ };   

  

  //构造一个方法,让外部可以得到唯一的实例。   

  

  公共静态Singleton getInstance(){   

  

  返回实例;   

  

  }   

  

  }   

  

  公共类Test06 {   

  

  公共静态void main(字符串参数){   

  

  singleton instance=singleton . getinstance();   

  

  }   

  

  }   

  

  中文模式下的Getlnstance只读取变量的内容。如果多个线程只是读取同一个变量而不修改它,那么它仍然是线程安全's.   

  

  1.2加载惰性模式类时,不会创建任何实例。第一次使用的时候才创建实例.   

  

  惰性模式-单线程类Singleton1{   

  

  in=null中的私有静态Singleton1   

  

  私有singleton 1(){ };   

  

  公共静态Singleton1 getInstance(){   

  

  //不是原子的,既包含读取,也包含修改。   

  

  if(in==null){   

  

  in=new singleton 1();   

  

  }   

  

  返回;   

  

  }   

  

  }   

  

  懒惰模式包括阅读和修改。此外,这里的读取和修改分为两步(不是原子的)并且存在线程安全问题。   

  

  懒惰模式——多线程版本的线程安全问题发生在第一次创建实例的时候。如果在多个线程中同时调用getInstance方法,它可以是能导致创建出多个实例.   

  

  锁定操作可以改变这里的线程安全问题。这里使用类对象作为锁对象(一个程序中只有一个类对象,这样多线程调用getInstance时可以锁定同一个对象)。   

  

  类Singleton1{   

  

  私有静态Singleton1实例=null   

  

  私有singleton 1(){ };   

  

  公共静态Singleton1 getInstance(){   

  

  同步(Singleton1.class){   

  

  if(instance==null){   

  

  instance=new singleton 1();   

  

  }   

  

  }   

  

  返回实例;   

  

  }   

  

}

  

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

  

而按照上述的加锁方式,无论代码是初始化之后,还是初始化之前,每次调用 getinstance方法都会进行加锁.也就意味着即使是初始化之后(已经线程安全了),仍然存在大量的锁竞争

  

以下代码在加锁的基础上, 做出了进一步改动:

  

1.使用双重 if 判定, 降低锁竞争的频率。

  

改进方案: 让getInstance初始化之前,才进行加锁,初始化之后,就不再加锁了。在加锁这里再加上一层条件判定即可.条件就是当前是否已经初始化完成 (instance == null)。

  

在使用了双重if判定之后,当前这个代码中还存在一个重要的问题:如果多个线程,都去调用这里的getlnstance 方法,就会造成大量的读instance内存的操作,这样可能会让编译器把这个读内存操作优化成读寄存器操作

  

—旦这里触发了优化,后续如果第一个线程已经完成了针对instance的修改,那么紧接着后面的线程都感知不到这个修改,仍然把 instance当成null 。所以这里需要给 instance 加上了 volatile。

  

2.给 instance 加上了 volatile

  

class Singleton2{

  

//不是立即初始化实例

  

//volatile 保证内存可见性

  

private static volatile Singleton2 instance = null;

  

private Singleton2(){};

  

//只有在真正使用这个实例的时候,才会真正的去创建这个实例

  

public static Singleton2 getInstance(){

  

//使用这里的类对象作为锁对象,类对象在一个程序中只有一份,

  

//判定的是是否要加锁。降低了锁竞争

  

if(instance == null){

  

//加锁操作,保证了线程安全

  

synchronized (Singleton2.class){

  

//判定的是是否要创建实例

  

if(instance == null){

  

instance = new Singleton2();

  

}

  

}

  

}

  

return instance;

  

}

  

}

  

public class Test07 {

  

public static void main(String<> args) {

  

Singleton2 instance = Singleton2.getInstance();

  

}

  

}

  

二、阻塞式队列阻塞队列是什么?

  

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

  

阻塞队列是一种线程安全的数据结构, 并且具有以下特性 : 产生阻塞效果

  

当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.阻塞队列的一个典型应用场景就是 “生产者消费者模型”. 这是一种非常典型的开发模型。

  

2.1 生产者消费者模型生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题

  

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

  

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

  

生产者消费者模型,是实际开发中非常有用的一种多线程开发手段。尤其是在服务器开发的场景中:

  

假设有两个服务器AB,A作为入口服务器直接接收用户的网络请求,B作为应用服务器,来给A提供一些数据。

  

  


  

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

  

使用生产者消费者模型,就可以降低这里的耦合.

  

  

对于请求:A是生产者,B是消费者.对于响应:A是消费者,B是生产者.阻塞队列都是作为交易场所 ,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

  


  

A只需要关注如何和队列交互,不需要认识B;

  

B也只需要关注如何和队列交互,也不需要认识A;

  

队列是不变的,如果B挂了,对于A没啥影响;如果把B换成C,A也完全感知不到。

  

2.能够对于请求进行“削峰填谷

  

未使用生产者消费者模型的时候,如果请求量突然暴涨(不可控)

  

  

A暴涨导致B暴涨;

  

A作为入口服务器,计算量很轻,请求暴涨,问题不大.B作为应用服务器,计算量可能很大,需要的系统资源也更多.如果请求更多了,需要的资源进—步增加,如果主机的硬件不够,可能程序就挂了。

  

  

A请求暴涨=>阻塞队列的请求暴涨,由于阻塞队列没啥计算量,就只是单纯的存个数据,就能抗住更大的压力.

  

B这边仍然按照原来的速度来消费数据,不会因为A的暴涨而引起暴涨.B就被保护的很好,就不会因为这种请求的波动而引起崩溃 。

  

"削峰”这种峰值很多时候不是持续的,就一阵过去了(比如双十一秒杀活动)就又恢复了 。

  

"填谷"B仍然是按照原有的频率来处理之前积压的数据。

  

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

  

2.2 标准库中的阻塞队列在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.

  

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

  

import java.util.concurrent.LinkedBlockingQueue;

  

public class Demo21 {

  

public static void main(String<> args) throws InterruptedException {

  

BlockingDeque<String> queue = new LinkedBlockingDeque<>();

  

//入队列

  

queue.put("hello");

  

//出队列

  

String s = queue.take();

  

System.out.println(s);//hello

  

}

  

}

  

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

  

对于put来说,阻塞条件,就是队列为满。put 中的wait要由take来唤醒.只要take成功了一个元素,不就队列不满了,就可以进行唤醒了.

  

对于take来说,阻塞条件,就是队列为空。对于take 中的等待,条件是队列为空.队列不为空,也就是put成功之后,就来唤醒.

  

import java.util.concurrent.BlockingDeque;

  

import java.util.concurrent.LinkedBlockingDeque;

  

class MyBlocking{

  

//基于数组实现阻塞队列

  

private int<> data = new int<1000>;

  

//队列长度

  

private int size = 0;

  

//队首下标

  

private int head = 0;

  

//队尾下标

  

private int tail = 0;

  

private static Object locker = new Object();

  

public void put(int value) throws InterruptedException {

  

synchronized (locker){

  

if(size == data.length){

  

//开始时站实现返回return

  

// return;

  

//针对哪个对象加锁,就返回哪个对象的wait 如果是针对this加锁,就this.wait

  

locker.wait();

  

}

  

//把新的元素方法tail位置上

  

data = value;

  

tail++;

  

//处理tail到达元素末尾的情况,需要从头开始,重新循环

  

//第1种写法

  

if(tail >= data.length){

  

tail = 0;

  

}

  

//第2种写法

  

// tail = tail % data.length;

  

size++;

  

//如果入队列成功,则队列非空,就唤醒take中的阻塞等待

  

locker.notify();

  

}

  

}

  

//出队列

  

//使用包装类

  

public Integer take() throws InterruptedException {

  

synchronized (locker){

  

if(size == 0){

  

// return null;

  

locker.wait();

  

}

  

int ret = data;

  

head++;

  

if(head >= data.length){

  

head = 0;

  

}

  

size--;

  

// take成功之后,就唤醒put中的等待.

  

locker.notify();

  

return ret;

  

}

  

}

  

}

  

public class Test08 {

  

public static void main(String<> args) {

  

MyBlocking queue = new MyBlocking();

  

//实现一个生产者消费者模式

  

Thread t = new Thread(()->{

  

int num = 0;

  

while (true){

  

System.out.println("生产了:" + num);

  

try {

  

queue.put(num);

  

// 当生产者生产的慢一些的时候, 消费者就得跟着生产者的步伐走.生产一个消费一个

  

// Thread.sleep(500);

  

} catch (InterruptedException e) {

  

e.printStackTrace();

  

}

  

num++;

  

}

  

});

  


  

t.start();

  

Thread t2 = new Thread(()->{

  

int num = 0;

  

while (true){

  

System.out.println("消费了:" + num);

  

try {

  

num = queue.take();

  

Thread.sleep(500);

  

} catch (InterruptedException e) {

  

e.printStackTrace();

  

}

  

}

  

});

  

t2.start();

  

}

  

}

  

上述代码输出结果:

  

  


  

当生产者生产的慢一些的时候, 消费者就得跟着生产者的步伐走,生产一个消费一个:

  

  

三、定时器定时器也是软件开发中的一个重要组件。类似于一个 “闹钟”: 达到一个设定的时间之后, 就执行某个指定好的代码。

  

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

  

import java.util.TimerTask;

  

public class Test09 {

  

public static void main(String<> args) {

  

Timer timer = new Timer();

  

timer.schedule(new TimerTask() {

  

@Override

  

public void run() {

  

System.out.println("hello");

  

}

  

},3000);

  

System.out.println("main");

  

}

  

}

  

先执行main,三秒之后执行hello。

  

输出结果:

  

  


  

3.2 实现定时器Timer内部需要什么?

  

1.描述任务

  

创建一个专门的类来表示一个定时器中的任务.(TimerTask)

  

2.组织任务(使用一定的数据结构把一些任务给放到一起)

  

通过—定的数据结构(一个带优先级的阻塞队列)来组织.

  

为啥要带优先级呢?

  

因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.

  

3.执行时间到了的任务

  

需要先执行时间最靠前的任务,就需要有一个线程,不停的去检查当前优先队列的队首元素,看看当前最靠前的这个任务时间是否到了。

  

import java.util.concurrent.PriorityBlockingQueue;

  

//创建一个类,表示一个任务

  

class MyTask implements Comparable<MyTask>{ //实现Comparable接口,设定比较规则

  

//任务具体要干什么

  

private Runnable runnable;

  

//任务具体啥时候干,保存任务要执行的毫秒级时间戳

  

private long time;

  

//提供一个构造方法

  

public MyTask(Runnable runnable, long delay) { //delay是一个时间间隔,不是绝对的时间戳的值

  

this.runnable = runnable;

  

this.time = System.currentTimeMillis() + delay;

  

}

  

public void run(){

  

菠萝的博客();

  

}

  

public long getTime() {

  

return time;

  

}

  

@Override

  

public int compareTo(MyTask o) {

  

//让时间小的在前,时间大的在后

  

return (int)(this.time - o.time);

  

}

  

}

  

//定时器

  

class MyTimer{

  

//定时器内部能够存放多个任务

  

//此处的队列要考虑到线程安全问题 可能在多个线程里进行注册任务.

  

// 同时还有一个专门的线程来取任务执行.此处的队列就需要注意线程安全问题.

  

private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

  

//使用schedule方法来注册任务到队列中

  

public void schedule(Runnable runnable,long delay){

  

MyTask task = new MyTask(runnable,delay);

  

queue.put(task);

  

//每次任务插入成功之后,都唤醒一下扫描线程,让线程重新检查一下队首的任务,看是否时间到了要执行

  

synchronized (locker){

  

locker.notify();

  

}

  

}

  

private Object locker = new Object();

  

//创建一个扫描线程

  

public MyTimer(){

  

Thread t = new Thread(()->{

  

while (true){

  

try {

  

//先取出队首元素

  

MyTask task = queue.take();

  

long curTime = System.currentTimeMillis();

  

//比较一下看当前时间到了吗

  

if(curTime < task.getTime()){

  

//时间没到,把任务塞回到队列中

  

queue.put(task);

  

//指定一个等待时间

  

synchronized (locker){

  

//wait可以被中途唤醒 sleep不能被中途唤醒

  

locker.wait(task.getTime() - curTime);

  

}

  

}else {

  

//时间到了,执行任务

  

task.run();

  

}

  

} catch (InterruptedException e) {

  

e.printStackTrace();

  

}

  

}

  

});

  

t.start();

  

}

  

}

  

public class Test10 {

  

public static void main(String<> args) {

  

MyTimer myTimer = new MyTimer();

  

myTimer.schedule(new Runnable() {

  

@Override

  

public void run() {

  

System.out.println("hello");

  

}

  

},3000);

  

System.out.println("main");

  

}

  

}

  

1.描述—个任务: runnable + time

  

2.使用优先队列来组织若干个任务. PriorityBlockingQueue

  

3.实现schedule方法来注册任务到队列中.

  

4.创建一个扫描线程,这个扫描线程不停的获取到队首元素,并且判定时间是否到达.

  

另外要注意,让MyTask 类能够支持比较:实现Comparable接口,并设定比较规则

  

注意解决这里的忙等问题:

  

在扫描线程当中,如果队列中的任务是空着的,就还好,这个线程就在这里阻塞了 (没问题)就怕队列中的任务不空,并且任务时间还没到,此时就称为"忙等"(等确实是等了,但是又没闲着,既没有实质性的工作产出,同时又没有进行休息)

  

忙等这种操作是非常浪费CPU的,可以基于wait这样的机制解决忙等问题:

  

wait有一个版本,指定等待时间.(不需要notify,时间到了自然唤醒)

  

计算出当前时间和任务的目标之间的时间差,就等待这么长时间即可。

  

locker.wait(task.getTime() - curTime);

  

在等待过程中,可能要插入新的任务。新的任务是可能出现在之前所有任务的最前面的在schedule操作中,就需要加上一个notify操作。

  

四、线程池进程比较重,若果频繁创建销毁,会导致开销大 。

  

线程虽然比进程轻了,但是如果创建销毁的频率进一步增加,仍然会发现开销还是有的。

  

解决方案:线程池or协程

  

把线程提前创建好,放到池子里,后面需要用线程,直接从池子里取,就不必从系统这边申请了;线程用完了,也不是还给系统,而是放回池子里,以备下次再用 ,这回创建销毁过程,速度就更快了 。线程池最大的好处就是减少每次创建、销毁线程的损耗

  

为什么线程放在池子里,就比从系统这边申请释放来的更快呢?

  

操作系统分为两种状态:用户态和内核态

  

  

咱们自己写的代码,就是在最上面的应用程序这一层来运行的,这里的代码都称为“用户态"运行的代码 。

  

有些代码,需要调用操作系统的API,进—步的逻辑就会在内核中执行。

  

例如,调用一个System.out.println,本质上要经过write系统调用,进入到内核中,内核执行一堆逻辑,控制显示器输出字符串。

  

在内核中运行的代码,称为"内核态"运行的代码

  

创建线程,本身就需要内核的支持.(创建线程本质是在内核中搞个PCB,加到链表里)

  

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

  

一般认为,纯用户态的操作,效率要比经过内核态处理的操作,要效率更高

  

认为内核态效率低,倒不是说一定就真的低,而是代码进入了内核态,就不可控了。内核啥时候给你把活干完,把结果给你(有的时候快,有的时候慢).

  

4.1 标准库中的线程池标准库中的线程池叫做:ThreadPoolExecutor

  

juc(java.util.concurrent): concurrent并发的意思.Java中很多和多线程相关的组件都在这个concurrent包里.

  

ThreadPoolExecutor中的第4个构造方法:

  

  

ThreadPoolExecutor(

  

int corePoolSize,

  

int maximumPoolSize,

  

long keepAliveTime,

  

TimeUnit unit,

  

BlockingQueue<Runnable> workQueue,

  

ThreadFactory threadFactory,

  

RejectedExecutionHandler handler)

  

int corePoolSize :核心线程(正式员工的数量)

  

int maximumPoolSize:最大线程(正式员工+临时工)

  

long keepAliveTime:允许临时工摸鱼的时间

  

TimeUnit unit:时间的单位(s, ms, us…)

  

BlockingQueue<Runnable> workQueue:任务队列.线程池会提供一个submit方法让程序猿把任务注册到线程池中,即加到这个任务队列中.

  

ThreadFactory threadFactory:线程工厂.线程是怎么创建出来的

  

RejectedExecutionHandler handler:拒绝策略 ,当任务队列满了,怎么做? 1.直接忽略最新的任务 2.阻塞等待3.直接丢弃最老的任务…

  

虽然线程池的参数这么多,但是使用的时候最重要的参数,还是第一组参数:线程池中线程的个数

  

面试题:有一个程序,这个程序要并发的/多线程的来完成一些任务,如果使用线程池的话,这里的线程数设为多少合适?

  

没有一个具体的数字,这要通过性能测试的方式,找到合适的值。

  

例如,写一个服务器程序,服务器里通过线程池,多线程的处理用户请求,就可以对这个服务器进行性能测试,比如构造一些请求,发送给服务器,这里的请求就需要构造很多,比如每秒发送50/100/20. .…根据实际的业务场景,构造一个合适的值

  

根据这里不同的线程池的线程数,来观察程序处理任务的速度,程序持有的CPU的占用率。

  

当线程数多了,整体的速度是会变快,但是CPU占用率也会高.

  

当线程数少了,整体的速度是会变慢,但是CPU占用率也会下降.

  

需要找到一个让程序速度能接受,并且CPU占用也合理这样的平衡点

  

不同类型的程序,因为单个任务,里面CPU上计算的时间和阻塞的时间是分布不相同的.

  

因此这里指定一个具体的数字往往是不靠谱。

  

标准库中还提供了一个简化版本的线程池–Executors,本质是针对ThreadPoolExecutor进行了封装,提供了—些默认参数。

  

import java.util.concurrent.ExecutorService;

  

import java.util.concurrent.Executors;

  

public class Test11 {

  

public static void main(String<> args) {

  

//创建一个固定线程数目的线程池,参数指定了线程的个数

  

ExecutorService pool = Executors.newFixedThreadPool(10);

  

//创建一个自动扩容的线程池,会根据任务量来进行自动扩容

  

// Executors.newCachedThreadPool();

  

//创建一个只有一个线程的线程池

  

// Executors.newSingleThreadExecutor();

  

//创建一个带有定时器功能的线程池,类似于Timer

  

// Executors.newScheduledThreadPool();

  

for (int i = 0; i < 100; i++) {

  

pool.submit(new Runnable() {

  

@Override

  

public void run() {

  

System.out.println("hello");

  

}

  

});

  

}

  

}

  

}

  

4.2 实现线程池线程池里面有什么?

  

先能够描述任务(直接使用Runnable)需要组织任务(直接使用BlockingQueue)能够描述工作线程.还需要组织这些线程.需要实现,往线程池里添加任务import java.util.ArrayList;

  

import java.util.List;

  

import java.util.concurrent.BlockingDeque;

  

import java.util.concurrent.LinkedBlockingDeque;

  

class MyThreadPool{

  

//1.描述一个任务,直接使用Runnable

  

//2.使用一个数据结构来组织任务

  

private BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();

  

//3.描述一个线程,工作线程的功能就是从任务队列中取任务并执行

  

static class Worker extends Thread{

  

//当前线程池中有若干个Worker线程,这些线程内部都持有上述的任务队列

  

private BlockingDeque<Runnable> queue = null;

  

public Worker( BlockingDeque<Runnable> queue) {

  

this.queue = queue;

  

}

  

@Override

  

public void run() {

  

while (true){

  

try {

  

//循环的去获取任务队列的任务,

  

//如果队列为空就直接阻塞,如果队列非空,就获取到里面的内容

  

Runnable runnable = queue.take();

  

//获取到之后,就执行任务

  

菠萝的博客();

  

} catch (InterruptedException e) {

  

e.printStackTrace();

  

}

  

}

  

}

  

}

  

//4.创建一个数据结构来组织若干个线程

  

private List<Thread> workers = new ArrayList<>();

  

public MyThreadPool(int n){

  

//构造方法中创建出若干个线程,放到上述的数组中

  

for (int i = 0; i < n; i++) {

  

Worker worker = new Worker(queue);

  

worker.start();

  

workers.add(worker);

  

}

  

}

  

//5.创建一个方法,允许程序员放任务到线程池当中

  

public void submit(Runnable runnable){

  

try {

  

queue.put(runnable);

  

} catch (InterruptedException e) {

  

e.printStackTrace();

  

}

  

}

  

}

  

public class Test12 {

  

public static void main(String<> args) {

  

MyThreadPool myThreadPool = new MyThreadPool(10);

  

for (int i = 0; i < 100; i++) {

  

myThreadPool.submit(new Runnable() {

  

@Override

  

public void run() {

  

System.out.println("hello myThreadPool");

  

}

  

});

  

}

  

}

  

}

  

<机智>点击获取资料

相关文章