目录
八、前台线程和后台线程
九、线程参数的传递
十、线程中的 lock 关键字
十一、Monitor类锁定
结束
八、前台线程和后台线程
默认情况下,显式创建的线程是前台线程,通过手动的设置 Thread 类的属性 IsBackground = true 来指示当前线程为一个后台线程。
前台线程和后台线程最主要的区别是,所有的前台线程执行完成后,后台线程会自动停止工作。
注意事项:
不管是前台线程还是后台线程,如果线程内出现了异常,都会导致进程的终止。
托管线程池中的线程都是后台线程,使用 new Thread 方式创建的线程默认都是前台线程。
代码:
using System;
using System.Threading;
namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var sample1 = new ThreadSample(10);
            var sample2 = new ThreadSample(20);
            var thread1 = new Thread(sample1.CountNum);
            thread1.Name = "thread1";
            var thread2 = new Thread(sample1.CountNum);
            thread2.Name = "thread2";
            thread2.IsBackground = true;
            thread1.Start();
            thread2.Start();
            Console.ReadKey();
        }
    }
    class ThreadSample
    {
        private readonly int iterations;
        public void CountNum()
        {
            for (int i = 0; i < iterations; i++)
            {
                Thread.Sleep(500);
                Console.WriteLine("线程名:{0}, i:{1}", Thread.CurrentThread.Name, i);
            }
        }
        public ThreadSample(int iterations)
        {
            this.iterations = iterations;
        }
    }
}运行:

在上面的代码中可以看到定义如下
var sample1 = new ThreadSample(10);
var sample2 = new ThreadSample(20);线程2的执行次数是20次,但是在打印中只执行了9次,线程2执行到第9次后,就停止运行了,因为这时候,前台线程已经执行完毕,后台线程也就跟随着一起结束了。
九、线程参数的传递
在线程的 Start 方法中,其实还有一个重载函数的,可以向线程执行的方法中传递一个参数,参考微软官方的代码:
[MethodImpl(MethodImplOptions.NoInlining)]
[HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
public void Start(object parameter)
{
    if (m_Delegate is ThreadStart)
    {
        throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ThreadWrongThreadStart"));
    }
    m_ThreadStartArg = parameter;
    StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
    Start(ref stackMark);
}可以看到,参数的类型为 object 类型,下面就看看 start 方法中的这个参数的用途吧
using System;
using System.Threading;
namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thread = new Thread(Test);
            thread.Start(10);
            Console.ReadKey();
        }
        static void Test(object obj)
        {
            int len = (int)obj;
            for (int i = 0; i < len; i++)
            {
                Console.WriteLine(i);
                Thread.Sleep(500);
            }
        }
    }
}运行:

将上面的方法换为委托,Lambda 表达式的写法,效果一样
using System;
using System.Threading;
namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thread = new Thread(delegate (object obj)
            {
                int len = (int)obj;
                for (int i = 0; i < len; i++)
                {
                    Console.WriteLine(i);
                    Thread.Sleep(500);
                }
            });
            thread.Start(10);
            Console.ReadKey();
        }
    }
}
十、线程中的 lock 关键字
lock 往往用在多线程的开发中,如果有几个线程,同时调用一个方法,就会出现这个方法轮流执行的情况,这可能会导致部分数据混乱,如果想这几个线程按顺序执行,则需要用到 lock。
下面是 lock 的一些注意点:
1.lock 只对多线程有效,对单线程无效,单线程 lock 不会导致死锁。
 2.不推荐使用 lock(this),因为在它外部也可以访问它。
 3.不应该使用 lock(string(类型)),因为 string 在内存分配上是重用的,可能会导致冲突。
 4.lock 中包含的代码最好不要太多,因为在这里是单线程运行的。
 5. .net 提供了一些线程安全的集合类,使用这些集合不需要用到 lock。
 6.在可以使用数据分拆的方法来使用多线程时,最好使用数据分拆而不使用 lock。
 7.lock 的对象应该是 private static readonly object Object_Lock = new object();
  
下面的代码,其实在上面的案例中用到了多次
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thread1 = new Thread(Test);
            thread1.Name = "thread1";
            thread1.Start();
            Thread thread2 = new Thread(Test);
            thread2.Name = "thread2";
            thread2.Start();
            Console.ReadKey();
        }
        static int count = 0;
        static void Test()
        {
            while (count < 20)
            {
                count++;
                Console.WriteLine("当前线程:{0},次数:{1}", Thread.CurrentThread.Name, count);
                Thread.Sleep(50);
            }
        }
    }
}运行:

从结果可以看到,方法是由这两个线程轮流执行的,比较混乱,如果线程执行的方法中有全局变量,那么有可能导致计算结果是不对的,下面,我们将代码更改一下
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thread1 = new Thread(Test);
            thread1.Name = "thread1";
            thread1.Start();
            Thread thread2 = new Thread(Test);
            thread2.Name = "thread2";
            thread2.Start();
            Console.ReadKey();
        }
        private static readonly object obj = new object();
        private static int thread1Count = 0;
        private static int thread2Count = 0;
        static void Test()
        {
            lock (obj)
            {
                while (true)
                {
                    if (Thread.CurrentThread.Name == "thread1")
                        thread1Count++;
                    if (Thread.CurrentThread.Name == "thread2")
                        thread2Count++;
                    Console.WriteLine("线程:{0} thread1Count:{1}, thread2Count:{2}", Thread.CurrentThread.Name, thread1Count, thread2Count);
                    Thread.Sleep(50);
                    if (thread1Count > 5)
                    {
                        thread1Count = 0;
                        break;
                    }
                    if (thread2Count > 5)
                    {
                        thread2Count = 0;
                        break;
                    }
                }
            }
        }
    }
}thread1Count = 0 是防止线程1执行完后,线程2执行时,依然满足条件,而直接退出了循环,所以,我将 thread1Count 设置为 0 了。
运行

从打印的结果来看,现在不再是两个线程轮流着执行了,这就是我们想要的效果了。
十一、Monitor类锁定
在上节中,我们使用了 lock 来锁定多线程的执行,实际上 lock 关键字是 Monitor 类用例的一个语法糖。如果我们分解使用了lock关键字的代码,将会看到它如下面代码所示:
object lockObject = new object();
bool acquiredLock = false;
try
{
    Monitor.Enter(lockObject, ref acquiredLock);
}
finally
{
    if (acquiredLock)
    {
        Monitor.Exit(lockObject);
    }
}Monitor的常用属性和方法:
- Enter(Object) 在指定对象上获取排他锁。
- Exit(Object) 释放指定对象上的排他锁。
- Pulse 通知等待队列中的线程锁定对象状态的更改。
- PulseAll 通知所有的等待线程对象状态的更改。
- TryEnter(Object) 试图获取指定对象的排他锁。
- TryEnter(Object, Boolean) 尝试获取指定对象上的排他锁,并自动设置一个值,指示是否得到了该锁。
- Wait(Object) 释放对象上的锁并阻止当前线程,直到它重新获取该锁。
案例:
using System;
using System.Threading;
namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread threadA = new Thread(ThreadMethod);
            threadA.Name = "线程A";
            Thread threadB = new Thread(ThreadMethod);
            threadB.Name = "线程B";
            threadA.Start();
            threadB.Start();
            Console.ReadKey();
        }
        private static readonly object obj = new object();
        public static void ThreadMethod()
        {
            //锁定对象
            Monitor.Enter(obj);
            try
            {
                for (int i = 1; i <= 10; i++)
                {
                    Console.Write("{0}:{1}  ", Thread.CurrentThread.Name, i);
                }
                Console.WriteLine();
            }
            finally
            {
                //释放对象
                Monitor.Exit(obj); 
            }
        }
    }
}运行:

结束
如果这个帖子对你有所帮助,欢迎 关注 + 点赞 + 留言,谢谢!
end



















