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. 返回值Student处理过程

  2. Mac系统之U盘重装(降级)

    U盘重装mac系统步骤如下 1. 将Mac系统U盘插上电脑,按下Mac开机键后,摁住"option"键不放,稍等几秒会出现启动盘选择界面,选择"安装 macOS" ...

  3. Method com/mchange/v2/c3p0/impl/NewProxyResultSet.isClosed()Z is abstract

    HTTP Status 500 - Handler dispatch failed; nested exception is java.lang.AbstractMethodError: Method ...

  4. 《HelloGitHub》第 70 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. https://github.com/521xueweiha ...

  5. PyCharm专业版破解教程

    破解步骤: 1. 下载激活包和注册码:https://pan.baidu.com/s/1_1nrQdTElf4aEg8vGnMTzg 2. 将下载的破解包放入PyCham安装目录的bin文件夹中 3. ...

  6. 「ZJOI2014」璀灿光华

    「ZJOI2014」璀灿光华 实际上,可以不用建水晶立方体... 因为,发光水晶的方向都要枚举一遍. 只需知道发光水晶每个方向有哪些水晶就可以了. 对于一个发光水晶,将它连接的水晶标号. 从该水晶bf ...

  7. Swift 介绍

    简介 Swift 语言由苹果公司在 2014 年推出,用来撰写 OS X 和 iOS 应用程序 2014 年,在 Apple WWDC 发布 几家欢喜,几家愁 愁者:只学Object-C的人 欢喜者: ...

  8. Td 内容不换行,超过部分自动截断,用...表示

    转载请注明来源:https://www.cnblogs.com/hookjc/ <table width="200px" style="table-layout:f ...

  9. 位运算符、|和||、&和&&的区别

    一.位运算符操作的都是整数类型 1.<<:左移,在一定范围内向左移动n位,相当于乘以2的n次幂 左移不管是正数还是负数,都是在后面添0: 2.>>:右移,在一定范围内向右移动n ...

  10. 简单实现Tabbar的隐藏显示动画 By H罗

    简单实现Tabbar的隐藏显示动画 Hide Tabbar Controller with Animation - (void)setTabBarVisible:(BOOL)visible anima ...