【加入默语老师的私域】C#面试题
什么是依赖注入,如何实现?
依赖注入是一种设计模式。我们不是直接在另一个类(依赖类)中创建一个类的对象,而是将对象作为参数传递给依赖类的构造函数。它有助于编写松散耦合的代码,并有助于使代码更加模块化和易于测试。实现依赖注入的三种方式:
构造函数注入:这是最常用的注入类型。在构造函数注入中,我们可以将依赖项传递给构造函数。我们必须确保这里没有默认构造函数,唯一的应该是参数化构造函数。
属性注入:在某些情况下,我们需要一个类的默认构造函数,那么在这种情况下,我们可以使用属性注入。
方法注入:在方法注入中,我们只需要在方法中传递依赖即可。当整个类不需要那个依赖时,就不需要实现构造函数注入。当我们对多个对象有依赖关系时,我们不会在构造函数中传递该依赖关系,而是在需要它的函数本身中传递该依赖关系。
为什么要使用线程池?直接new个线程不是很舒服?
如果我们在方法中直接new一个线程来处理,当这个方法被调用频繁时就会创建很多线程,不仅会消耗系统资源,还会降低系统的稳定性,一不小心把系统搞崩了,就可以直接去财务那结帐了。
如果我们合理的使用线程池,则可以避免把系统搞崩的窘境。总得来说,使用线程池可以带来以下几个好处:
降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
增加线程的可管理型。线程是稀缺资源,使用线程池可以进行统一分配,调优和监控。
线程池的核心属性有哪些?
threadFactory(线程工厂):用于创建工作线程的工厂。
corePoolSize(核心线程数):当线程池运行的线程少于 corePoolSize 时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态。
workQueue(队列):用于保留任务并移交给工作线程的阻塞队列。
maximumPoolSize(最大线程数):线程池允许开启的最大线程数。
handler(拒绝策略):往线程池添加任务时,将在下面两种情况触发拒绝策略:
1)线程池运行状态不是 RUNNING;
2)线程池已经达到最大线程数,并且阻塞队列已满时。
keepAliveTime(保持存活时间):如果线程池当前线程数超过 corePoolSize,则多余的线程空闲时间超过 keepAliveTime 时会被终止。
锁有哪几种?
的分类
1.公平锁/非公平锁
2.可重入锁
3.独享锁/共享锁
4.互斥锁/读写锁
5.乐观锁/悲观锁
6.分段锁
7.偏向锁/轻量级锁/重量级锁8.自旋锁
乐观锁
所谓的乐观,实际上是相对于悲观锁来说,我们先看一下百度百科中的解释。
乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库 性能的大量开销, 特别是对长事务而言,这样的开销往往无法承受。相对悲观锁而言,乐观锁更倾向于开发运用。
悲观锁
惯例,先来看看百度百科中的解释
悲观锁,正如其名,具有强烈的独占和排他特性。
它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度, 因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性, 否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
因为悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
-
lock关键字:这是C#中最简单和最常用的锁机制,用于在代码块中获取对象的互斥锁,确保同一时间只有一个线程能够执行该代码块。lock关键字实际上是一个语法糖,它将Monitor对象进行封装,给对象加上一个互斥锁。lock锁定的对象应该是静态的引用类型(字符串除外)。
-
Monitor类:Monitor类提供了一种更灵活的同步机制,通过Monitor.Enter和Monitor.Exit方法来获取和释放对象的锁。Monitor类还提供了等待和通知的功能,可以实现更复杂的线程同步方案。
-
Mutex类:Mutex是一种操作系统级别的内核对象,用于进程间的同步。在C#中,Mutex类封装了操作系统提供的互斥体,可以用于实现跨进程的线程同步。使用Mutex时,需要成对使用WaitOne和ReleaseMutex方法,以避免死锁。
-
Semaphore类:Semaphore也是一种操作系统级别的同步原语,用于控制同时访问共享资源的线程数量。Semaphore类允许指定一个计数器,表示可访问共享资源的线程数量,适用于一些限流的场景。
-
AutoResetEvent和ManualResetEvent类:这两种类是基于事件的同步原语,用于线程间的信号通知和同步。它们允许一个或多个线程等待另一个线程发送信号,然后继续执行。
-
SpinLock:这是一种内核模式锁,自旋锁是“原地等待”的方式解决资源冲突的,即线程会不停地检查锁是否可用(忙等待),直到获取到锁为止。自旋锁适用于锁定时间极短的场景,可以避免线程的上下文切换,但长时间持有会导致CPU资源的浪费。
选择合适的锁类型的重要性:
选择合适的锁类型对于避免性能下降和死锁非常重要。例如,自旋锁适用于锁定时间极短的场景,而互斥锁和监视锁则适用于需要长时间保护的代码段。此外,Mutex和Semaphore适用于进程间或资源数量控制的场景
在C#中,使用多线程可以通过System.Threading
命名空间下的Thread
类来实现。以下是一个简单的多线程示例,它创建了两个线程,每个线程打印出自己的名字和当前线程ID:
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread thread1 = new Thread(ThreadProc);
thread1.Name = "Thread1";
Thread thread2 = new Thread(ThreadProc);
thread2.Name = "Thread2";
thread1.Start();
thread2.Start();
Console.WriteLine("Main thread ends.");
}
static void ThreadProc()
{
Console.WriteLine($"{Thread.CurrentThread.Name} is running, thread ID: {Thread.CurrentThread.ManagedThreadId}");
}
}
在这个例子中,ThreadProc
是一个线程执行的方法,每次调用都会打印出线程的名字和当前线程ID。thread1
和thread2
是两个通过调用ThreadProc
方法创建的线程。每个线程都有自己的名字和ID,这些信息通过Thread.CurrentThread.Name
和Thread.CurrentThread.ManagedThreadId
属性获取。
请注意,在实际应用中,应该避免在多个线程间共享资源,除非有适当的同步机制,否则可能会导致竞态条件和不一致的状态。
在C#中,设置线程的优先级可以使用Thread类的Priority属性。ThreadPriority枚举定义了五个级别:
-
Lowest
-
BelowNormal
-
Normal
-
AboveNormal
-
Highest
以下是设置线程优先级的示例代码:
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread thread = new Thread(Run);
// 设置线程优先级为最低
thread.Priority = ThreadPriority.Lowest;
// 启动线程
thread.Start();
}
static void Run()
{
// 线程体
// 此处为了演示,简单地打印线程优先级
Console.WriteLine("线程运行,优先级: " + Thread.CurrentThread.Priority);
}
}
请注意,设置线程的优先级并不保证操作系统会按照指定的优先级来调度线程。操作系统根据线程的优先级来决定何时以及如何分配CPU时间,但最终的调度策略还受到很多其他因素的影响,例如当前系统负载、其他运行线程的优先级等。此外,线程的优先级也可能被提升以保证系统服务和应用程序的稳定性。
在C#中,可以使用System.Threading
命名空间下的Thread
类来创建和控制线程,使用System.Diagnostics
命名空间下的Process
类来创建和控制进程。
创建并启动一个新线程的示例代码:
using System.Threading;
public class ThreadExample
{
public static void Main()
{
ThreadStart ts = new ThreadStart(ThreadMethod);
Thread t = new Thread(ts);
t.Start();
}
private static void ThreadMethod()
{
// 线程执行的代码
Console.WriteLine("线程运行中...");
}
}
创建并启动一个新进程的示例代码:
using System.Diagnostics;
public class ProcessExample
{
public static void Main()
{
Process.Start("notepad.exe");
}
}
上述代码分别展示了如何在C#中创建并启动一个线程和进程。线程是操作系统能够进行运算调度的最小单位,而进程是运行中的程序,它可以包含一个或多个线程。在C#中,通过使用Thread
类,可以创建和控制用户模式的执行线程;通过使用Process
类,可以创建和控制一个或多个进程。