C# 学习笔记14 再战多线程Lock

C# 学习笔记14 再战多线程Lock

参考

参考:https://blog.csdn.net/weixin_39839541/article/details/111845549?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2aggregatepagefirst_rank_v2~rank_aggregation-1-111845549.pc_agg_rank_aggregation&utm_term=volatile%E8%83%BD%E4%BF%9D%E6%8C%81%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%90%97&spm=1000.2123.3001.4430

参考:https://www.cnblogs.com/liancs/p/3879252.html

参考:https://www.cnblogs.com/apsnet/archive/2012/07/08/2581475.html

参考:https://www.cnblogs.com/soundcode/p/6571167.html

参考:https://www.cnblogs.com/kzwrcom/p/5392141.html

先搞个死锁先哈哈哈。

死锁

递归死锁

除了常见的死锁还有递归死锁。

所谓递归函数就是自调用函数,在函数体内直接或间接的调用自己,即函数的嵌套是函数本身。
递归方式有两种:直接递归和间接递归,直接递归就是在函数中出现调用函数本身。间接递归,指函数中调用了其他函数,而该其他函数又调用了本函数。例如下方,下面是java代码,c#不确定会不会死锁

public class Test {  
    public void recursive(){  
        this.businessLogic();  
    }  
    public synchronized void businessLogic(){  
        System.out.println("处理业务逻辑");  
    		System.out.println("保存");
        this.recursive();  
    }  
}  

最小粒度的加速,避免死锁

public class Test {  
    public void recursive(){  
        this.businessLogic();  
    }  
    public  void businessLogic(){  
        System.out.println("处理业务逻辑");  
        this.saveToDB();  
        this.recursive();  
    }  
    public synchronized void saveToDB(){  
        System.out.println("保存到数据库");  
    }  
}  

死锁的产生条件

image-20210503145632241

死锁避免方法

加锁顺序

线程2和线程3只有在获取了锁A之后才能尝试获取锁C(译者注:获取锁A是获取锁C的必要条件)。因为线程1已经拥有了锁A,所以线程2和3需要一直等到锁A被释放。然后在它们尝试对B或C加锁之前,必须成功地对A加了锁。

按照顺序加锁是一种有效的死锁预防机制。但是,这种方式需要你事先知道所有可能会用到的锁(译者注:并对这些锁做适当的排序),但总有些时候是无法预知的。

加锁时限

问题有两个

1.如何回退(开始前flat=flase,结束的时候判断下是否完成,否则再调用一次)

2.超时和重试机制是为了避免在同一时间出现的竞争,但是当线程很多时,其中两个或多个线程的超时时间一样或者接近的可能性就会很大,因此就算出现竞争而导致超时后,由于超时时间一样,它们又会同时开始重试,导致新一轮的竞争,带来了新的问题。

死锁检测

每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。

线程A请求锁7,但是锁7这个时候被线程B持有,这时线程A就可以检查一下线程B是否已经请求了线程A当前所持有的锁。如果线程B确实有这样的请求,那么就是发生了死锁(线程A拥有锁1,请求锁7;线程B拥有锁7,请求锁1)。

可是现实生活这可能是下面这样。A要绕一大圈,问锁2,问锁3,问锁4,最后才检测到死锁的产生路径。

这里写图片描述

那么当检测出死锁时,这些线程该做些什么呢?

一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁(编者注:原因同超时类似,不能从根本上减轻竞争)。

一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。

这个算法怎么写呢(放着先吧)

涉及到图我就不会了呀,唉。

随便写一下思路吧。

1.死锁检测不能在每个执行线程中检测。拿一个单独线程来做?

2.首先写一个算法,未得锁出发->已得锁,如果可以到达。那么就死锁了。已得锁和未得锁就是数据结构问题了。

3.释放谁的锁

volatile

volatile 关键字可应用于以下类型的字段:

  • 引用类型。
  • 指针类型(在不安全的上下文中)。 请注意,虽然指针本身可以是可变的,但是它指向的对象不能是可变的。 换句话说,不能声明“指向可变对象的指针”。
  • 简单类型,如 sbytebyteshortushortintuintcharfloatbool
  • 具有以下基本类型之一的 enum 类型:bytesbyteshortushortintuint
  • 已知为引用类型的泛型类型参数。
  • IntPtrUIntPtr

原理

①操作没有用volatile来修饰字段时,各个线程都是先从主内存(堆)中复制一份数据到自己的工作内存中,然后操作自己工作内存中的数据,最后再更新到主内存中。

②当字段被volatile修饰后,各个线程操作该字段时,都是直接在主内存中进行操作的。

因为volatile能够确保可见性,所以,在一些特定情形下可以使用 volatile 变量替代锁,例如:在直接修改变量(不需先判断再修改)的情况下,多个线程同时去修改某个变量,一旦某个线程操作成功了,其他线程对这个变量的修改就立刻建立在最新的变量值上再进行修改,这样一来就避免了线程安全问题。但是,

但是,要使 volatile 变量提供理想的线程安全,必须同时满足两个条件:

①对变量的写操作不依赖于当前值;

②该变量没有包含在具有其他变量的不变式中;(不明白)

否则依旧会出现线程安全问题。

package com.springboot.model.mybatisPlus;

/**
 * @author 
 * on 2021/5/3 16:24
 */
class Window implements Runnable {
    private volatile int ticket = 100;

    public void run() {
        for (; ; ) {//通过下面的①②两个步骤我们可以发现:当不能满足“对变量的操作不依赖与当前值”,自然就会有线程安全问题。

            if (ticket > 0) {
                try {
                    Thread.sleep(100);//①多个线程同时判断到“ticket>0”,然后挂起了

                } catch (InterruptedException e) {
                    e.printStackTrace();

                }//②多个线程同时醒来,同时进行“ticket--”操作:

                System.out.println(Thread.currentThread().getName() + ":" + ticket--);

            } else {
                break;

            }

        }

    }
}
public class A03UseVolatileIsNotThreadSafe {
    public static void main(String[]args){
        Window w = new Window();

        Thread t1 = new Thread(w);

        Thread t2 = new Thread(w);

        Thread t3 = new Thread(w);

        t1.setName("窗口1");

        t2.setName("窗口2");

        t3.setName("窗口3");

        t1.start();

        t2.start();

        t3.start();

    }

}


lock

lock是一种比较好用的简单的线程同步方式,它是通过为给定对象获取互斥锁来实现同步的。它可以保证当一个线程在关键代码段的时候,另一个线程不会进来,它只能等待,等到那个线程对象被释放,也就是说线程出了临界区。用法:

public void Function() 
{
    object lockThis =new object(); 
    lock(lockThis)
    {
      //Access thread-sensitive resources.
    }
}

lock的参数必须是基于引用类型的对象,不要是基本类型像bool,int什么的,这样根本不能同步,原因是lock的参数要求是对象,如果传入int,势必要发生装箱操作,这样每次lock的都将是一个新的不 同的对象。

最好避免使用public类型或不受程序控制的对象实例,因为这样很可能导致死锁。

特别是不要使用字符串作为lock的参数,因为字符串被CLR“暂留”,就是说整个应用程序中给定的字符串都只有一个实例,因此更容易造成死锁现象。

建议使用不被“暂留”的私有或受保护成员作为参数。

其实某些 类已经提供了专门用于被锁的成员,比如Array类型提供SyncRoot,许多其它集合类型也都提供了SyncRoot。

综上,使用lock应该注意以下几点:

1.避免锁定public类型对象。

//lock(this) 范围太大,导致DoNotLockMe无法访问
class C1
    {
        private bool deadlocked = true;
        //这个方法用到了lock,我们希望lock的代码在同一时刻只能由一个线程访问
        public void LockMe(object o)
        {
            lock (this)
            {
                while (deadlocked)
                {
                    deadlocked = (bool)o;
                    Console.WriteLine("Foo: I am locked");
                    Thread.Sleep(500);
                }
            }
        }
        //所有线程都可以同时访问的方法
        public void DoNotLockMe()
        {
            Console.WriteLine("I am not locked :)");
        }
    }
//lock(syncRoot),DoNotLockMe可以访问
class C1
    {
        private bool deadlocked = true;
        private object syncRoot = new object();
        //这个方法用到了lock,我们希望lock的代码在同一时刻只能由一个线程访问
        public void LockMe(object o)
        {
            lock (syncRoot)
            {
                while (deadlocked)
                {
                    deadlocked = (bool)o;
                    Console.WriteLine("Foo: I am locked");
                    Thread.Sleep(500);
                }
            }
        }
        //所有线程都可以同时访问的方法
        public void DoNotLockMe()
        {
            Console.WriteLine("I am not locked :)");
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            DeadLock();
        }
        static void DeadLock()
        {
            C1 c1 = new C1();
            //在t1线程中调用LockMe,并将deadlock设为true(将出现死锁)
            Task t1 = Task.Factory.StartNew(c1.LockMe,true);
            Thread.Sleep(100);
            //在主线程中lock c1
            lock (c1)
            {
                //调用没有被lock的方法
                c1.DoNotLockMe();
                //调用被lock的方法,并试图将deadlock解除,可是LockMe被锁了无法在主线程解锁
                c1.LockMe(false);
            }
        }
    }

2.禁止锁定类型

这样范围就比public更大了,public只是锁定一个实例,锁定类型是锁定所有实例

真的需要锁定所有对象的时候,private static object syncRoot = new object(); 锁静态锁,这样至少不会锁没锁的方法。

3.禁止锁定字符串

4.不要锁定值类型,因为值类型总会装箱和拆箱。

微软给出的建议是:只锁定私有对象。

首先,类以外的任何代码都无法锁定MyClass.somePrivateStaticObject,因此避免了许多死锁的可能。由于死锁属于那种最难找到根源的问题,因此,避免发生死锁的可能是一件很好的事情。

其次,应用程序中只有一份MyClass.somePrivateStaticObject的副本,并且系统上运行的其他每个应用程序也只有一个副本。因此,在同一个应用程序域中的应用程序之间没有相互影响。

Interlocked

class Atomicity
{
  static int _x, _y;
  static long _z;

  static void Test()
  {
    long myLocal;
    _x = 3;             // 原子的
    _z = 3;             // 32位环境下不是原子的(_z 是64位的)
    myLocal = _z;       // 32位环境下不是原子的(_z 是64位的)
    _y += _x;           // 不是原子的 (结合了读和写操作)
    _x++;               // 不是原子的 (结合了读和写操作)
  }
}

原子操作,无需加锁。缺点,如果在循环中多次迭代使用Interlocked,就可能比在循环外使用一个锁的效率低(不过Interlocked可以实现更高的并发度)。

Interlocked的数学运算操作仅限于IncrementDecrement以及Add。如果你希望进行乘法或其它计算,**在无锁方式下可以使用CompareExchange方法(通常与自旋等待一起使用)。

while (initialValue != Interlocked.CompareExchange(ref totalValue, 
            computedValue, initialValue));

其他自己看文档去

Monitor(未研究)

Monitor类提供了与lock类似的功能,不过与lock不同的是,它 能更好的控制同步块,当调用了Monitor的Enter(Object o)方法时,会获取o的独占权,直到调用Exit(Object o)方法时,才会释放对o的独占权,可以多次调用Enter(Object o)方法,只需要调用同样次数的Exit(Object o)方法即可,Monitor类同时提供了TryEnter(Object o,[int])的一个重载方法,该方法尝试获取o对象的独占权,当获取独占权失败时,将返回false。

但使用 lock通常比直接使用 Monitor更可取,一方面是因为lock更简洁,另一方面是因为lock确保了即使受保护的代码引发异常,也可以释放基础监视器。这是通过finally中调用Exit来实现的。事实上,lock就是用Monitor类来实现的。下面两段代码是等效的:

lock(x)
 {
   DoSomething();
 }等效于

object obj =(object)x;
System.Threading.Monitor.Enter(obj);
try
{
 DoSomething();
}
finally
{
 System.Threading.Monitor.Exit(obj);
}

关于用法,请参考下面的代码:

private static objec tm_monitorObject =new object();
[STAThread]
static void Main(string[] args)
 {
 Thread thread =new Thread(new ThreadStart(Do));
 thread.Name ="Thread1";
 Thread thread2 =new Thread(new ThreadStart(Do));
 thread2.Name ="Thread2";
 thread.Start();
 thread2.Start();
 thread.Join();
 thread2.Join();
 Console.Read();
 }
static void Do()
 {
if(!Monitor.TryEnter(m_monitorObject))
 {
 Console.WriteLine("Can't visit Object "+Thread.CurrentThread.Name);
return;
 }
try
{
 Monitor.Enter(m_monitorObject);
 Console.WriteLine( "Enter Monitor "+Thread.CurrentThread.Name);
 Thread.Sleep(5000);
 }
finally
{
 Monitor.Exit(m_monitorObject);
}
}

当线程1获取了m_monitorObject对象独占权时,线程2尝试调用TryEnter(m_monitorObject),此时会由于无法获取独占权而返回false,输出信息如下:
img

另外,Monitor还提供了三个静态方法Monitor.Pulse(Object o),Monitor.PulseAll(Object o)和Monitor.Wait(Object o ) ,用来实现一种唤醒机制的同步。关于这三个方法的用法,可以参考MSDN,这里就不详述了。

Mutex(未研究)

在使用上,Mutex与上述的Monitor比较接近,不过Mutex不具 备Wait,Pulse,PulseAll的功能,因此,我们不能使用Mutex实现类似的唤醒的功能。不过Mutex有一个比较大的特点,Mutex是 跨进程的,因此我们可以在同一台机器甚至远程的机器上的多个进程上使用同一个互斥体。尽管Mutex也可以实现进程内的线程同步,而且功能也更强大,但这 种情况下,还是推荐使用Monitor,因为Mutex类是win32封装的,所以它所需要的互操作转换更耗资源。

ReaderWriterLockSlim

ReaderWriterLock的升级版,默认不支持递归调用,所有就某种意义上说避免了递归死锁。性能更好(快百分之10左右)。

定义支持单个写线程和多个读线程的锁。读写锁互斥,写锁互斥。

lock锁对象,这个锁方法块,需要搭配try finally。

备注及注意事项

1、对于同一把锁、多个线程可同时进入读模式。
2、对于同一把锁、同时只允许一个线程进入写模式。
3、对于同一把锁、同时只允许一个线程进入可升级的读模式。
4、通过默认构造函数创建的读写锁是不支持递归的,若想支持递归 可通过构造 ReaderWriterLockSlim(LockRecursionPolicy) 创建实例。
5、对于同一把锁、同一线程不可两次进入同一锁状态(开启递归后可以)
6、对于同一把锁、即便开启了递归、也不可以在进入读模式后再次进入写模式或者可升级的读模式(在这之前必须退出读模式)。
7、再次强调、不建议启用递归。
8、读写锁具有线程关联性,即两个线程间拥有的锁的状态相互独立不受影响、并且不能相互修改其锁的状态。
9、升级状态:在进入可升级的读模式 EnterUpgradeableReadLock后,可在恰当时间点通过EnterWriteLock进入写模式。
10、降级状态:可升级的读模式可以降级为读模式:即在进入可升级的读模式EnterUpgradeableReadLock后, 通过首先调用读取模式EnterReadLock方法,然后再调用 ExitUpgradeableReadLock 方法。

ReaderWriterLock

// The complete code is located in the ReaderWriterLock class topic.
using System;
using System.Threading;

public class Example
{
    static ReaderWriterLock rwl = new ReaderWriterLock();
    // Define the shared resource protected by the ReaderWriterLock.
    static int resource = 0;

    const int numThreads = 26;
    static bool running = true;

    // Statistics.
    static int readerTimeouts = 0;
    static int writerTimeouts = 0;
    static int reads = 0;
    static int writes = 0;

    public static void Do()
    {
        // Start a series of threads to randomly read from and
        // write to the shared resource.
        Thread[] t = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++)
        {
            t[i] = new Thread(new ThreadStart(ThreadProc));
            t[i].Name = new String(Convert.ToChar(i + 65), 1);
            t[i].Start();
            if (i > 10)
                Thread.Sleep(300);
        }

        // Tell the threads to shut down and wait until they all finish.
        running = false;
        for (int i = 0; i < numThreads; i++)
            t[i].Join();

        // Display statistics.
        Console.WriteLine("\n{0} reads, {1} writes, {2} reader time-outs, {3} writer time-outs.",
              reads, writes, readerTimeouts, writerTimeouts);
        Console.Write("Press ENTER to exit... ");
        Console.ReadLine();
    }

    static void ThreadProc()
    {
        Random rnd = new Random();

        // Randomly select a way for the thread to read and write from the shared
        // resource.
        while (running)
        {
            double action = rnd.NextDouble();
            if (action < .8)
                ReadFromResource(10);
/*            else if (action < .81)
                ReleaseRestore(rnd, 50);*/
            else if (action < .90)
                UpgradeDowngrade(rnd, 100);
            else
                WriteToResource(rnd, 100);
        }
    }

    // Request and release a reader lock, and handle time-outs.
    static void ReadFromResource(int timeOut)
    {
        try
        {
            rwl.AcquireReaderLock(timeOut);
            try
            {
                // It is safe for this thread to read from the shared resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ReleaseReaderLock();
            }
        }
        catch (ApplicationException)
        {
            // The reader lock request timed out.
            Interlocked.Increment(ref readerTimeouts);
        }
    }

    // Request and release the writer lock, and handle time-outs.
    static void WriteToResource(Random rnd, int timeOut)
    {
        try
        {
            rwl.AcquireWriterLock(timeOut);
            try
            {
                //Thread.Sleep(1000);
                // It's safe for this thread to access from the shared resource.
                resource = rnd.Next(500);
                Display("writes resource value " + resource);
                Interlocked.Increment(ref writes);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ReleaseWriterLock();
            }
        }
        catch (ApplicationException)
        {
            // The writer lock request timed out.
            Interlocked.Increment(ref writerTimeouts);
        }
    }

    // Requests a reader lock, upgrades the reader lock to the writer
    // lock, and downgrades it to a reader lock again.
    static void UpgradeDowngrade(Random rnd, int timeOut)
    {
        try
        {
            rwl.AcquireReaderLock(timeOut);
            try
            {
                // It's safe for this thread to read from the shared resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);

                // To write to the resource, either release the reader lock and
                // request the writer lock, or upgrade the reader lock. Upgrading
                // the reader lock puts the thread in the write queue, behind any
                // other threads that might be waiting for the writer lock.
                try
                {
                    LockCookie lc = rwl.UpgradeToWriterLock(timeOut);
                    try
                    {
                        // It's safe for this thread to read or write from the shared resource.
                        resource = rnd.Next(500);
                        Display("writes resource value " + resource);
                        Interlocked.Increment(ref writes);
                    }
                    finally
                    {
                        // Ensure that the lock is released.
                        rwl.DowngradeFromWriterLock(ref lc);
                    }
                }
                catch (ApplicationException)
                {
                    // The upgrade request timed out.
                    Interlocked.Increment(ref writerTimeouts);
                }

                // If the lock was downgraded, it's still safe to read from the resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ReleaseReaderLock();
            }
        }
        catch (ApplicationException)
        {
            // The reader lock request timed out.
            Interlocked.Increment(ref readerTimeouts);
        }
    }

    // Release all locks and later restores the lock state.
    // Uses sequence numbers to determine whether another thread has
    // obtained a writer lock since this thread last accessed the resource.
    static void ReleaseRestore(Random rnd, int timeOut)
    {
        int lastWriter;

        try
        {
            rwl.AcquireReaderLock(timeOut);
            try
            {
                // It's safe for this thread to read from the shared resource,
                // so read and cache the resource value.
                int resourceValue = resource;     // Cache the resource value.
                Display("reads resource value " + resourceValue);
                Interlocked.Increment(ref reads);

                // Save the current writer sequence number.
                lastWriter = rwl.WriterSeqNum;

                // Release the lock and save a cookie so the lock can be restored later.
                LockCookie lc = rwl.ReleaseLock();

                // Wait for a random interval and then restore the previous state of the lock.
                Thread.Sleep(rnd.Next(250));
                rwl.RestoreLock(ref lc);

                // Check whether other threads obtained the writer lock in the interval.
                // If not, then the cached value of the resource is still valid.
                if (rwl.AnyWritersSince(lastWriter))
                {
                    resourceValue = resource;
                    Interlocked.Increment(ref reads);
                    Display("resource has changed " + resourceValue);
                }
                else
                {
                    Display("resource has not changed " + resourceValue);
                }
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ReleaseReaderLock();
            }
        }
        catch (ApplicationException)
        {
            // The reader lock request timed out.
            Interlocked.Increment(ref readerTimeouts);
        }
    }

    // Helper method briefly displays the most recent thread action.
    static void Display(string msg)
    {
        Console.Write("Thread {0} {1}.       \r", Thread.CurrentThread.Name, msg);
    }
}

ReaderWriterLockSlim

// The complete code is located in the ReaderWriterLock class topic.
using System;
using System.Threading;

public class Example3
{
    static ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();
    // Define the shared resource protected by the ReaderWriterLock.
    static int resource = 0;

    const int numThreads = 26;
    static bool running = true;

    // Statistics.
    static int readerTimeouts = 0;
    static int writerTimeouts = 0;
    static int reads = 0;
    static int writes = 0;

    public static void Do()
    {
        // Start a series of threads to randomly read from and
        // write to the shared resource.
        Thread[] t = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++)
        {
            t[i] = new Thread(new ThreadStart(ThreadProc));
            t[i].Name = new String(Convert.ToChar(i + 65), 1);
            t[i].Start();
            if (i > 10)
                Thread.Sleep(300);
        }

        // Tell the threads to shut down and wait until they all finish.
        running = false;
        for (int i = 0; i < numThreads; i++)
            t[i].Join();

        // Display statistics.
        Console.WriteLine("\n{0} reads, {1} writes, {2} reader time-outs, {3} writer time-outs.",
              reads, writes, readerTimeouts, writerTimeouts);
        Console.Write("Press ENTER to exit... ");
        Console.ReadLine();
    }

    static void ThreadProc()
    {
        Random rnd = new Random();

        // Randomly select a way for the thread to read and write from the shared
        // resource.
        while (running)
        {
            double action = rnd.NextDouble();
            if (action < .8)
                ReadFromResource(10);
            else if (action < .90)
                UpgradeDowngrade(rnd, 100);
            else
                WriteToResource(rnd, 100);
        }
    }

    // Request and release a reader lock, and handle time-outs.
    static void ReadFromResource(int timeOut)
    {

        if (rwl.TryEnterReadLock(timeOut))
        {
            try
            {
                // It is safe for this thread to read from the shared resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ExitReadLock();
            }
        }
        else
        {
            Interlocked.Increment(ref readerTimeouts);
        }
    }

    // Request and release the writer lock, and handle time-outs.
    static void WriteToResource(Random rnd, int timeOut)
    {

        if (rwl.TryEnterWriteLock(timeOut))
        {
            try
            {
                //Thread.Sleep(1000);
                // It's safe for this thread to access from the shared resource.
                resource = rnd.Next(500);
                Display("writes resource value " + resource);
                Interlocked.Increment(ref writes);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ExitWriteLock();
            }
        }
        else
        {
            Interlocked.Increment(ref writerTimeouts);
        }
    }

    // Requests a reader lock, upgrades the reader lock to the writer
    // lock, and downgrades it to a reader lock again.
    static void UpgradeDowngrade(Random rnd, int timeOut)
    {

        if (rwl.TryEnterUpgradeableReadLock(timeOut))
        {
            try
            {
                // It's safe for this thread to read from the shared resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);

                // To write to the resource, either release the reader lock and
                // request the writer lock, or upgrade the reader lock. Upgrading
                // the reader lock puts the thread in the write queue, behind any
                // other threads that might be waiting for the writer lock.
                if (rwl.TryEnterWriteLock(timeOut)) {
                    try
                    {
                        // It's safe for this thread to read or write from the shared resource.
                        resource = rnd.Next(500);
                        Display("writes resource value " + resource);
                        Interlocked.Increment(ref writes);
                    }
                    finally
                    {
                        // Ensure that the lock is released.
                        rwl.ExitWriteLock();
                    }
                }
                else
                {
                    // The upgrade request timed out.
                    Interlocked.Increment(ref writerTimeouts);
                }

                // If the lock was downgraded, it's still safe to read from the resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ExitUpgradeableReadLock();
            }

        }
        else
        {
            Interlocked.Increment(ref readerTimeouts);
        }
    }

    // Release all locks and later restores the lock state.
    // Uses sequence numbers to determine whether another thread has
    // obtained a writer lock since this thread last accessed the resource.

    // Helper method briefly displays the most recent thread action.
    static void Display(string msg)
    {
        Console.Write("Thread {0} {1}.       \r", Thread.CurrentThread.Name, msg);
    }
}

SynchronizationAttribute(未研究)

当我们确定某个类的实例在同一时刻只能被一个线程访问时,我们可以直接将类标识成Synchronization的,这样,CLR会自动对这个类实施同步机制,实际上,这里面涉及到同步域的概念,当类按如下设计时,我们可以确保类的实例无法被多个线程同时访问
  1). 在类的声明中,添加System.Runtime.Remoting.Contexts.SynchronizationAttribute属性。
2). 继承至System.ContextBoundObject
需要注意的是,要实现上述机制,类必须继承至System.ContextBoundObject,换句话说,类必须是上下文绑定的。
一个示范类代码如下:

[System.Runtime.Remoting.Contexts.Synchronization]
public class SynchronizedClass : System.ContextBoundObject
 {

 }

MethodImplAttribute(未研究)

如果临界区是跨越整个方法的,也就是说,整个方法内部的代码都需要上锁的 话,使用MethodImplAttribute属性会更简单一些。这样就不用在方法内部加锁了,只需要在方法上面加上 [MethodImpl(MethodImplOptions.Synchronized)] 就可以了,MehthodImpl和MethodImplOptions都在命名空间System.Runtime.CompilerServices里面。但要注意这个属性会使整个方法加锁,直到方法返回,才释放锁。因此,使用上不太灵活。

各种锁概念

自旋锁

在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。

而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。

image-20210503220903901

自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。

适应性自旋锁

自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

公平锁和非公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

image-20210503220938310

如上图所示,假设有一口水井,有管理员看守,管理员有一把锁,只有拿到锁的人才能够打水,打完水要把锁还给管理员。每个过来打水的人都要管理员的允许并拿到锁之后才能去打水,如果前面有人正在打水,那么这个想要打水的人就必须排队。管理员会查看下一个要去打水的人是不是队伍里排最前面的人,如果是的话,才会给你锁让你去打水;如果你不是排第一的人,就必须去队尾排队,这就是公平锁。

但是对于非公平锁,管理员对打水的人没有要求。即使等待队伍里有排队等待的人,但如果在上一个人刚打完水把锁还给管理员而且管理员还没有允许等待队伍里下一个人去打水时,刚好来了一个插队的人,这个插队的人是可以直接从管理员那里拿到锁去打水,不需要排队,原本排队等待的人只能继续等待。如下图所示:

image-20210503220954606

可重入锁

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

独享锁 VS 共享锁

读写锁中,读锁是共享锁,写锁是独享锁,读写锁互斥

下一章线程通信预告

同步事件这些

https://www.cnblogs.com/binfire/p/5045032.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值