• 线程安全(ThreadSafety)


    这节讲一下线程安全的例子,以及如何解决线程安全问题。

        上节提到了线程安全的问题,说了一个例子,1000个人抢100张票,这节就从此案例着手,下面先看一下代码实现:

    private static int tickets = 100;
    static void Main(string[] args)
    {
        Thread thread = BuyTicket();
        Thread thread2 = BuyTicket();
        Thread thread3 = BuyTicket();
        thread.Name = "Thread1";
        thread2.Name = "Thread2";
        thread3.Name = "Thread3";
        thread.Start();
        thread2.Start();
        thread3.Start();
        Thread.Sleep(5000);
    }
    private static Thread BuyTicket()
    {
        Thread thread = new Thread(() =>
          {
              while (tickets > 0)
              {
                    Console.WriteLine($"{Thread.CurrentThread.Name}---------------->买了一张票,票号为:{tickets}");
                    tickets--;
              }
          });
        thread.IsBackground = true;
        return thread;
    }

     现有三个线程,同时访问共享资源tickets ,我们先来看一下运行结果:

    100卖出了三次,这就是很明显的线程安全问题,也就是说,他们都同时进入到了while块中,同时拿到了tickets为100的值,所以我们解决线程安全问题,就要从此处下手,让线程访问共享数据的时候,同一时刻只能有一个线程去访问。

        lock锁

        解决线程安全的方法就是加锁(同步锁,互斥锁),现在将代码改一下,使其线程安全:

    private static object o = new object();
    private static int tickets = 100;
    static void Main(string[] args)
    {
        Thread thread = BuyTicket();
        Thread thread2 = BuyTicket();
        Thread thread3 = BuyTicket();
        thread.Name = "Thread1";
        thread2.Name = "Thread2";
        thread3.Name = "Thread3";
        thread.Start();
        thread2.Start();
        thread3.Start();
        Thread.Sleep(5000);
    }
    private static Thread BuyTicket()
    {
        Thread thread = new Thread(() =>
          {
              while (true)
              {
                  lock (o)
                  {
                      if (tickets > 0)
                      {
                          Console.WriteLine($"{Thread.CurrentThread.Name}---------------->买了一张票,票号为:{tickets}");
                          tickets--;
                      }
                  }
              }
          });
        thread.IsBackground = true;
        return thread;
    }

    while块中,我加上了一个lock块,它需要一个Object类型的参数作为同步对象,被lock块包住的代码,在同一时间只能有一个线程访问,看一下运行结果(方便查看,我将数量改为了30):

    可以看到,线程安全问题已经解决。我们再来看一下同步对象:

     lock (object obj){}

      lock块,它需要一个object类型的参数作为同步对象,也就是说,线程走到这里,会先看看这个同步对象是不是被占用着,如未被占用,则进入,否则线程阻塞,直到同步对象被解除占用,注意,多个线程,要使用一个同步对象,不然,一个线程访问一个单独的同步对象,那跟没加锁一样,另外,根据多态性,这个同步对象可以是任意对象,因为object是所有类的父类,但是string类型不可用,这点要注意。

    Monitor锁

        monitor锁的用法跟lock差不多,请看如下代码:

    while (true)
    {
        Monitor.Enter(o);
        if (tickets > 0)
        {
            Console.WriteLine($"{Thread.CurrentThread.Name}---------------->买了一张票,票号为:{tickets}");
            tickets--;
        }
        Monitor.Exit(o);
    }

    monitor将代码块改为了enter和exit两个方法,也是需要同步对象。

    Mutex互斥锁

        互斥锁是一个互斥的同步对象,同一时间有且仅有一个线程可以获取它。跟monitor一样,也是通过两个方法控制的,具体用法请看下面的代码:

    private static Mutex mutex = new Mutex();
    private static Thread BuyTicket3()
    {
        Thread thread = new Thread(() =>
          {
              while (true)
              {
                  mutex.WaitOne();//等待
                  if (tickets > 0)
                  {
                      Console.WriteLine($"{Thread.CurrentThread.Name}---------------->买了一张票,票号为:{tickets}");
                      tickets--;
                  }
                  mutex.ReleaseMutex();//解除
              }
          });
        thread.IsBackground = true;
        return thread;
    }

    死锁

        如果滥用线程锁,容易出现死锁的问题,什么是死锁呢?比如有两个线程T1,T2,它们共用两个同步锁L1,L2,T1先走L1,T2先走L2,T1下一步走L2,T2下一步走l1,这样这两个线程各种握着对方的下一步锁,一直阻塞最后谁也走不了。或者使用像monitor这样的锁,突然出现异常,Exit方法来不及执行,也会死锁,其它的线程也会一直阻塞。下面来演示一下:

     private static Thread BuyTicket2()
    {
        Thread thread = new Thread(() =>
          {
              try
              {
                  while (true)
                  {
                      Monitor.Enter(o);
                      throw new Exception("THREAD DEAD!");
                      if (tickets > 0)
                      {
                          Console.WriteLine($"{Thread.CurrentThread.Name}---------------->买了一张票,票号为:{tickets}");
                          tickets--;
                      }
                      Monitor.Exit(o);
                  }
              }
              catch{}          
          });
        thread.IsBackground = true;
        return thread;
    }

     运行结果如下:

     因为一开始线程就直接死锁,其它的线程无法继续执行,会一直阻塞。

    这是我的公众号二维码,获取最新文章,请关注此号

  • 相关阅读:
    AQS笔记二 ---- 使用AQS自定义锁
    AQS笔记一 --- 源码分析
    ElasticSearch(二十一)正排和倒排索引
    ElasticSearch(二十)定位不合法的搜索及其原因
    ElasticSearch(十八)初识分词器
    ElasticSearch(十七)初识倒排索引
    还在用 kill -9 停机?这才是最优雅的姿势(转)
    一文快速搞懂MySQL InnoDB事务ACID实现原理(转)
    你真的理解零拷贝了吗?(转)
    关于分布式事务的读书笔记
  • 原文地址:https://www.cnblogs.com/charlesmvp/p/13456969.html
Copyright © 2020-2023  润新知