线程安全(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;
}
运行结果如下:

因为一开始线程就直接死锁,其它的线程无法继续执行,会一直阻塞。
这是我的公众号二维码,获取最新文章,请关注此号

线程安全(ThreadSafety)的更多相关文章
- Python与数据库[1] -> 数据库接口/DB-API[0] -> 通用标准
数据库接口 / DB-API 在Python中,数据库是通过适配器(Adaptor)来连接访问数据库的,适配器通常与数据库客户端接口(通常为C语言编写)想连接,而不同的适配器都会尽量满足相同的DB-A ...
- 可重入与线程安全(Reentrancy and Thread-Safety)
http://blog.csdn.net/zzwdkxx/article/details/49338067 http://blog.csdn.net/zzwdkxx/article/details/4 ...
- 三种线程不安全现象描述(escaped state以及hidden mutable state)
hidden mutable state和escaped state是两种线程不安全问题:两者原因不同,前者主要是由于类成员变量中含有其他对象的引用,而这个引用是immutable的:后者是成员方法的 ...
- ArrayList,Vector线程安全性测试
import java.util.ArrayList; import java.util.List; //实现Runnable接口的线程 public class HelloThread implem ...
- 3、flask之基于DBUtils实现数据库连接池、本地线程、上下文
本篇导航: 数据库连接池 本地线程 上下文管理 面向对象部分知识点解析 1.子类继承父类__init__的三种方式 class Dog(Animal): #子类 派生类 def __init__(se ...
- C#线程安全类型
1.IProducerConsumerCollection (线程安全接口) 此接口的所有实现必须都启用此接口的所有成员,若要从多个线程同时使用. using System; using System ...
- flask之基于DBUtils实现数据库连接池、本地线程、上下文
本篇导航: 数据库连接池 本地线程 上下文管理 面向对象部分知识点解析 1.子类继承父类__init__的三种方式 class Dog(Animal): #子类 派生类 def __init__(se ...
- Java并发编程:什么是线程安全,以及并发必须知道的几个概念
废话 众所周知,在Java的知识体系中,并发编程是非常重要的一环,也是面试的必问题,一个好的Java程序员是必须对并发编程这块有所了解的.为了追求成为一个好的Java程序员,我决定从今天开始死磕Jav ...
- Writing Reentrant and Thread-Safe Code(译:编写可重入和线程安全的代码)
Writing Reentrant and Thread-Safe Code 编写可重入和线程安全的代码 (http://www.ualberta.ca/dept/chemeng/AIX-43/sha ...
随机推荐
- 有必要了解的大数据知识(一) Hadoop
前言 之前工作中,有接触到大数据的需求,虽然当时我们体系有专门的大数据部门,但是由于当时我们中台重构,整个体系的开发量巨大,共用一个大数据部门,人手已经忙不过来,没法办,为了赶时间,我自己负责的系统的 ...
- Java8的新特性--Optional
目录 Optional 一.Optional类是什么? 二.Optional类常用的方法 1. 创建Optional实例 1.1 Optional.of(T) 1.2 Optional.empty() ...
- Paperfolding HDU - 6822
传送门:https://vjudge.net/problem/HDU-6822 题意:给你一张无限的纸有四种折叠方式,并且在n次折叠后减两刀问最后纸张数量的数学期望. 思路:我们要得到一个通项公式对于 ...
- 【LeetCode】10.Regular Expression Matching(dp)
[题意] 给两个字符串s和p,判断s是否能用p进行匹配. [题解] dp[i][j]表示s的前i个是否能被p的前j个匹配. 首先可以分成3大类情况,我们先从简单的看起: (1)s[i - 1] = p ...
- c++ 反汇编 局部静态变量
vs2017下测试 34: for (int i = 0; i < 5; i++) 0029734E C7 45 F8 00 00 00 00 mov dword ptr [ebp-8],0 0 ...
- 提高ASP.NET Web应用性能的24种方法和技巧
那性能问题到底该如何解决?以下是应用系统发布前,作为 .NET 开发人员需要检查的点. 1.debug=「false」 当创建 ASP.NET Web应用程序,默认设置为「true」.开发过程中,设置 ...
- VIM 编辑器操作详解
1 vim 使用介绍 1.1 vim 安装 # CentOS 安装: yum install -y vim # Ubuntu 安装: sudu apt-get install vim 安装完成后,可使 ...
- Echarts4.x雷达图如何展示一维数据?
最近做的项目其中一个功能是画雷达图,鼠标滑过雷达图的拐点,展示该维相关数据,并且需要显示雷达图的刻度. 但是我发现单纯的雷达图似乎没办法展示一维数据. 我总结了一下,关于画雷达图,我遇到的难点有三个: ...
- WordPress的SEO优化技巧
世界上大约有30%的网站都是由Wordpress搭建的,因为Wordpress自身构架清晰,代码规范,且网页评论直接书写在整个页面里,能够被搜索引擎检索到,因此对搜索引擎很友好.但有时候还是会出现只被 ...
- Spring Cloud Alibaba(2)---Nacos概述
Spring Cloud Alibaba(2)---nacos概述 上一篇博客讲了有关 SpringCloudAlibaba的概述,这篇开始讲SpringCloudAlibaba组件之一---Naco ...