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

上节提到了线程安全的问题,说了一个例子,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;
}

运行结果如下:

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

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

线程安全(ThreadSafety)的更多相关文章

  1. Python与数据库[1] -> 数据库接口/DB-API[0] -> 通用标准

    数据库接口 / DB-API 在Python中,数据库是通过适配器(Adaptor)来连接访问数据库的,适配器通常与数据库客户端接口(通常为C语言编写)想连接,而不同的适配器都会尽量满足相同的DB-A ...

  2. 可重入与线程安全(Reentrancy and Thread-Safety)

    http://blog.csdn.net/zzwdkxx/article/details/49338067 http://blog.csdn.net/zzwdkxx/article/details/4 ...

  3. 三种线程不安全现象描述(escaped state以及hidden mutable state)

    hidden mutable state和escaped state是两种线程不安全问题:两者原因不同,前者主要是由于类成员变量中含有其他对象的引用,而这个引用是immutable的:后者是成员方法的 ...

  4. ArrayList,Vector线程安全性测试

    import java.util.ArrayList; import java.util.List; //实现Runnable接口的线程 public class HelloThread implem ...

  5. 3、flask之基于DBUtils实现数据库连接池、本地线程、上下文

    本篇导航: 数据库连接池 本地线程 上下文管理 面向对象部分知识点解析 1.子类继承父类__init__的三种方式 class Dog(Animal): #子类 派生类 def __init__(se ...

  6. C#线程安全类型

    1.IProducerConsumerCollection (线程安全接口) 此接口的所有实现必须都启用此接口的所有成员,若要从多个线程同时使用. using System; using System ...

  7. flask之基于DBUtils实现数据库连接池、本地线程、上下文

    本篇导航: 数据库连接池 本地线程 上下文管理 面向对象部分知识点解析 1.子类继承父类__init__的三种方式 class Dog(Animal): #子类 派生类 def __init__(se ...

  8. Java并发编程:什么是线程安全,以及并发必须知道的几个概念

    废话 众所周知,在Java的知识体系中,并发编程是非常重要的一环,也是面试的必问题,一个好的Java程序员是必须对并发编程这块有所了解的.为了追求成为一个好的Java程序员,我决定从今天开始死磕Jav ...

  9. Writing Reentrant and Thread-Safe Code(译:编写可重入和线程安全的代码)

    Writing Reentrant and Thread-Safe Code 编写可重入和线程安全的代码 (http://www.ualberta.ca/dept/chemeng/AIX-43/sha ...

随机推荐

  1. 递归函数初步理解---python实现(汉诺塔问题)

    递归常被用来描述以自相似的方法重复事物的过程,在程序中指的是在函数定义中使用函数自身的方法. 递归是一个树结构,分为递推和回归的过程,当递推到达底部时,就会开始回归. 问题描述:A比B大两岁,B比C大 ...

  2. Java中HashMap的源码分析

    先来回顾一下Map类中常用实现类的区别: HashMap:底层实现是哈希表+链表,在JDK8中,当链表长度大于8时转换为红黑树,线程不安全,效率高,允许key或value为null HashTable ...

  3. ch1_6_5求解旋转词问题

    import java.util.Scanner; public class ch1_6_5求解旋转词问题 { public static void main(String[] args) { // ...

  4. 「HTML+CSS」--自定义加载动画【007】

    前言 Hello!小伙伴! 首先非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出- 哈哈 自我介绍一下 昵称:海轰 标签:程序猿一只|C++选手|学生 简介:因C语言结识编程,随后转入计算机 ...

  5. H5 离线存储-使用 serverWorker 实现

    H5 离线存储-使用 serverWorker 实现 离线存储所用技术 serverWorker serviceWorker 生命周期 install 事件会在 serviceWorker 注册成功时 ...

  6. Mediapipe 在RK3399PRO上的初探(二)(自定义Calculator)

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明   本文作为本人csdn blog的主站的备份.(Bl ...

  7. 超详细!!Spring5框架开发笔记

    Spring5开发教程 简介 spring特性 sping是轻量级的开源的JavaEE框架 Spring可以解决企业应用开发的复杂性 Sping两个核心的部分:IOC和AOC IOC:控制反转.把创建 ...

  8. 手把手教你搭建自己的Angular组件库 - DevUI

    摘要:DevUI 是一款面向企业中后台产品的开源前端解决方案,它倡导沉浸.灵活.至简的设计价值观,提倡设计者为真实的需求服务,为多数人的设计,拒绝哗众取宠.取悦眼球的设计.如果你正在开发 ToB 的工 ...

  9. 前端缓存API请求数据

    1. 背景 在一些项目中,有时候会出现不同模块重复请求大量相同api接口的情况,特别是在一些功能相似的后台管理页面中.以下面这几个页面为例,每次进入页面都需要请求等大量重复的下拉框数据,下拉框数据短时 ...

  10. mysql is null 和 =null 区别

    数据库中 null 表示 不可知,不确定所以 判断都用 字段 is null的方式进行判断而 = null .<> null 的判断结果,仍然是不可知,不确定,所以 不会返回任何结果.或者 ...