转载 线程初步了解 - <第一篇>
操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源。在这些资源之中,会包含一个称为主线程的线程数据结构,用来管理这个程序的执行状态。
在Windows操作系统下,线程的的数据结构包含以下内容:
1、线程的核心对象:主要包含线程当前的寄存器状态,当操作系统调度这个线程开始运行的时候,寄存器的状态将被加载到CPU中,重新构建线程的执行环境,当线程被调度出来的时候,最后的寄存器状态被重新保存到这里,已备下一次执行的时候使用。
2、线程环境块(Thread Environment Block,TED):是一块用户模式下的内存,包含线程的异常处理链的头部。另外,线程的局部存储数据(Thread Local Storage Data)也存在这里。
3、用户模式的堆栈:用户程序的局部变量和参数传递所使用的堆栈,默认情况下,Windows将会被分配1M的空间用于用户模式堆栈。
4、内核模式堆栈:用于访问操作系统时使用的堆栈。
在抢先式多任务的环境下,在一个特定的时间,CPU将一个线程调度进CPU中执行,这个线程最多将会运行一个时间片的时间长度,当时间片到期之后,操作系统将这个线程调度出CPU,将另外一个线程调度进CPU,我们通常称这种操作为上下文切换。
在每一次的上下文切换时,Windows将执行下面的步骤:
- 将当前的CPU寄存器的值保存到当前运行的线程数据结构中,即其中的线程核心对象中。
- 选中下一个准备运行的线程,如果这个线程处于不同的进程中,那么,还必须首先切换虚拟地址空间。
- 加载准备运行线程的CPU寄存器状态到CPU中。
公共语言运行时CLR(Common Language Runtime)是.Net程序运行的环境,它负责资源管理,并保证应用和底层操作系统之间必要的分离。
在.Net环境下,CLR中的线程需要通过操作系统的线程完成实际的工作,目前情况下,.Net直接将CLR中的线程映射到操作系统的线程进行处理和调度,所以,我们每创建一个线程将会消耗1M以上的内存空间。但未来CLR中的线程并不一定与操作系统中的线程完全对应。通过创建CLR环境下的逻辑线程,我们可能创建更加节省资源的线程,使得大量的CLR线程可以工作在少量的操作系统线程之上。
一、线程的定义
在单CPU系统的一个单位时间(time slice)内,CPU只能运行单个线程,运行顺序取决于线程的优先级别。如果在单位时间内线程未能完成执行,系统就会把线程的状态信息保持到线程的本地存储器(TLS)中,以便下次执行时恢复执行。而多线程只是系统带来的一个假象,它在多个单位时间内进行多个线程的切换,因为切换频密而且单位时间非常短暂,所以多线程被视作同时运行。
适当使用多线程能提高系统的性能,比如:在系统请求大容量的数据时使用多线程,把数据输出工作交给异步线程,使主线程保持其稳定性去处理其他问题。但需要注意一点,因为CPU需要花费不少的时间在线程的切换上,所以过多地使用多线程反而会导致性能的下降。
1、System.Threading 命名空间中的常用类
在System.Threading命名空间内提供多个方法来构建多线程应用程序,其中ThreadPool与Thread是多线程开发中最常用到的,在.NET中专门设定了一个CLR线程池专门用于管理线程的运行,这个CLR线程池正是通过ThreadPool类来管理,而Thread是管理线程的最直接方式。
| 类 | 说明 |
| AutoResetEvent | 通知正在等待的线程已发生事件 |
| ManualResetEvent | 通知正在等待的线程已发生事件 |
| Interlocked | 为多个线程共享的变量提供原子操作 |
| Monitor | 提供同步对对象的访问的机制 |
| Mutex | 一个同步基元,也可用于进程间同步 |
| Thread | 创建并控制线程,设置其优先级并获取其状态 |
| ThreadPool | 提供一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器 |
| WaitHandle | 封装等待对共享资源的独占访问的操作系统特定的对象 |
| ReadWriterLock | 读写锁 |
| Semaphore | 控制线程的访问数量 |
二、线程的优先级
理解有误,示例并不正确;
为了方便线程的管理,线程有个优先级,优先级用于决定哪个线程优先执行,在Thread对象中就是Priority属性。
优先级由低到高分别是:
| 优先级 | 说明 |
| Lowest | 可以将 Thread 安排在具有任何其他优先级的线程之后 |
| BelowNormal | 可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前 |
| Normal | 默认值。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前 |
| AboveNormal | 可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前 |
| Highest | 可以将 Thread 安排在具有任何其他优先级的线程之前 |
先来看一个优先级的示例:

class Program
{
static void Main(string[] args)
{
//新建3个线程并设定各自的优先级
Thread t1 = new Thread(Run);
t1.Priority = ThreadPriority.Lowest;
Thread t2 = new Thread(Run);
t2.Priority = ThreadPriority.Normal;
Thread t3 = new Thread(Run);
t3.Priority = ThreadPriority.Highest;
//由低到高优先级的顺序依次调用
t1.Start();
t2.Start();
t3.Start();
Console.ReadKey();
} public static void Run()
{
Console.WriteLine("我的优先级是:" + Thread.CurrentThread.Priority);
}
}

来看输出:

留意到线程是按照优先级的顺序执行的。
三、常用属性
| 常用属性 | 说明 |
| CurrentThread | 获取当前正在运行的线程 |
| IsAlive | 获取一个值,该值指示当前线程的执行状态 |
| IsBackground | 获取或设置一个值,该值指示某个线程是否为后台线程, 后台线程会随前台线程的关闭而退出 |
| IsThreadPoolThread | 获取一个值,该值指示线程是否属于托管线程池 |
| ManagedThreadId | 获取当前托管线程的唯一标识符 |
| Name | 获取或设置线程的名称 |
| Priority | 获取或设置一个值,该值指示线程的调度优先级 |
| ThreadState | 获取一个值,该值包含当前线程的状态 |
ManagedThreadId是确认线程的唯一标识符,程序在大部分情况下都是通过Thread.ManagedThreadId来辨别线程的。不能够通过Name,因为Name只是一个简单的属性,可以随便改,不能保证无重复。
常用属性示例:

class Program
{
static void Main(string[] args)
{
//新建3个线程并设定各自的优先级
Thread t1 = new Thread(Run);
t1.Priority = ThreadPriority.Normal;
t1.Start(); Console.ReadKey();
} public static void Run()
{
Thread t1 = Thread.CurrentThread; //静态属性,获取当前执行这行代码的线程
Console.WriteLine("我的优先级是:" + t1.Priority);
Console.WriteLine("我是否还在执行:" + t1.IsAlive);
Console.WriteLine("是否是后台线程:" + t1.IsBackground);
Console.WriteLine("是否是线程池线程:" + t1.IsThreadPoolThread);
Console.WriteLine("线程唯一标识符:" + t1.ManagedThreadId);
Console.WriteLine("我的名称是:" + t1.Name);
Console.WriteLine("我的状态是:" + t1.ThreadState);
}
}

输出如下:

1、前台线程与后台线程的区别
我们看到上面有个属性叫后台线程,非后台线程就叫前台线程吧,Thread.Start()启动的线程默认为前台线程,启动程序时创建的主线程一定是前台线程。应用程序与必须等到所有的前台线程执行完毕才会卸载。而当IsBackground设置为true时,就是后台线程了,当主线程执行完毕后就直接卸载,不再理会后台线程是否执行完毕。
前台与后台线程的设置必须在线程启动之前进行设置,线程启动之后就不能设置了。
Thread创建的线程是前台线程,线程池中的是后台线程。

class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(Run);
t1.IsBackground = true; //设为后台线程
t1.Start();
Console.WriteLine("不等你咯,后台线程!"); //注意这里不要Console.Readxxx();,让控制台执行完毕就自动关闭
} public static void Run()
{
Thread.Sleep(5000);
Console.WriteLine("后台线程正在执行!");
}
}

前台线程与后台线程的区别如下,上面的示例没法用图片来说明,简要说发生的情况。当t1设置为前台线程时,5秒后,控制台窗口才关闭。如果t1设置为后台线程,则窗口瞬间就关闭了。
2、ThreadState的状态
对于ThreadState的值有以下几种:
| 线程状态 | 说明 |
| Aborted | 线程已停止 |
| AbortRequested | 线程的Thread.Abort()方法已被调用,但是线程还未停止 |
| Background | 线程在后台执行,与属性Thread.IsBackground有关 |
| Running | 线程正在正常运行 |
| Stopped | 线程已经被停止 |
| StopRequested | 线程正在被要求停止 |
| Suspended | 线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行) |
| SuspendRequested | 线程正在要求被挂起,但是未来得及响应 |
| Unstarted | 未调用Thread.Start()开始线程的运行 |
| WaitSleepJoin | 线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态 |
线程在以上几种状态的切换如下:
刚刚创建的线程处于已经准备好运行,但是还没有运行的状态,称为Ready(准备)状态。在操作系统的调度之下,这个线程可以进入(Runing)运行状态。运行状态的线程可能因为时间片用完的缘故被操作系统切换出CPU,称为Suspended(暂停运行)状态,也可能在时间片还没有用完的情况下,因为等待其他优先级更高的任务,而转换到Blocked(阻塞)状态。在阻塞状态下的线程,随时可以因为再次调度而重新进入运行状态。线程还可能通过Sleep方法进入Sleep(睡眠)状态,当睡眠时间到期之后,可以再次被调度运行。处于运行状态的线程还可能被主动终止执行,直接结束;也可能因为任务已经完成,被操作系统正常结束。
四、方法
| 方法 | 说明 |
| Abort | 终止线程 |
| GetDomain | 当前线程运行在的应用程序域 |
| GetDomainID | 唯一的应用程序域标识符 |
| Interrupt | 中断处于 WaitSleepJoin 线程状态的线程 |
| Join | 阻塞调用线程,直到某个线程终止时为止 |
| ResetAbort | 取消为当前线程请求的 Abort |
| Sleep | 将当前线程阻塞指定的毫秒数 |
| SpinWait | 导致线程等待由 iterations 参数定义的时间量 |
| Start | 启动线程以按计划执行 |
1、Join串行执行
Join,串行执行,相当于ajax里面的async:false

class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(Run);
t1.Name = "t1";
t1.Start();
t1.Join(); //等待t1执行完之后,主线程再执行,线程间的关系为串行,非并行
Console.WriteLine("主线程执行这了么?");
Console.ReadKey();
} public static void Run()
{
Console.WriteLine("线程" + Thread.CurrentThread.Name + "开始执行!");
Thread.Sleep(5000);
Console.WriteLine("线程" + Thread.CurrentThread.Name + "执行完毕!");
}
}

输出:

2、Interrupt 与 Abort
Interrupt和Abort:这两个关键字都是用来强制终止线程,不过两者还是有区别的。
1、Interrupt: 抛出的是 ThreadInterruptedException 异常。
Abort: 抛出的是 ThreadAbortException 异常。
2、Interrupt:如果终止工作线程,只能管到一次,工作线程的下一次sleep就管不到了,相当于一个contine操作。如果线程正在sleep状态,则通过Interrypt跳过一次此状态也能够达到唤醒效果。
Abort:这个就是相当于一个break操作,工作线程彻底停止掉。 当然,你也已在catch(ThreadAbortException ex){...} 中调用Thread.ResetAbort()取消终止。

class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(Run);
t1.Start();
//当Interrup时,线程已进入for循环,中断第一次之后,第二次循环无法再停止
t1.Interrupt(); t1.Join();
Console.WriteLine("============================================================"); Thread t2 = new Thread(Run);
t2.Start();
//停止1秒的目的是为了让线程t2开始,否则t2都没开始就直接中止了
Thread.Sleep(1000);
//直接终止掉线程,线程被终止,自然无法输出什么!
t2.Abort(); Console.ReadKey();
} static void Run()
{
for (int i = 1; i <= 5; i++)
{
try
{
//连续睡眠5次
Thread.Sleep(2000);
Console.WriteLine("第" + i + "次Sleep!");
}
catch (Exception e)
{
Console.WriteLine("第" + i + "次Sleep被中断!" + " " + e.Message);
}
}
}
}

输出:

3、Suspend 与 Resume (慎用)
Thread.Suspend()与 Thread.Resume()是在Framework1.0 就已经存在的老方法了,它们分别可以挂起、恢复线程。但在Framework2.0中就已经明确排斥这两个方法。这是因为一旦某个线程占用了已有的资源,再使用Suspend()使线程长期处于挂起状态,当在其他线程调用这些资源的时候就会引起死锁!所以在没有必要的情况下应该避免使用这两个方法。在MSDN中,这两个方法也已被标记为已过时。
五、ThreadStart委托
ThreadStart所生成并不受线程池管理。
通过ThreadStart委托启动线程:

class Program
{
static void Main(string[] args)
{
Console.WriteLine("主线程Id是:" + Thread.CurrentThread.ManagedThreadId);
Message message = new Message();
Thread thread = new Thread(new ThreadStart(message.ShowMessage));
thread.Start();
Console.WriteLine("正在做某事......");
Console.WriteLine("主线程工作完成!"); Console.ReadKey();
} public class Message
{
public void ShowMessage()
{
string message = string.Format("异步线程Id是:{0}", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine(message);
for (int i = 0; i < 10; i++)
{
Thread.Sleep(300);
Console.WriteLine("异步线程当前循环执行到" + i);
}
}
}
}

输出:

六、ParameterizedThreadStart委托
ParameterizeThreadStart委托于ThreadStart委托非常相似,但ParameterizedThreadStart委托是面向带参数方法的。注意ParameterizedThreadStart对应方法的参数为object。

class Person
{
public Person(string name, int age){ this.Name = name;this.Age = age; }
public string Name { get; set; }
public int Age { get; set; }
} class Program
{
static void Main(string[] args)
{
//整数作为参数
for (int i = 0; i < 2; i++)
{
Thread t = new Thread(new ParameterizedThreadStart(Run));
t.Start(i);
}
Console.WriteLine("主线程执行完毕!"); //自定义类型作为参数
Person p1 = new Person("关羽", 22);
Person p2 = new Person("张飞", 21);
Thread t1 = new Thread(new ParameterizedThreadStart(RunP));
t1.Start(p1);
Thread t2 = new Thread(new ParameterizedThreadStart(RunP));
t2.Start(p2); Console.ReadKey();
} public static void Run(object i)
{
Thread.Sleep(50);
Console.WriteLine("线程传进来的参数是:" + i.ToString());
} public static void RunP(object o)
{
Thread.Sleep(50);
Person p = o as Person;
Console.WriteLine(p.Name + p.Age);
}
}

输出:

七、TimerCallback委托
TimerCallback委托专门用于定时器的操作,这个委托允许我们定义一个定时任务,在指定的间隔之后重复调用。实际的类型与ParameterizedThreadStart委托是一样的。
Timer类的构造函数定义如下:
public Timmer(TimerCallback callback,Object state,long dueTime,long period)
- Callback表示一个时间到达时执行的委托,这个委托代表的方法必须符合委托TimerCallback的定义。
- State表示当调用这个定时器委托时传递的参数。
- dutTime表示从创建定时器到第一次调用时延迟的时间,以毫秒为单位。
- Period表示定时器开始之后,每次调用之间的时间间隔,以毫秒为单位。
示例,使用TimerCallback每隔一秒钟输出一次时间:

class Program
{
static void Main(string[] args)
{
System.Threading.Timer clock = new System.Threading.Timer(ConsoleApplication1.Program.ShowTime, null, 0, 1000); Console.ReadKey();
} public static void ShowTime(object userData)
{
Console.WriteLine(DateTime.Now.ToString());
}
}

输出如下:

转载 线程初步了解 - <第一篇>的更多相关文章
- 线程初步了解 - <第一篇>
操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源.在这些资源之中,会包含一个称为主线程的线程数据结构 ...
- [转载] Java高新技术第一篇:类加载器详解
本文转载自: http://blog.csdn.net/jiangwei0910410003/article/details/17733153 首先来了解一下字节码和class文件的区别: 我们知道, ...
- [转载] Android Metro风格的Launcher开发系列第一篇
前言:从毕业到现在已经三年多了,回忆一下这三年基本上没有写过博客,总是觉得忙,没时间写,也觉得写博客没什么大用.但是看到很多大牛们都在写博客,分享自己的东西,所以嘛本着向大牛看齐,分享第一,记录第二的 ...
- 转载:eclipse 搭建SSH项目(第一篇)
第一篇:原文地址:http://blog.csdn.net/aaaaaaaa0705/article/details/6288431(虽然没有具体的例子,不过逻辑性强点,比较容易看懂) SSH框架是最 ...
- android调用第三方库——第一篇 (转载)
转自:http://blog.csdn.net/jiuyueguang/article/details/9447245 版权声明:本文为博主原创文章,未经博主允许不得转载. 0:前言: 这两天一直在研 ...
- SpringCloud 教程 | 第一篇: 服务的注册与发现Eureka(转载)
SpringCloud 教程 | 第一篇: 服务的注册与发现Eureka(Finchley版本) 转载请标明出处:http://blog.csdn.net/forezp/article/details ...
- IIS负载均衡-Application Request Route详解第一篇: ARR介绍(转载)
IIS负载均衡-Application Request Route详解第一篇: ARR介绍 说到负载均衡,相信大家已经不再陌生了,本系列主要介绍在IIS中可以采用的负载均衡的软件:微软的Applica ...
- [转载]char * 和char []的区别---之第一篇
char * 和char []的区别---之第一篇 原文地址http://blog.csdn.net/yahohi/article/details/7427724 在C/C++中,指针和数组在很多地 ...
- Python开发【第一篇】:目录
本系列博文包含 Python基础.前端开发.Web框架.缓存以及队列等,希望可以给正在学习编程的童鞋提供一点帮助!!! Python开发[第一篇]:目录 Python开发[第二篇]:初识Python ...
随机推荐
- 以前没有写笔记的习惯,现在慢慢的发现及时总结是多么的重要。 这一篇文章主要关于java多线程一些常见的疑惑点。因为讲解多线程的书籍和文章已经很多了,所以我也不好意思多说,嘻嘻嘻、大家可以去参考一些那些书籍。我这个文章主要关于实际的一些问题。同时也算是我以后复习的资料吧,。还请大家多多指教。 同时希望多结交一些技术上的朋友。谢谢。
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口. 以下就是我们常见的问题了: 1. 为什么我们不能直接调用run()方法呢? 我的理解是:线程的运行 ...
- java基础-配置java的环境变量
学习java之前首先在https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html上面下载与 ...
- phothoshop 快捷键
界面构成: 1 菜单栏 2 工具箱 3 工具属性栏 4 悬浮面板(辅助作用) CTRL + N 新建对话框(对画布进行设置) 回车确定 CTRL + O (字母) 打开对话框(选择图片) 画布的三种显 ...
- 记一次wepy里面的渲染问题(this.$apply()的使用)
今天在用wepy搞小程序的时候遇到了一个小坑,卡了我好一会,因为之前在做React,所以对wepy的了解不是特别深入,所以导致了这个问题的发生 先贴上来关键代码让大家看一看(备注之处是问题解决的方法) ...
- Chrome调试本地文件无法使用window.opener对象进行窗口间值传递
今天在百度BAE上建了个应用,svn上传后发现页面间互调有些问题,几经查询发现: (1)IE下正常的window.opener.object1.object2页面间对象访问方法在Chrome下不能使用 ...
- loadrunner 脚本开发-url解码
url解码 by:授客 QQ:1033553122 脚本结构如下: Action.c中的代码如下: int htoi(char *s) { int value = 0; int c = 0; c = ...
- 喜闻乐见-Activity生命周期
Activity的生命周期,对于Android开发者来说,再熟悉不过了.但是我们接触到的资料,绝大部分都只是谈了一些表面上的东西,例如各个回调的顺序等等.本文试图换个角度来讲解,也希望对各位读者有所帮 ...
- okhttp 的使用
①在OK HTTP 的GitHub上下载 jar 包 或者添加 grad'le依赖 OK HTTP 的地址 : https://github.com/square/okhttp ②导入jar包不想 ...
- .Net Core 2.0 生态(1).NET Standard 2.0 特性介绍和使用指南
.NET Standard 2.0 发布日期:2017年8月14日 公告原文地址 前言 早上起来.NET社区沸腾了,期待已久的.NET Core 2.0终于发布!根据个人经验,微软的产品一般在2.0时 ...
- Spark Word2Vec算法代码实现
1 import com.hankcs.hanlp.tokenizer.NLPTokenizer import org.apache.hadoop.io.{LongWritable, Text} im ...