1. 简介

1、延迟初始化出现于.NET 4.0,主要用于提高性能,避免浪费计算,并减少程序内存要求。也可以称为,按需加载。

2、从net 4.0开始,C#开始支持延迟初始化,通过Lazy关键字,我们可以声明某个对象为仅仅当第一次使用的时候,再初始化,如果一直没有调用,那就不初始化,省去了一部分不必要的开销,提升了效率,同时Lazy是天生线程安全的

3、注:Lazy<T> 对象初始化默认是线程安全的,在多线程环境下,第一个访问 Lazy<T> 对象的 Value 属性的线程将初始化 Lazy<T> 对象,以后访问的线程都将使用第一次初始化的数据。

2. 应用场景

  • 1、大对象和耗时对象

    对象创建成本高且程序可能不会使用它。 例如,假定内存中有具有 Orders 属性的 Customer 对象,该对象包含大量 Order 对象,初始化这些对象需要数据库连接。 如果用户永远不要求显示 Orders 或在计算中使用该数据,则无需使用系统内存或计算周期来创建它。 通过使用 Lazy<Orders> 来声明 Orders 对象用于迟缓初始化,可以避免在不使用该对象时浪费系统资源。对象创建成本高,且希望将其创建推迟到其他高成本操作完成后。 例如,假定程序在启动时加载多个对象实例,但是只需立即加载其中一部分。 可以通过推迟初始化不需要的对象,直到创建所需对象,提升程序的启动性能。

  • 2、封装方法

    将委托或者方法对象保存,并在需要的时候调用。

虽然可以编写自己的代码来执行迟缓初始化,但我们建议使用 Lazy<T>。 Lazy<T> 及其相关的类型还支持线程安全并提供一致的异常传播策略。

下表列出了 .NET Framework 版本 4 提供的在不同方案中启用迟缓初始化的类型。

表 1
类型 说明
Lazy<T> 为任何类库或用户定义类型提供迟缓初始化语义的包装类。
ThreadLocal<T> 类似于 Lazy<T>,除了该包装类基于线程本地提供迟缓初始化语义。 每个线程都可以访问自己唯一的值。
LazyInitializer 为对象的迟缓初始化提供高级 static(Visual Basic 中的 Shared)方法,无需支付类的成本。

Lazy构造函数

构造函数
Lazy<T>()

初始化 Lazy<T> 类的新实例。 发生迟缓初始化时,使用目标类型的无参数构造函数。

Lazy<T>(Boolean)

初始化 Lazy<T> 类的新实例。 发生迟缓初始化时,使用目标类型的无参数构造函数和指定的初始化模式。

Lazy<T>(Func<T>)

初始化 Lazy<T> 类的新实例。 出现迟缓初始化时,将使用指定的初始化函数。

Lazy<T>(Func<T>, Boolean)

初始化 Lazy<T> 类的新实例。 当延迟初始化发生时,将使用指定的初始化函数和初始化模式。

传入方法,用来return value。

Lazy<T>(Func<T>, LazyThreadSafetyMode)

初始化 Lazy<T> 类的新实例,它使用指定的初始化函数和线程安全模式。

传入方法,用来return value。

Lazy<T>(LazyThreadSafetyMode)

初始化 Lazy<T> 类的新实例,其中使用 T 的无参数构造函数和指定的线程安全性模式。

Lazy<T>(T)

初始化 Lazy<T> 类的新实例,该类使用已预先初始化的指定值。

Lazy.Value属性的使用

默认值或者方法的返回值

Lazy对象创建后,并不会立即创建对应的对象,一旦使用.Vale,那么对应的变量就会被实例化,IsValueCreated属性也就变成了true。

Value属性是只读的,也就意味着如果Value存储了引用类型,将无法为其分配新对象,只可以更改此对象公共的属性或者字段等,如果Value存储的是值类型,那么就不能修改其值了,只能通过再次调用变量的函数使用新的参数来创建新的变量

在Lazy对象创建后,在首次访问变量的Value属性前,

Lazy.IsValueCreated属性的使用

判断是实例化

LazyThreadSafetyMode 枚举

指定 Lazy<T> 实例如何同步多个线程间的访问。

所有此类构造函数都是完全线程安全的。

在内部使用锁,则可能会发生死锁

ExecutionAndPublication  
不是线程安全的。 None false 不适用。
完全线程安全;用于初始化值的线程争用。 PublicationOnly

构造时使用默认的初始化方式

using System;

namespace LazyUsage
{
class LazyDemo
{
static void Main()
{
Lazy<Data> lazyData = new Lazy<Data>();
Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated);
lazyData.Value.Print();//此处访问时才会将Data真正的初始化
Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated); Console.ReadKey();
}
} class Data
{
public Data()
{
Console.WriteLine("Data::.ctor->Initialized");
} public void Print()
{
Console.WriteLine("Data::Print->println");
}
}
}

输出结果

Main->is lazyData Initialized? value = False
Data::.ctor->Initialized
Data::Print->println
Main->is lazyData Initialized? value = True

构造时使用指定的委托初始化

using System;

namespace LazyUsage
{
class LazyDemo
{
static void Main()
{
//指定委托来初始化Data
Lazy<Data> lazyData = new Lazy<Data>(
() =>
{
Console.WriteLine("Main->lazyData will be Initialized!");
return new Data("Test");
});
Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated);
lazyData.Value.Print();
Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated); Console.ReadKey();
}
} class Data
{
public string Name { get; private set; } public Data(string name)
{
Name = name;
Console.WriteLine("Data::.ctor->Initialized,name = "+name);
} public void Print()
{
Console.WriteLine("Data::Print->name = " + Name);
}
}
}

输出结果

Main->is lazyData Initialized? value = False
Main->lazyData will be Initialized!
Data::.ctor->Initialized,name = Test
Data::Print->name = Test
Main->is lazyData Initialized? value = True

扩展. 实现延迟属性

class Customer
{
private Lazy<Orders> _orders; public string CustomerID {get; private set;} public Customer(string id)
{
CustomerID = id;
_orders = new Lazy<Orders>(() =>
{
return new Orders(this.CustomerID);
});
} public Orders MyOrders
{
get
{
return _orders.Value;
}
}
}

ThreadLocal<T> 类

该类相当与一个线程结界,将变量的值和作用限制在线程中。所以用该类包装过的类型是线程安全的。该类包装过的类型变量的只能在该线程中使用,其他线程包括子线程无法使用。

ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

构造函数

构造函数
ThreadLocal<T>()

初始化 ThreadLocal<T> 实例。

ThreadLocal<T>(Boolean)

初始化 ThreadLocal<T> 实例并指定是否可从任意线程访问所有值。

ThreadLocal<T>(Func<T>)

使用指定的 valueFactory 函数初始化 ThreadLocal<T> 实例。传入方法 用来return value

ThreadLocal<T>(Func<T>, Boolean)

使用指定的 valueFactory 函数和指示是否可从任意线程访问所有值的标志初始化 ThreadLocal<T> 实例。

传入方法 用来return value

属性

属性
IsValueCreated

获取是否在当前线程上初始化 Value。用法类似lazy<T>

Value

数据类型的默认值或者方法返回值。用法类似lazy<T>

Values

获取当前由已经访问此实例的所有线程存储的所有值的列表。

提供线程数据的本地存储。

static void Main(string[] args)
{
ThreadLocal<int> threadLocal = new ThreadLocal<int>();
//在主线程这个变量值为1
threadLocal.Value = 1;
new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start();
new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start();
new Thread(() => Console.WriteLine($"托管线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value++}")).Start();
Console.WriteLine($"主线程ID:{Thread.CurrentThread.ManagedThreadId} 值为:{threadLocal.Value}");
}

输出结果:
托管线程ID:10 值为:0
主线程ID:1 值为:1
托管线程ID:11 值为:0
托管线程ID:12 值为:0

using System;
using System.Threading;
using System.Threading.Tasks; class ThreadLocalDemo
{ // Demonstrates:
// ThreadLocal(T) constructor
// ThreadLocal(T).Value
// One usage of ThreadLocal(T)
static void Main()
{
// Thread-Local variable that yields a name for a thread
ThreadLocal<string> ThreadName = new ThreadLocal<string>(() =>
{
return "Thread" + Thread.CurrentThread.ManagedThreadId;
}); // Action that prints out ThreadName for the current thread
Action action = () =>
{
// If ThreadName.IsValueCreated is true, it means that we are not the
// first action to run on this thread.
bool repeat = ThreadName.IsValueCreated; Console.WriteLine("ThreadName = {0} {1}", ThreadName.Value, repeat ? "(repeat)" : "");
}; // Launch eight of them. On 4 cores or less, you should see some repeat ThreadNames
Parallel.Invoke(action, action, action, action, action, action, action, action); // Dispose when you are done
ThreadName.Dispose();
}
}
// This multithreading example can produce different outputs for each 'action' invocation and will vary with each run.
// Therefore, the example output will resemble but may not exactly match the following output (from a 4 core processor):
// ThreadName = Thread5
// ThreadName = Thread6
// ThreadName = Thread4
// ThreadName = Thread6 (repeat)
// ThreadName = Thread1
// ThreadName = Thread4 (repeat)
// ThreadName = Thread7
// ThreadName = Thread5 (repeat)

【C# 线程】 延迟初始化的更多相关文章

  1. .NET多线程之线程安全,Lock(锁)、Monitor(同步访问)、LazyInitializer(延迟初始化)、Interlocked(原子操作)、static(静态)构造函数、volatile、

    1.什么是线程安全 线程安全是编程中的术语,指某个函数.函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成.一般来说,线程安全的函数应该为每个调用它的线程分配专门的 ...

  2. C#性能优化之Lazy<T> 实现延迟初始化

    在.NET4.0中,可以使用Lazy<T> 来实现对象的延迟初始化,从而优化系统的性能.延迟初始化就是将对象的初始化延迟到第一次使用该对象时.延迟初始化是我们在写程序时经常会遇到的情形,例 ...

  3. C#性能优化:延迟初始化Lazy<T>

    1. 概述 我们创建某一个对象需要很大的消耗,而这个对象在运行过程中又不一定用到,为了避免每次运行都创建该对象,这时候延迟初始化(也叫延迟实例化)就出场了. 延迟初始化出现于.NET 4.0,主要用于 ...

  4. C# 延迟初始化

    一个对象的延迟初始化意味着该对象的创建将会延迟至第一次使用该对象时.(在本主题中,术语“延迟初始化”和“延迟实例化”是同义词.)延迟初始化主要用于提高性能,避免浪费计算,并减少程序内存要求. 以下是最 ...

  5. Lazy<T>延迟初始化

    延迟初始化:Lazy<T> 1. 概述 我们创建某一个对象需要很大的消耗,而这个对象在运行过程中又不一定用到,为了避免每次运行都创建该对象,这时候延迟初始化(也叫延迟实例化)就出场了. 延 ...

  6. 双重检查锁定与延迟初始化(转自infoq)

    很好的文章,转自http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization 在java程序中,有 ...

  7. Effective Java 第三版——83. 明智谨慎地使用延迟初始化

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  8. 延迟初始化Lazy

    延迟初始化出现于.NET 4.0,主要用于提高性能,避免浪费计算,并减少程序内存要求.也可以称为,按需加载. 基本语法: Lazy<T> xx = new Lazy<T>(); ...

  9. volatile双重检查锁定与延迟初始化

    一.基本概念: 1.volatile是轻量级的synchronized,在多核处理器开发中保证了共享变量的“可见性”.可见性的意思是,当一个线程修改一个共享变量时,另一个线程能读到这个修改的值. 2. ...

随机推荐

  1. 1000粉!使用Three.js制作一个专属3D奖牌🥇

    背景 破防了 !突然发现 SegmentFault 平台的粉丝数量已经突破 1000 了,它是我的三个博客平台掘金.博客园.SegmentFault中首个粉丝突破 1000 的,于是设计开发这个页面, ...

  2. pytest文档8-参数化(parametrize)结合allure.title()生成不同标题报告

    参数化parametrize 先看一个简单的pytest参数化案例演示test_a.py # test_a.py import pytest import allure def login(usern ...

  3. golang中的标准库context

    在 Go http包的Server中,每一个请求在都有一个对应的 goroutine 去处理.请求处理函数通常会启动额外的 goroutine 用来访问后端服务,比如数据库和RPC服务.用来处理一个请 ...

  4. Error building SqlSession. ### The error may exist in dao/UserMapper.xml ### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration(2 字节的 UTF-8 序列的字节 2 无效。)

    关于在学习Mybatis框架时运行报错 Caused by: org.apache.ibatis.exceptions.PersistenceException: ### Error building ...

  5. IntelliJ IDEA 热部署,修改java文件 不用重启tomcat

    详情见大佬:https://www.cnblogs.com/chenweichu/articles/6838842.html

  6. windows下使用LTP分词,安装pyltp

    1.LTP介绍 ltp是哈工大出品的自然语言处理工具箱, 提供包括中文分词.词性标注.命名实体识别.依存句法分析.语义角色标注等丰富. 高效.精准的自然语言处理技术.pyltp是python下对ltp ...

  7. 计算机网络-5-9-TCP拥塞控制

    TCP拥塞控制 拥塞控制的一般原理 在计算机网络中的链路容量(带宽),交换节点中的缓存和处理机等,都是网络的资源,在某段时间,若对网络中某一资源的需求超过该资源所能提供的可用部分,网络性能就会变坏,这 ...

  8. Spring事物

    简介 Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现. Spring事务管理器的接口是org ...

  9. Thread的打断

    常用方法 public void interrupt() t.interrupt() 打断t线程(设置t线程某给标志位f=true,并不是打断线程的运行),不能打断正在竞争锁的线程. public b ...

  10. 关于obj.class.getResource()和obj.getClass().getClassLoader().getResource()的路径问题

    感谢原文作者:yejg1212 原文链接:https://www.cnblogs.com/yejg1212/p/3270152.html 注:格式内容与原文有轻微不同. Java中取资源时,经常用到C ...