zoukankan      html  css  js  c++  java
  • 多线程 锁机制

     当多个线程在并发的时候,难免会碰到相互冲突的事情,比如最经典的ATM机的问题,并发不可怕,可怕的是我们没有能力控制。

    线程以我的理解可以分为三种

    ① 锁。

    ② 互斥。

    ③ 信号。

     C#提供了2种手工控制的锁

    一:  Monitor类

         这个算是实现锁机制的纯正类,在锁定的临界区中只允许让一个线程访问,其他线程排队等待。主要整理为2组方法。

    1:Monitor.Enter和Monitor.Exit

             微软很照护我们,给了我们语法糖Lock,对的,语言糖确实减少了我们不必要的劳动并且让代码更可观,但是如果我们要精细的

         控制,则必须使用原生类,这里要注意一个问题就是“锁住什么”的问题,一般情况下我们锁住的都是静态对象,我们知道静态对象

         属于类级别,当有很多线程共同访问的时候,那个静态对象对多个线程来说是一个,不像实例字段会被认为是多个。

    不加锁的情况:

     1    class Program
    2 {
    3 static void Main(string[] args)
    4 {
    5 for (int i = 0; i < 10; i++)
    6 {
    7 Thread t = new Thread(Run);
    8
    9 t.Start();
    10 }
    11 }
    12
    13 //资源
    14 static object obj = new object();
    15
    16 static int count = 0;
    17
    18 static void Run()
    19 {
    20 Thread.Sleep(10);
    21
    22 Console.WriteLine("当前数字:{0}", ++count);
    23 }
    24 }

    加锁的情况:

     1     class Program
    2 {
    3 static void Main(string[] args)
    4 {
    5 for (int i = 0; i < 10; i++)
    6 {
    7 Thread t = new Thread(Run);
    8
    9 t.Start();
    10 }
    11 }
    12
    13 //资源
    14 static object obj = new object();
    15
    16 static int count = 0;
    17
    18 static void Run()
    19 {
    20 Thread.Sleep(10);
    21
    22 //进入临界区
    23 Monitor.Enter(obj);
    24
    25 Console.WriteLine("当前数字:{0}", ++count);
    26
    27 //退出临界区
    28 Monitor.Exit(obj);
    29 }
    30 }

    2:Monitor.Wait和Monitor.Pulse

     首先这两个方法是成对出现,通常使用在Enter,Exit之间。

     Wait: 暂时的释放资源锁,然后该线程进入”等待队列“中,那么自然别的线程就能获取到资源锁。

     Pulse:  唤醒“等待队列”中的线程,那么当时被Wait的线程就重新获取到了锁。

    这里我们是否注意到了两点:

    ①   可能A线程进入到临界区后,需要B线程做一些初始化操作,然后A线程继续干剩下的事情。

    ②   用上面的两个方法,我们可以实现线程间的彼此通信。

    下面举个例子来模拟两个人的对话。

     1 using System;
    2 using System.Collections.Generic;
    3 using System.Text;
    4 using System.Threading;
    5
    6 namespace Test
    7 {
    8 public class Program
    9 {
    10 public static void Main(string[] args)
    11 {
    12 LockObj obj = new LockObj();
    13
    14 //注意,这里使用的是同一个资源对象obj
    15 Jack jack = new Jack(obj);
    16 John john = new John(obj);
    17
    18 Thread t1 = new Thread(new ThreadStart(jack.Run));
    19 Thread t2 = new Thread(new ThreadStart(john.Run));
    20
    21 t1.Start();
    22 t1.Name = "Jack";
    23
    24 t2.Start();
    25 t2.Name = "John";
    26
    27 Console.ReadLine();
    28 }
    29 }
    30
    31 //锁定对象
    32 public class LockObj { }
    33
    34 public class Jack
    35 {
    36 private LockObj obj;
    37
    38 public Jack(LockObj obj)
    39 {
    40 this.obj = obj;
    41 }
    42
    43 public void Run()
    44 {
    45 Monitor.Enter(this.obj);
    46
    47 Console.WriteLine("{0}:我已进入茅厕。", Thread.CurrentThread.Name);
    48
    49 Console.WriteLine("{0}:擦,太臭了,我还是撤!", Thread.CurrentThread.Name);
    50
    51 //暂时的释放锁资源
    52 Monitor.Wait(this.obj);
    53
    54 Console.WriteLine("{0}:兄弟说的对,我还是进去吧。", Thread.CurrentThread.Name);
    55
    56 //唤醒等待队列中的线程
    57 Monitor.Pulse(this.obj);
    58
    59 Console.WriteLine("{0}:拉完了,真舒服。", Thread.CurrentThread.Name);
    60
    61 Monitor.Exit(this.obj);
    62 }
    63 }
    64
    65 public class John
    66 {
    67 private LockObj obj;
    68
    69 public John(LockObj obj)
    70 {
    71 this.obj = obj;
    72 }
    73
    74 public void Run()
    75 {
    76 Monitor.Enter(this.obj);
    77
    78 Console.WriteLine("{0}:直奔茅厕,兄弟,你还是进来吧,小心憋坏了!",
    79 Thread.CurrentThread.Name);
    80
    81 //唤醒等待队列中的线程
    82 Monitor.Pulse(this.obj);
    83
    84 Console.WriteLine("{0}:哗啦啦....", Thread.CurrentThread.Name);
    85
    86 //暂时的释放锁资源
    87 Monitor.Wait(this.obj);
    88
    89 Console.WriteLine("{0}:拉完了,真舒服。", Thread.CurrentThread.Name);
    90
    91 Monitor.Exit(this.obj);
    92 }
    93 }
    94 }

    二:ReaderWriterLock类

        先前也知道,Monitor实现的是在读写两种情况的临界区中只可以让一个线程访问,那么如果业务中存在”读取密集型“操作,就

    好比数据库一样,读取的操作永远比写入的操作多。针对这种情况,我们使用Monitor的话很吃亏,不过没关系,ReadWriterLock

    就很牛X,因为实现了”写入串行“,”读取并行“。

    ReaderWriteLock中主要用3组方法:

    <1>  AcquireWriterLock: 获取写入锁。

              ReleaseWriterLock:释放写入锁。

    <2>  AcquireReaderLock: 获取读锁。

              ReleaseReaderLock:释放读锁。

    <3>  UpgradeToWriterLock:将读锁转为写锁。

             DowngradeFromWriterLock:将写锁还原为读锁。

    下面就实现一个写操作,三个读操作,要知道这三个读操作是并发的。

     1 namespace Test
    2 {
    3 class Program
    4 {
    5 static List<int> list = new List<int>();
    6
    7 static ReaderWriterLock rw = new System.Threading.ReaderWriterLock();
    8
    9 static void Main(string[] args)
    10 {
    11 Thread t1 = new Thread(AutoAddFunc);
    12
    13 Thread t2 = new Thread(AutoReadFunc);
    14
    15 t1.Start();
    16
    17 t2.Start();
    18
    19 Console.Read();
    20 }
    21
    22 /// <summary>
    23 /// 模拟3s插入一次
    24 /// </summary>
    25 /// <param name="num"></param>
    26 public static void AutoAddFunc()
    27 {
    28 //3000ms插入一次
    29 Timer timer1 = new Timer(new TimerCallback(Add), null, 0, 3000);
    30 }
    31
    32 public static void AutoReadFunc()
    33 {
    34 //1000ms自动读取一次
    35 Timer timer1 = new Timer(new TimerCallback(Read), null, 0, 1000);
    36 Timer timer2 = new Timer(new TimerCallback(Read), null, 0, 1000);
    37 Timer timer3 = new Timer(new TimerCallback(Read), null, 0, 1000);
    38 }
    39
    40 public static void Add(object obj)
    41 {
    42 var num = new Random().Next(0, 1000);
    43
    44 //写锁
    45 rw.AcquireWriterLock(TimeSpan.FromSeconds(30));
    46
    47 list.Add(num);
    48
    49 Console.WriteLine("我是线程{0},我插入的数据是{1}。", Thread.CurrentThread.ManagedThreadId, num);
    50
    51 //释放锁
    52 rw.ReleaseWriterLock();
    53 }
    54
    55 public static void Read(object obj)
    56 {
    57 //读锁
    58 rw.AcquireReaderLock(TimeSpan.FromSeconds(30));
    59
    60 Console.WriteLine("我是线程{0},我读取的集合为:{1}",
    61 Thread.CurrentThread.ManagedThreadId, string.Join(",", list));
    62 //释放锁
    63 rw.ReleaseReaderLock();
    64 }
    65 }
    66 }

  • 相关阅读:
    ConcurrentHashMap
    Linux中如何开启8080端口供外界访问 和开启允许对外访问的端口8000
    CentOs 7 Linux系统下我的/etc/sysconfig/路径下无iptables文件
    CentOS7开启SSH服务
    Centos7下Samba服务器配置
    CentOS7(Linux)网络yum源配置
    Linux(Centos7)中配置Java环境变量
    SpringAOP-什么是面向切面编程?
    Swagger Demo
    自定义个Bean名称生成策略, 解决不同包下同名类问题/AnnotationBeanNameGenerator
  • 原文地址:https://www.cnblogs.com/ShaYeBlog/p/2660261.html
Copyright © 2011-2022 走看看