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

一,单例模式

单例模式就是其中一种常用设计模式。

设计模式有哪些?

设计模式,相当于“棋谱”里的某些固定代码套路,按棋谱进行下盘,通常下盘效果并不理想,软件开发过程中还有许多常见的“问题情景”.为了解决这类问题情景,大佬总结了几个固定套路,按此套路进行下盘,并没有损失。

单例模式可以确保一个类只有程序的唯一的一个例子,并且不产生多个例子。

这种情况是许多情景所要求的。例如:数据库应用、Web服务等,这些需求往往是由多个不同类型的对象来实现的。如何有效地管理这些对象?通过使用XML语言可以很好地解决这个问题。例如,在JDBC上,DataSource的例子仅需1。

单例模式的具体实施方法,分为“饿汉”与“懒汉”。

下面就举例说明:洗碗

午间此饭,用四碗,食毕,马上将四碗洗掉〔饿汉〕

午饭时间,用的是四碗饭。这4个碗子分别用来装米饭、面条和汤菜。每次吃饭前都要先把它们洗一洗。如果你有时间的话,可以用筷子来代替碗。吃饱喝足后,先不洗澡,到了晚上这顿饭,只用了两碗饭,再只用了两碗水[懒汉]->就是更有效率的运作

饿汉单例模式,创建实例更急。

懒汉单例模式并不是很急着创建一个例子,直到使用时才会真的建立。

1.1饿汉模式

在类加载时创建一个实例。

一个Java程序,类对象仅有1个(由JVM确保)进—步还确保类static成员只有1个。

//使用Singleton执行单例模式以确保Singleton类具有唯一的示例

//饿汉模式

class Singleton{

//static修饰成员对「类成员」──」类属性/方式

//1、用static建立实例并马上实例化

//这instance的相应例子是这一类中唯一的例子

private static Singleton instance = new Singleton();

//2、为避免异地new该Singleton一个Singleton被设置成私有

private Singleton(){};

//构建允许外部获得唯一示例的方法

public static Singleton getInstance(){

return example;

}

}

public class Test06 {

public static void main(String[] args) {

Singleton instance = Singleton.getInstance();

}

}

饿汉模式(getlnstance)只需要阅读变量内容即可。每个线程都可以读到它对应的数据信息;而每个线程只可在其中一个线程读到该变量时对其进行相应操作。如果一个以上线程只读取相同变量而没有进行修改,这时还是线程安全。

1.2懒汉模式

类加载时,实例不会被建立。在首次使用时,只建立了一个例子。

懒汉模式–单线程版本

public class Singleton1{

private statics Singleton1 in = null;

private Singleton1(){};

public static Singleton1 getInstance(){

//非原子,包括阅读和修改

if(in == null){

in = new Singleton1();

}

return in;

}

}

懒汉模式,包括阅读和修正。在此,我们把它们分别称为“读写”和“修改”.这两种情况都是程序可以处理的。而在此阅读与修改,仍然分两步进行(而非原子)有线程安全问题。

懒汉模式-多线程版是解决这个问题最有效的方法之一。

线程安全问题出现于第一次创建一个实例的时候。因为创建一个新的应用需要使用大量的线程,而每个线程又是由不同类型的进程来执行的。当我们将某个进程从系统上卸载后。如果该进程没有被启动。如果getInstance方法同时被多个线程调用,则可能会产生多个示例。

为了解决这个问题,本文提出了一个新的方法来处理这种情况下的加锁操作,即线程安全问题;通过使用该方法,我们可以使线程之间的通讯更加稳定、高效,从而提高应用程序的运行效率。本文详细介绍了如何利用这种方法来实现对不同类型的线程进行有效的管理。将此处的类对象用作锁对象(类对象只存在于程序的唯一份上,可以确保多个线程在调用getInstance时对同一对象加锁。)。

public class Singleton1{

private static Singleton1 instance= null;

private Singleton1(){};

public static Singleton1 getInstance(){

synchronized (Singleton1.class){

if(instance== null){

instance= new Singleton1();

}

}

return example;

}

}

懒汉模式–多线程版本(改良)

目前,尽管在加锁后线程安全问题已经解决,但也出现了一些新问题,那就是对刚刚这种懒汉模式下的编码。线程不安全出现在instance初始化前。在没有初始化时,多线程对getinstance的调用可能既涉及阅读,也涉及修改。因此,我们可以通过设置一个条件来防止读写操作。如果读写成功的话,那么就意味着线程安全了;如果读写失败的话,那就是线程存在安全隐患。但一旦instance初始化完成后(肯定不是nul,if条件肯定不会成立),getInstance的运行只有2个读运行也是线程安全的。

并且根据以上加锁方法,不管代码在初始化后还是初始化前,每调用一次getinstance方法就加锁一次。这样做使得程序在运行过程中需要消耗大量资源来完成对cache和内存访问等操作。当程序进入死循环状态时,如果要重新执行这些操作。代价就是非常高的。还意味着甚至在初始化后(已线程安全)还有很多锁竞争。

下列编码以加锁为基础,并做进一步修改:

1、采用双重if判断,减少锁竞争次数。

改进方案:使getInstance在初始化前,只需加锁,初始化后,不需要再加锁。如果要增加新的用户,则需要将原来的用户名和密码都修改成这个用户名对应的密码后才能添加;同时,也可以将原有的密码修改为一个新键。加锁的过程中需要判断每一层的状态和条件判定。条件是现在是否初始化完毕(instance==null)。

采用双重if判定后,目前该代码仍然存在着重要问题:如果有多个线程,同时调用此处的getlnstance方法将导致大量读instance内存,这可能使编译器将此读内存操作最优化为读寄存器。

—旦优化在此被引发,随后若首个线程已完成对instance所做的更改,则随即后一个线程均无法感知该更改,依旧将instance视为null。当前面一个线程结束之后,接下来的线程就会继续对它进行更新。这样做虽然可以减少内存开销,但却无法避免错误发生的概率和代价。而且这种做法也是不经济的。因此,在此有必要在instance中添加一个volatile。

2.给 instance 加上了 volatile

public class Singleton2{

//并非马上初始化示例

//volatile确保内存的可见性

Singleton2 private static volatile instance = nil;

private Singleton2(){};

//当真正用到此示例时才能真正地创建它

public static Singleton2 getInstance(){

//用此处的类对象为锁对象。类对象只存在于一个程序之中

//判断是否加锁减少了锁的竞争

if(instance == null){

//加锁操作以确保线程的安全性

synchronized (Singleton2.class){

//判断是否需要创建一个示例

if(instance == null){

instance = new Singleton2();

}

}

}

return example;

}

}

public class Test07 {

public static void main(String[] args) {

Singleton2 instance = Singleton2.getInstance();

}

}

二,阻塞式队列

何谓阻塞队列?

阻塞队列就是这样一个专用队列。还遵循“先进先出”方针;

阻塞队列是一种用于保证线程安全的数据结构,它能在一定程度上提高系统的阻塞效果。

队列满后,继续进入队列将被堵塞,直至出现其它线程将队列中的元素取出。

队列空置时,持续出队还将被堵塞,直至出现其它线程将元素插入队列。

阻塞队列最典型的应用场景是“生产者-消费者模型”.可以说,它是很有代表性的开发模型之一。

2.1、基于角色的生产者消费者模型在这种情况下。

生产者消费者模式是用容器解决生产者与消费者之间强耦合。

生产者与消费者之间并没有直接的通信,而是通过阻塞队列实现的,因此生产者在制作数据后无需等消费者的加工就可以直接丢到阻塞队列中去,消费者也不会向生产者索取数据,只需要直接在阻塞队列中取用即可。

本文主要完成了以下工作:1.建立了基于阻塞队列的生产者与消费者之间的模型。

生产者-消费者模型,在实际的开发过程中,是一个很有帮助的多线程开发方法。本文主要讨论了如何将这一方法应用于服务器的开发过程当中,以提高系统效率和降低开发成本。作者根据多年来从事软件开发的经验,总结出一套行之有效的使用策略。本文主要研究如何在服务器端实现这种方法并将其应用于服务器开发之中。

假定存在两台服务器AB,其中A为入口服务器,直接接受用户网络请求,而B为应用服务器,为A提供部分数据。

图片[1]-多线程案例(单例模式、阻塞式队列、定时器及线程池)-【聚禄鼎】一站式企业服务平台

在没有生产者消费者模型的情况下。那么就不能很好地解释B与A之间的关系。例如。当我们要将B改装成C时。必须先用一个程序将其转换成A.然后再进行修改。这样做非常麻烦。这时甲与乙之间耦合性较强:当开发甲码时必须完全理解乙所提供的某些界面,而开发乙码时又必须完全理解甲如何调用它;—旦要用乙代丙时,甲码就要进行更大的修改,而乙若挂机,还会直接造成甲还顺便挂机。

采用生产者-消费者模型,可减少此处耦合。

图片[2]-多线程案例(单例模式、阻塞式队列、定时器及线程池)-【聚禄鼎】一站式企业服务平台

关于要求:甲为生产者,乙为消费者。对反应:甲为消费者,乙为生产者。在现实中,有许多需求与供给同时发生。当需求大于供给时,市场上便会出现供需不匹配现象。为了解决这一问题,需要一种机制来调节这种不匹配程度。这就是阻塞队列。阻塞队列均被用作交易场所。阻塞队列被等效为兼顾生产者与消费者处理能力的缓冲区。

甲只要注意与队列的交互方式,而不必意识到乙;

乙还只要注意怎样与队列互动,而不必意识到甲的存在;

队列没有变化,若B被挂起,则对A没有多大影响,若将B替换为C,则A根本就无法被察觉。

2、能为要求“削峰填谷”。

没有采用生产者-消费者模型时,若请求量骤然飙升(失控)

图片[3]-多线程案例(单例模式、阻塞式队列、定时器及线程池)-【聚禄鼎】一站式企业服务平台

甲的大涨引起乙的大涨;

A是入口服务器,计算量小,要求飙升,问题不大;B做为应用服务器,其计算量会非常大,所需系统资源较多。C作为存储服务器,会占用大量存储空间。对于一个大型的系统来说,要想把它放在一台高性能的服务器上是比较困难的。D为了减少I/O时间,可以通过提高CPU利用率来达到目的。若要求较高,则所需资源进人—步递增,若主机硬件不完善,则可能会出现程序挂机现象。

图片[4]-多线程案例(单例模式、阻塞式队列、定时器及线程池)-【聚禄鼎】一站式企业服务平台

A请求飙升=>阻塞队列请求飙升,因为阻塞队列没有太多计算量,只需要简单地保存一个数据即可抵抗较大压力。

乙这一边还是按原有速度消耗数据,并没有因甲大涨导致大涨;这是因为,在A和B之间存在着一个中间区域。这个区域中只有一个节点可以访问。而其他节点都处于休眠状态。因此,不会对整个网络产生冲击。但是如果节点受到攻击的话。B便得到了很好地保护,不会因这一要求起伏而导致垮台。

“削峰”指的是在没有新的数据到来时,通过对数据进行压缩和过滤,将一些不重要的数据删除掉,从而达到降低峰值的目的,”双十一”期间也是如此,所以我们会采取这种方式来进行一些有针对性的活动,比如开展一些有意义的活动或者是开展一些有意义但又不是很重要的活动,例如:参加一些有意义但是又不是特别重要的活动等等。

“填谷”B仍按原频率对以往积压资料进行处理。

而“阻塞队列”则是一个全新的数据结构和方法,它允许用户创建和/或删除一组文件,然后将这些文件保存到服务器程序上。本文首先介绍了目前流行的一些阻塞队列技术以及它们各自的特点;然后详细阐述了“消息队列”的实现过程;最后给出了它与传统的“阻塞队列”相比所具有的优势。而且其所提供的特性并不只是阻塞队列所具有的特性,在此基础上还可以提供更加丰富的特性(针对数据持久化存储、支持多种数据通道、支持多节点容灾冗余备份、支持管理面板、便于配置参数……等).这种队列还被赋予了一个全新的名称——“消息队列(今后发展中应用较为广泛的部件)”。

将阻塞队列嵌入Java标准库。通常情况下,通过调用JDBC将阻塞队列与其它类进行绑定。这样就可以把阻塞队列作为一个单独对象来管理,从而使得开发过程更加简单快捷。假如我们要用阻塞队列来处理某些程序,那么直接用标准库里的就可以。

BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.

在阻塞式入队列中使用put法,在阻塞式出队列中使用take法。

BlockingQueue还有offer、poll和peek,但它们没有阻塞特性。

sign java.util.concurrent.BlockingQueue;

sign java.util.concurrent.LinkedBlockingQueue;

public Demo21{

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

BlockingDeque 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在成功后,会过来叫醒。

sign java.util.concurrent.BlockingDeque;

import java.util.concurrent.LinkedBlockingDeque;

class MyBlocking{

//根据数组来执行阻塞队列

private int[] data = new int[1000];

//队列长度

private int sizing = 0;

//队首下标的

private int head = 0;

//队尾部下标的

private int tail = 0;

private static Object locker = new Object();

public void place(int value) throws InterruptedException{

sync (locker){

if(size == data.length){

//在启动时,站点执行回到return的功能

// return;

//为哪一个对象添加锁返回哪一个对象wait若为this添加锁则this

locker.wait();

}

//在tail中定位新元素方法

data[tail] = value;

tail++;

//解决tail到元素结束时需要从头再循环的问题

//第一种写法

if(tail >= data.length){

tail = 0;

}

//第二种写法

TAO tail = data.length of tail conn;

size++;

//若成功进入队列且队列为非空时唤醒take内的堵塞等待

locker.notify();

}

}

//出队

//采用包装类

public collector take() throws InterruptedException{

sync (locker){

if(size == 0){

// return null;

locker.wait();

}

int ret = dat [head];

head++;

if(head >= data.length){

head = 0;

}

Size –;

//(take)成功后,在put里叫醒了等待

locker.notify();

return rett;

}

}

}

public-class Test08{

public static void main(String[] args) {

MyBlocking queue = new MyBlocking();

//达成一种生产者消费者的模式

Threads t = new Thread(() Voldemort{

int num = 0;

while (true){

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

try {

queue.put(num);

//生产者产量稍慢时,消费者必须追随生产者。”生产“与”消费“之间有什么关系呢?我认为,它们是一种对立统一的关系。首先,”生产“要为”消费“服务。生产一消费一

Yahoo Thread.sleep(500);

} capture(InterruptedException e){

e.printStackTrace();

}

num++;

}

});

t.start();

Threads t2 = New Thread(() Voldemort{

int num = 0;

while (true){

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

try {

num = queue.take();

Thread.sleep(500);

} capture(InterruptedException e){

e.printStackTrace();

}

}

});

t2.start();

}

}

以上编码的输出结果:

图片[5]-多线程案例(单例模式、阻塞式队列、定时器及线程池)-【聚禄鼎】一站式企业服务平台

生产者在生产速度稍慢时,消费者必须跟在生产者后面生产一消费:

图片[6]-多线程案例(单例模式、阻塞式队列、定时器及线程池)-【聚禄鼎】一站式企业服务平台

三,定时器

定时器在软件开发过程中同样具有重要作用。它能在程序运行过程中随程序自动地定时,从而使软件具有较高的效率和灵活性。本文介绍了一种以AT89C51单片机为核心组成的定时器。类似“闹钟”:在到达设定时间后,执行某规定的编码。

3.1在标准库中使用定时器。

标准库提供Timer类;它可以在用户和服务器之间进行信息交换。由于TIMer类能支持多种网络协议,所以它很适合于各种不同类型网络环境下的应用系统。它还具有良好的扩展性,并且易于扩充。在Timer中存在一个特殊线程,负责完成注册任务,而Timer类则以schedule作为核心方法。

schedule中有2个参数,第1个规定了将要完成任务的代码,第2个规定了完成任务后的时间(以毫秒为单位)。

import java.util.Timer command;

import java.util.TimerTask command;

public class Test09 {

public static void main(String[] args) {

timers = new timer ();

timer.schedule(new TimerTask() {

@Override notation

public void runtime(){

System.out.println(“hello”);

}

},3000);

System.out.println(“main”);

}

}

首先进行main, 3秒钟后再进行hello。

输出结果:

图片[7]-多线程案例(单例模式、阻塞式队列、定时器及线程池)-【聚禄鼎】一站式企业服务平台

3.2定时器的实现

Timer里面有哪些要求?

1,叙述任务

创建专用类,用于指示定时器内的工作。在设计过程中要考虑到系统的实时性和可靠性,这样才能保证数据在执行时不出错。本文介绍了一种基于JAVA语言开发的实时多线程软件的实现方法。

2、组织任务(利用某种数据结构,将某些任务组合起来)

由—定数据结构,即具有优先级阻塞队列进行组织;

为什么会有优先级?

由于阻塞队列内的所有任务均有其执行时刻(delay)。而这些工作又与每个del有关,所以我们需要对每一个任务进行排序。如果你想在最短时间内找到某个任务的最早完成时间(time),那么必须先找出它的第一个。首先完成的工作必须是delay中最年轻的工作。用一个有优先级队列,能有效率地找到该delay中最微小的工作。

3、完成时机成熟时的工作

首先要完成时间最前面的工作,必须有个线程,不断地对当前优先队列中的队首元素进行检查,看当前最前面的工作时间到达没有。

sign java.util.concurrent.PriorityBlockingQueue;

//建立类代表任务

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

//工作是专门做什么的

runnable private;

//工作到底什么时候做,把工作需要完成的毫秒级时间戳保存起来

private lengthy period;

//提供了一种构造方法

public MyTask(Runnable runnable;long delay){\\delay}指时间间隔而非绝对时间戳数值

this.runnable = raced;

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

}

public void run(){

菠萝博客(?);

}

public lengthy getTime(){

return period;

}

@Override notation

public int compareTo(MyTask o) {

//任时小者为先,任时大者为后

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

}

}

//定时器

class MyTimer{

//定时器内可存储多项工作

//这里的队列需要考虑线程安全问题,可能会有多个线程中注册任务

//与此同时,有专用线程取任务来完成。当在进程间共享了很多资源时,为了保证系统正常运行,必须保证每个进程都有足够大的空闲时间来完成自己所需的操作。因此我们要把所有线程放在同一个队列中。这里的队列需要关注线程的安全性。

private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();

//用schedule方法将任务登记在队列里

public void schedule(Runnable runnable,long delay){

Task MyTask = Newly Tasked MyTask (runnable,delay);

queue.put(task);

//在每一次任务成功插入后,唤醒扫描线程并允许线程再次检查队首任务以查看是否时机成熟

sync (locker){

locker.notify();

}

}

private Object locker = new Object();

//建立扫描线程

public MyTimer(){

Threads t = new Thread(() Voldemort{

while (true){

try {

//首先把队首元素拿出来

MyTask task = queue.take();

long curTime = System.currentTimeMillis();

//对比看看现在的时机是否已经到来

if (curTime < task.getTime(): A

//时间还没到就将任务塞回排队

queue.put(task);

//规定等待时间

sync (locker){

//wait能在半路上叫醒sleep就无法在半路上叫醒

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

}

}else {

//时机成熟时就完成工作

task.run();

}

} capture(InterruptedException e){

e.printStackTrace();

}

}

});

t.start();

}

}

public-class Test10{

public static void main(String[] args) {

MyTimer myTimer = new MyTimer();

function myTimer.schedule(new Runnable(){

@Override notation

public void runtime(){

System.out.println(“hello”);

}

},3000);

System.out.println(“main”);

}

}

1、说明—任务:runnable加上time

2、采用优先队列的方式组织多个工作。首先,我们提出了一种基于优先级的任务调度算法。该算法通过将所有的资源划分为几个等级并分别分配给各个等级中不同的任务进行处理,从而使整个系统能够高效地运行。

3、实现了schedule方法,用于将任务登记为队列。

4.建立扫描线程,此扫描线程持续取得队首元素,并判断时机是否已到。

此外还需要指出的是,允许MyTask类支持对比:执行Comparable界面和设置对比规则

注意在此处理繁忙和其他事项:

在扫描线程中,若队列内任务为空,则好,此线程在此被堵塞(无问题)担心队列内任务不为空,而任务时间未到,这时叫“忙等待”(等待的确为等待,但也不闲着。既无实质工作输出,也不休息

忙乱等待这类操作很浪费CPU,可根据wait等机制来处理忙乱等情况:

wait有个版本,规定了等待的时间。这个时间是指你在某个时刻,从一个地方去另一个地点所花费的时间。如果你正在旅行,就必须知道要走多久才能到达目的地。(不用notify,该自然唤醒的时候就自然唤醒

算出当前时间与任务目标时间差,等那么久就行了。

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

等待期间,可能会插入一些新工作。这就是所谓的“跨页”现象,通常我们称之为top-hat.为了避免这种情况发生,我们可以通过对页面进行修改来实现这一目的。但是如何才能让修改变得简单呢?新任务为可能存在于先前全部任务之首schedule运行时,则需添加notify运行。

四,线程池

过程较为繁重,如果果经常建立销毁,将造成较大的开销。

线程虽轻于进程,但若产生破坏的次数再提高,仍可找到开销。

所以我们可以在线程池中加入一个叫or的模块来完成协程。

将线程预先建立并放入池中,之后要使用的线程,直接在池中取出,不需要在系统那边应用;线程使用完毕后,并不归还系统,而是重新放入池中以备下一次再次使用,这一次建立销毁的流程,会更加快捷。这就是我们说的线程池技术。在使用这个技术之前,我们先了解一下它有哪些好处:1.节省大量CPU时间,提高工作效率。2.降低系统资源消耗。线程池最重要的优点是降低了每一次创建,破坏线程所带来的损失。

为什么线程被放置在池子中,会比向系统那边请求释放速度要快?

操作系统有两个态,一个是用户态,一个是内核态

图片[8]-多线程案例(单例模式、阻塞式队列、定时器及线程池)-【聚禄鼎】一站式企业服务平台

我们自己编写的编码,是在最上层应用程序这个层次上进行操作,此处的编码全部叫做“用户态”操作的编码。

在这个层次上,API和内核是相互独立的。

比如调用某个System.out.println实质上是通过write系统调用进入内核,由内核来完成一大堆逻辑并控制显示器的输出字符串。

内核是程序的核心部分,“内核态”就是其中之一。

创建一个线程,它自己需要内核来支持。但在我们的软件开发过程中,往往忽视了对这些资源的合理使用,从而使系统效率低下。其实只要掌握一些基本的编程技巧和方法,完全可以通过创建线程来提高效率。(建立线程的实质就是在内核上弄一块PCB,并将其添加到链表中)

调用Thread.start实际上归根到底还是进入内核态运行;同时将所建立的线程置于“池中”,因为池是由用户态执行的,因此该置于池中/从池中取出的程序无需参与内核态的执行,即纯用户态代码即可执行。

纯用户态则不然,它必须先进入内核态才可以运行。

以为内核态的效率低下,倒并不意味着它一定是真正低下的,只是代码已经进入内核态,是无法控制的。在开发过程中,我们会发现一个规律:程序越复杂,运行速度越慢;而当程序执行到最后阶段时,其性能反而下降得很快。其实这就是所谓的“死循环”现象。内核什么时候让你完成工作,什么时候让你获得成果(有时很快,有时很慢)。

4.1线程池在标准库

标准库内线程池称为:ThreadPoolExecutor

juc,java.util,concurrent:concurrents并发之意,Java里许多与多线程有关的部件就位于这concurrens包之中。

ThreadPoolExecutor第四种构造方法:

图片[9]-多线程案例(单例模式、阻塞式队列、定时器及线程池)-【聚禄鼎】一站式企业服务平台

ThreadPoolExecutor(

int corePoolSize.

int maximumPoolSize.

long keepAliveTime.

TimeUnit unit.

Queue BlockingQueue workQueue.

ThreadFactory threadFactory.

Man Handler RejectedExecutionHandler)

intcorePoolSize:核心线程,即正式员工人数

intmaximumPoolSize:最大线程(正式员工加临时工)

longkeep AliveTime:让临时工可以摸到鱼

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

BlockingQueue〈Runnable〉workQueue:任务队列线程池技术是一种很有效的并行编程工具。它能够在不增加处理器负担的情况下提高处理速度。线程池技术分为两个层次:线程池和线程池管理器。线程池提供了submit的方式,允许程序猿在线程池中登记任务,也就是将其加入该任务队列。

ThreadFactorythreadFactory:线程工厂它的名字来自于一个非常有意思的故事。这个故事讲的是,当一位着名的计算机科学家发明了一种新的编程语言后,他发现这种语言有两个最重要的特点——简单和高效。线程如何产生

RejectedExecutionHandlerhandler:拒绝策略。任务队列满员时如何处理?rn20世纪90年代初提出一个新的策略——拒绝策略。1、直接无视最新任务2、堵塞等待3、直接抛弃最旧任务。。。

尽管线程池里有那么多参数,但在使用时最主要的参数,是第一组:线程池里线程数量。

并发和/或多线程时,如何确定线程池中每个线程数?

目前尚无具体数据,多采用性能测试来寻找适当数值。

服务器程序中的线程池是由多线程组成的,每个线程都有自己独立运行的时间和空间,因此在对服务器进行性能测试时,需要将整个服务器划分为50/100/20%(即每块内存)的大小,这样才能达到50/100.同时还要考虑到实际的业务场景。

当线程池中的线程数少了时,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();

//建立线程池,线程池仅有1个线程

// Executors.newSingleThreadExecutor();

//建立与Timer相似的具有定时器的线程池

// Executors.newScheduledThreadPool();

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

pool.submit(new Runnable() {

@Override notation

public void runtime(){

System.out.println(“hello”);

}

});

}

}

}

4.2线程池的实现

线程池中包含哪些内容?

首先,可以对任务进行描述(Runnable的直接用法)

任务的组织是必要的(BlockingQueue的直接用法)

能描述工作线程。

这些线程也要整理好。

要执行的,是向线程池中加入任务

import java.util.ArrayList;

import java.util.List;

sign java.util.concurrent.BlockingDeque;

import java.util.concurrent.LinkedBlockingDeque;

public class MyThreadPool{

/////1、叙述一项直接用Runnable完成的工作

/2、用一种数据结构安排工作

private BlockingDeque queue = new LinkedBlockingDeque<>();

//3.说明了一种线程——工作线程——的作用是将任务从任务队列中取出来进行执行

static class Worker extension thread{

//在当前线程池中存在多个Worker线程且其中均持有前述任务队列

private BlockingDeque queue = null;

public Worker( BlockingDeque queue) {

this.queue = queue;

}

@Override notation

public void runtime(){

while (true){

try {

//在任务队列中循环往复地去取得任务、

//若所述队列是空列则直接被屏蔽,若所述队列是非空列则获取其中内容

Runnable runnable = queue.take();

//取得后完成工作

菠萝博客(?);

} capture(InterruptedException e){

e.printStackTrace();

}

}

}

}

/4、建立数据结构,整理多个线程

private List 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);

} capture(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++) {

submit myThreadPool.submit(new Runnable(){

@Override notation

public void runtime(){

System.out.println(“hello myThreadPool”);

}

});

}

}

}

原文链接:http://www.sfdkj.com/12953.html

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片