简单理解设计模式——享元模式-线程池-任务(tesk)
前面在写到多线程的文章的时候,一直想写一篇关于线程池等一系列的文章,做一下记录,本篇博客记录一下设计模式中享元模式的设计思想,以及使用享元模式的实现案例——线程池,以及线程池的简化版——任务(task)
享元模式
在软件开发过程中,如果我们需要重复使用某个对象的时候,重复的去new这样一个对象,我们在内存中就会多次的去申请内存空间了,这样,可能会出现内存使用越来越多的情况。
如果让我们解决这个问题,会不会这样想:“既然是同一个对象,能不能只创建一个对象,然后下次需要再创建这个对象的时候,让它直接用已经创建好的对象就好了”,也就是说--让一个对象共享!
这种实现方式有点类似排版印刷术,将所有的字先提前印刷好,需要哪个字直接拿过来用,就不用每次打印字的时候再重新造一个字的模板了,这就是我理解的享元模式的思想。
享元模式的正式定义:
运用共享技术有效的支持大量细粒度的对象,享元模式可以避免大量相类似的开销,在软件开发中如果需要生成大量细粒度的类实例来表示数据,如果这些实例除了几个参数外基本都是相同的,这个时候就可以使用享元模式。如果把这些参数(指的是这是实例不同的参数,比如:排版印刷的时候每个字的位置)移动到类的外面,在调用方法时把他们传递进来,这样就通过共享数据,减少了单个实例的数目(这个也是享元模式的实现要领),我们把类实例外面的参数称之为享元对象的外部状态,把在享元模式内部定义称之为内部状态。
享元模式的实现小demo
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 享元模式
{
class Program
{
static void Main(string[] args)
{
//定义外部状态,例如字母的位置等信息
int externalstate = ;
//初始化享元工厂
FlyweighFactory factory = new FlyweighFactory();
//判断是否已经创建了字母A,如果已经创建就直接使用创键的对象A
Flyweight fa = factory.GetFlyweight("A");
if (fa != null)
{
//把外部状态作为享元对象的方法调用参数
fa.Operation(--externalstate);
}
//判断是否已经创建了字母B
Flyweight fb = factory.GetFlyweight("B");
if (fb!=null)
{
fb.Operation(--externalstate);
}
//判断是否已经创建了字母C
Flyweight fc = factory.GetFlyweight("C");
if (fc != null)
{
fc.Operation(--externalstate);
}
//判断是否创建了字母D
Flyweight fd = factory.GetFlyweight("D");
if (fd != null)
{
fd.Operation(--externalstate);
}
else
{
Console.WriteLine("驻留池中不存在字符串D");
//这个时候就需要创建一个对象并放入驻留池中
ConcreteFlyweight d = new ConcreteFlyweight("D");
factory.flyweights.Add("D", d);
}
Console.ReadLine(); }
}
/// <summary>
/// 享元工厂,负责创建和管理享元对象
/// </summary>
public class FlyweighFactory
{
/// <summary>
/// 定义一个池容器
/// </summary>
public Hashtable flyweights = new Hashtable();
public FlyweighFactory()
{
flyweights.Add("A", new ConcreteFlyweight("A"));//将对应的内部状态添加进去
flyweights.Add("B", new ConcreteFlyweight("B"));
flyweights.Add("C", new ConcreteFlyweight("C"));
}
/// <summary>
/// 根据键来查找值
/// </summary>
/// <param name="key">键</param>
/// <returns></returns>
public Flyweight GetFlyweight(string key)
{
return flyweights[key] as Flyweight;
}
} /// <summary>
/// 抽象享元类,提供具体享元类具有的方法
/// </summary>
public abstract class Flyweight
{
public abstract void Operation(int extrinsicstate);
}
/// <summary>
/// 具体享元对象,这样我们不把每个字符设计成一个单独的类了,而是把共享的字母作为享元对象的内部状态
/// </summary>
public class ConcreteFlyweight : Flyweight
{
/// <summary>
/// 内部状态
/// </summary>
private string intrinsicstate;
public ConcreteFlyweight(string innerState)
{
this.intrinsicstate = innerState;
}
/// <summary>
/// 享元类的实例方法
/// </summary>
/// <param name="extrinsicstate">外部状态</param>
public override void Operation(int extrinsicstate)
{
Console.WriteLine("具体实现类:intrinsicstate(内部状态){0},extrinsicstate(外部状态){1}", intrinsicstate, extrinsicstate);
}
}
}
享元模式的使用场景:
一个系统中有大量的对象;
这些对象耗费大量的内存
这些对象可以按照内部状态分成很多组,当把外部对象从对象中剔除时,每一个组都可以仅用一个对象代替
软件系统不依赖这些对象的身份。
注意:使用享元模式需要额外的维护一个记录子系统已有额所有享元的表,这也是耗费资源的。所以当在有足够多的对象实例,或者这些享元实例的创建特别耗费资源的时候可以考虑使用享元模式。
不知道你这里有没有发现,其实享元模式定义了一个“池“的概念。在排版印刷的时候,我们将所有的字(内部状态)放在一个字体池中,使用完之后将这些字(内部状态)再放回池中。
这跟我们接下来说的线程池似乎不谋而合。
线程池:
先说一下后台线程和前台线程:两者几乎相同,唯一的区别是,前台线程会阻止进程的正常退出,后台线程则不会。
线程的创建和销毁要消耗很多时间,而且过多的线程不仅会浪费内存空间,还会导致线程上下文切换频繁,影响程序性能,为改善这些问题,.Net运行时(CLR)会为每个进程开辟一个全局唯一的线程池来管理其线程。
线程池内部维护一个操作请求队列,程序执行异步操作的时候,添加目标操作到线程池的请求队列;线程池代码提取记录项并派发线程池中的一个线程;如果线程池中没有可用线程,就创建一个新的线程,创建的新线程不会随着任务的完成而销毁,这样就可以避免线程的频繁创建和销毁。如果线程池中大量的线程长时间无所事事,空闲线程会进行自我终结以释放资源。
线程池中通过保持进程中线程的少量和高效来优化程序的性能。
当线程数达到设定值且忙碌,异步任务将进入请求队列,直到有线程空闲才会执行
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace 线程池
{
class Program
{
static void Main(string[] args)
{
RunThreadPoolDemo();
Console.ReadLine();
} static void RunThreadPoolDemo()
{
线程池.ThreadPoolDemo.ShowThreadPoolInfo();
ThreadPool.SetMaxThreads(, );//默认(1023,1000)(8核心CPU)
ThreadPool.SetMinThreads(, ); // 默认是CPU核心数
线程池.ThreadPoolDemo.ShowThreadPoolInfo();
线程池.ThreadPoolDemo.MakeThreadPoolDoSomeWork();//计算限制任务
线程池.ThreadPoolDemo.MakeThreadPoolDoSomeIOWork();//IO限制任务
}
} public class ThreadPoolDemo
{
/// <summary>
/// 显示线程池信息
/// </summary>
public static void ShowThreadPoolInfo()
{
int workThreads, completionPortThreads; //当前线程池可用工作线程数量和异步IO线程数量
ThreadPool.GetAvailableThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"GetAvailableThreads => workThreads:{0};completionPortThreads:{1}", workThreads, completionPortThreads);
//线程池最大可用的工作线程数量和异步IO线程数量
ThreadPool.GetMaxThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"GetMaxThreads => workThreads:{0};completionPortThreads:{1}", workThreads, completionPortThreads);
//出现新的请求,判断是否需要创建新线程的依据
ThreadPool.GetMinThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"GetMinThreads => workThreads:{0};completionPortThreads:{1}", workThreads, completionPortThreads);
Console.WriteLine(); }
/// <summary>
/// 让线程池做些事情
/// </summary>
/// <param name="workCount"></param>
public static void MakeThreadPoolDoSomeWork(int workCount = )
{
for (int i = ; i < workCount; i++)
{
int index = i;
ThreadPool.QueueUserWorkItem(s =>
{
Thread.Sleep();//模拟工作时长
Debug.Print($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] is running. [{index}]");
ShowAvailableThreads("WorkerThread");
});
}
} /// <summary>
/// 让线程做一些IO工作
/// </summary>
public static void MakeThreadPoolDoSomeIOWork()
{
//随便找一些可以访问的网址
IList<string> urlList = new List<string>()
{
"http://news.baidu.com/",
"https://www.hao123.com/",
"https://map.baidu.com/",
"https://tieba.baidu.com/",
"https://wenku.baidu.com/",
"http://fanyi-pro.baidu.com",
"http://bit.baidu.com/",
"http://xueshu.baidu.com/",
"http://www.cnki.net/",
"http://www.wanfangdata.com.cn",
}; foreach (var uri in urlList)
{
WebRequest request = WebRequest.Create(uri);
//request包含此异步请求的状态信息的对象
request.BeginGetResponse(ac =>
{
try
{
WebResponse response = request.EndGetResponse(ac);
ShowAvailableThreads("IOThread");
Debug.Print($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] is running. [{response.ContentLength}]");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
},request);
}
} /// <summary>
/// 打印线程池可用线程
/// </summary>
/// <param name="sourceTag"></param>
private static void ShowAvailableThreads(string sourceTag = null)
{
int workThreads, completionPortThreads;
ThreadPool.GetAvailableThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"{0} GetAvailableThreads => workThreads:{1};completionPortThreads:{2}",sourceTag,workThreads,completionPortThreads);
Console.WriteLine();
} /// <summary>
/// 取消通知者
/// </summary>
public static CancellationTokenSource CTSource { get; set; } = new CancellationTokenSource(); /// <summary>
/// 执行可取消的任务
/// </summary>
public static void DoSomeWorkWithCancellation()
{
ThreadPool.QueueUserWorkItem(t =>
{
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] begun running. [0 - 9999]"); for (int i = ; i < ; i++)
{
if (CTSource.Token.IsCancellationRequested)
{
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] recived the cancel token. [{i}]");
break;
}
Thread.Sleep();//模拟工作时长
}
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] was cancelled.");
});
}
}
}
代码中含有中文命名空间,这样写不规范,请不要模仿~
线程池内部维护着一个工作项队列,这个队列指的是线程池的全局队列,实际上,除了全局队列,线程池会给每个工作者线程维护一个本地队列
当我们调用ThreadPool.QueueUserWorkItem
方法时,工作项会被放入全局队列;使用定时器Timer
的时候,也会将工作项放入全局队列;但是,当我们使用任务Task
的时候,假如使用默认的任务调度器,任务会被调度到工作者线程的本地队列中。
工作者线程优先执行本地队列中最新进入的任务,如果本地队列中已经没有任务,线程会尝试从其他工作者线程任务队列的队尾取任务执行,这里需要进行同步。如果所有工作者线程的本地队列都没有任务可以执行,工作者线程才会从全局队列取最新的工作项来执行。所有任务执行完毕后,线程睡眠,睡眠一定时间后,线程醒来并销毁自己以释放资源
异步IO实现过程如下:
- 托管的IO请求线程调用Win32本地代码ReadFile方法
- ReadFile方法分配IO请求包IRP并发送至Windows内核
- Windows内核把收到的IRP放入对应设备驱动程序的IRP队列中,此时IO请求线程已经可以返回托管代码
- 驱动程序处理IRP并将处理结果放入.NET线程池的IRP结果队列中
- 线程池分配IO线程处理IRP结果
任务(Task)
我理解的任务是在线程池的基础上进行的优化,但是任务比线程有更小的开销和更精确的控制,任务是架构在线程之上的,就是说,任务最后还是抛给线程去执行。
开10个任务,并不会开是个线程,这是我理解的再线程池的基础上优化的依据。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace Tesk_任务_
{
class Program
{
static void Main(string[] args)
{ #region 创建任务
//第一种方式开一个任务
Task t = new Task(() =>
{
Console.WriteLine("任务工作开始......");
//模拟工作过程
Thread.Sleep();
});
t.Start();
//第二种方式创建任务
Task t = Task.Factory.StartNew(() =>
{
Console.WriteLine("任务工作开始......");
Thread.Sleep();
});
//当第一的任务工作完成之后接着执行这一步操作
t.ContinueWith((task) =>
{
Console.WriteLine("任务完成,完成时的状态为:");
Console.WriteLine("IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);
});
Console.WriteLine("等待任务完成!");
Console.ReadKey();
#endregion #region 任务的生命周期
var task1 = new Task(() =>
{
Console.WriteLine("Begin");
Thread.Sleep();
Console.WriteLine("Finish");
});
Console.WriteLine("Begin start:" + task1.Status);
task1.Start();//开启任务
Console.WriteLine("After start:" + task1.Status);
task1.Wait();
Console.WriteLine("After Finsh:" + task1.Status);
Console.ReadLine();
#endregion #region Task的任务控制
var task1 = new Task(() =>
{
Console.WriteLine("Begin1");
Thread.Sleep();
Console.WriteLine("Finish1");
});
var task2 = new Task(() =>
{
Console.WriteLine("Begin2");
Thread.Sleep();
Console.WriteLine("Finish2");
}); task1.Start();//开启任务
task2.Start();//开启第二个任务
//public Task ContinueWith(Action<Task> continuationAction);
//ContinueWith<string>:string是这个任务的返回值类型
var result = task1.ContinueWith<string>(task =>
{
Console.WriteLine("task1 finished");
return "this is task result";
});
//task1.Wait();//等待第一个任务完成
//Task.WaitAll(task1, task2);
//Console.WriteLine("All task Finshed:");
Console.WriteLine(result.Result.ToString());
Console.ReadLine(); //通过ContinueWith获取第一个任务的返回值
var a = Task.Factory.StartNew(() => { return "One"; }).ContinueWith<string>(ss => { return ss.Result.ToString(); }); Task b = new Task<string>(() =>
{
return "one";
});
Console.WriteLine(b.ToString());//这样获取不到b任务的返回值 Console.WriteLine(a.Result); #region TaskContinuationOptions 定义延续任务在什么情况下执行
Task<Int32> t = new Task<Int32>(i => Sum((Int32)i), );
t.Start();
//TaskContinuationOptions创建延续任务的行为,OnlyOnRanToCompletion只有当前面的任务执行完才能安排延续任务
t.ContinueWith(task => Console.WriteLine("The sum is:{0}", task.Result), TaskContinuationOptions.OnlyOnRanToCompletion); //OnlyOnFaulted延续任务前面的任务出现了异常才会安排延续任务,将任务中的错误信息打印出来了
t.ContinueWith(task => Console.WriteLine("Sum throw:{0}", task.Exception), TaskContinuationOptions.OnlyOnFaulted);
//OnlyOnCanceled延续任务前面的任务已取消的情况下才会安排延续任务
t.ContinueWith(task => Console.WriteLine("Sum was cancel:{0}", task.IsCanceled), TaskContinuationOptions.OnlyOnCanceled);
try
{
t.Wait();
}
catch (AggregateException)
{ Console.WriteLine("出错");
}
#endregion #region AttachedToParnt枚举类型(父任务)
Task<Int32[]> parent = new Task<int[]>(() =>
{
var results = new Int32[];
new Task(() => results[] = Sum(), TaskCreationOptions.AttachedToParent).Start();
new Task(() => results[] = Sum(), TaskCreationOptions.AttachedToParent).Start();
new Task(() => results[] = Sum(), TaskCreationOptions.AttachedToParent).Start();
return results;
});
//任务返回的是一个数组,我要做的是对数组进行打印ForEach(),
var cwt = parent.ContinueWith(parentTask => Array.ForEach(parentTask.Result, Console.WriteLine));
parent.Start();
cwt.Wait();
#endregion #region 取消任务
CancellationTokenSource cts = new CancellationTokenSource();
Task<Int32> t = new Task<int>(() => Sum(cts.Token, ), cts.Token);
//可以现在开始,也可以以后开始
t.Start();
//在之后的某个时间,取消CancellationTokenSource 以取消Task
cts.Cancel();//这个是异步请求,Task可能已经完成了
//注释这个为了测试抛出的异常
//Console.WriteLine("This sum is:", t.Result);
try
{
//如果任务已经取消了,Result会抛出AggregateException
Console.WriteLine("This sum is:", t.Result);
}
catch (AggregateException x)
{
x.Handle(e => e is OperationCanceledException);
Console.WriteLine("Sum was Canceled"); } #endregion
Console.ReadLine();
#endregion
} private static Int32 Sum(Int32 i)
{
Int32 sum = ;
for (; i > ; i--)
{
checked { sum += i; }
}
return sum;
}
private static Int32 Sum(CancellationToken ct, Int32 i)
{
Int32 sum = ;
for (; i >; i--)
{
//在取消标志引用的CancellationTokenSource上如果调用
//Cancel,下面这一行就会抛出OperationCanceledException
ct.ThrowIfCancellationRequested();
checked { sum += i; }
}
return sum;
}
}
}
注意:这里的代码并不是复制就可以执行的,之前做demo测试的时候将所有的代码都糅杂在一起了!
关于技术与业务我也纠结过一段时间,是业务重要还是技术重要,后来发现,技术是服务于业务的,设计模式是技术吗?其实它是为了解决某种实现场景总结出来的。业务和技术应该是相辅相成的,在工作中难免会遇到一些重复性的工作,可不可以尝试着改进在工作中的实现方式来提高自己的技术水平呢?加油~ 追梦人!
参考文章:
https://www.cnblogs.com/chenbaoshun/p/10566124.html
https://www.cnblogs.com/zhili/p/FlyweightPattern.html
设计模式相关网页:
https://www.cnblogs.com/caoyc/p/6927092.html
简单理解设计模式——享元模式-线程池-任务(tesk)的更多相关文章
- Java设计模式—享元模式
享元模式:是池技术的重要实现方式. 定义如下: 使用共享对象可有效地支持大量的细粒度的对象. 个人理解:享元模式利用共享对象的技术,解决了Java中内存溢出的问题. 享元模式的定义为我们提出了两个要求 ...
- java设计模式——享元模式
一. 定义与类型 定义:提供了减少对象数量从而改善应用所需的对象结构的方式,运用共享技术有效地支持大量细粒度的对象 类型:结构性 二. 使用场景 (1) 常常应用于系统底层的开发,以便解决系统的性能 ...
- 【设计模式】Java设计模式 - 享元模式
Java设计模式 - 享元模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起记录分享自己 ...
- 8. 星际争霸之php设计模式--享元模式
题记==============================================================================本php设计模式专辑来源于博客(jymo ...
- C++设计模式——享元模式
本文版权归果冻说所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利.如果这篇文章对你有帮助,你可以请我喝杯咖啡. » 本文链接:http:// ...
- javascript设计模式——享元模式
前面的话 享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级.享元模式的核心是运用共享技术来有效支持大量细粒度的对象.如果系统中因为创建了大量类似的对象而 ...
- Java设计模式-享元模式(Flyweight)
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用. FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查 ...
- java设计模式---享元模式
享元模式 顾名思义:共享元对象.如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必为每一次使用创建新的对象. 享元模式是为数不多的.只为提升系统性能而生的设计模式.它的主要作用就 ...
- 结合JDK源码看设计模式——享元模式
前言 在说享元模式之前,你一定见到过这样的面试题 public class Test { public static void main(String[] args) { Integer a=Inte ...
随机推荐
- H3C命令调试debugging--用户视图
<H3C>terminal debugging //使用debugging必须使用的命令--打开调试信 息的屏幕输出开关 <H3C>display debugging ...
- Linux 内核注册一个 USB 驱动
所有 USB 驱动必须创建的主要结构是 struct usb_driver. 这个结构必须被 USB 驱动填 充并且包含多个函数回调和变量, 来向 USB 核心代码描述 USB 驱动: struct ...
- 大数据vs计算机
大数据有两个方向,一个是偏计算机的,另一个是偏经济的.你学过Java,所以你可以偏将计算机 基础1. 读书<Introduction to Data Mining>,这本书很浅显易懂,没有 ...
- vim 方式快捷编辑代码
说明 **I: ** 行首插入 **a: ** 追加 **A: ** 行尾插入 **R: ** 替换文字 **v: ** 选择 **ctrl-v: ** 选择举行区域 **x: ** 删除 **dd: ...
- [Vue源码]一起来学Vue模板编译原理(二)-AST生成Render字符串
本文我们一起通过学习Vue模板编译原理(二)-AST生成Render字符串来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学V ...
- 记一次linux磁盘清理 - 已经删除的文件占用了大量磁盘空间
今天开发环境磁盘占满了,导致开发环境上的 nginx .redis 等组件总是报异常. 跳到系统根目录下 cd / 检查磁盘占用情况 df -h 哇,40G硬盘全用完了.看看是哪些文件占了那么多内存. ...
- 机器学习算法概述第五章——CART算法
特点: 是一个二叉树,元素可以重复利用,可以做回归也可以做分类,分类用最小二乘法,即误差平方和最小 切割方法: 对于可量化的x来说: 切割点通常为两个x的平均值 左右两部分分别取均值,再评判以哪个分割 ...
- SpringJDBC的使用(转载)
转载自 https://www.yiibai.com/spring/maven-spring-jdbc-example.html 工具: eclipse4.7.2及mysql-8.0.13 项目最 ...
- python之面向对象中的多态
直接看代码: class Dog: def __init__(self,name): self.name = name def play(self): print("%s在汪汪汪" ...
- 洛谷$P4316$ 绿豆蛙的归宿 期望
正解:期望 解题报告: 传送门! 看懂题目还是挺水的$(bushi$ 三个方法,但因为题目太水了懒得一一介绍了,,,反正都是期望,,,$so$随便港个最简单的趴$QwQ$ 直接考虑每条边的贡献,就会是 ...