最近由于工作的需要,我花时间学习了Java中多线程部分的知识,阅读了《Java Threads 2nd Edition》,虽然第二版比较老,没有最新线程特性中介绍的部分,但是这本书中介绍的多线程知识比较基础,且非常深入,很适合初学多线程并想要深刻了解其中奥妙的程序员阅读。
读完本书后,我的第一感觉就是本书中所介绍的都非常清晰明了,尤其是书中介绍的示例程序都非常有代表性,能够透彻地反映出问题,且让人印象深刻,不愧是大师的杰作。下面就是对其中的内容进行的总结和归纳。
第一章 线程简介
线程是控制线程的缩写,就是在一个程序中与其他控制线程无关的能够独立运行的代码片段。尽管线程可以选择合作运行,但是每个线程都是独立运行的。
在java程序中,多线程有如下的特性:
- 每个线程都是从一个预先定义好的,众所周知的地方开始运行(main()方法)。
- 线程的任务是简单的,就是执行序列中的下一条语句,从其开始点,按照预定义的顺序运行。
- 每一个线程都是独立于本程序内的其他线程而执行自己的代码的。
- 线程看上去是在某种程度上并行运行的。
- 线程可以访问各种类型的数据。一方面,方法的局部变量存在于堆栈中,与其他线程区分开来。如果两个线程都去调用一个方法,则每个线程得到一个单独的此方法的局部变量拷贝。另一方面,对象和它们的实例变量可以在Java程序的线程中共享,静态变量会自动地在所有线程中共享。
为什么要使用多线程?最初,是为了编写一些可分解多任务的算法,随着图形界面的使用,线程系统可以使用户觉得程序有更好的性能,而最近线程编程可以最大化地利用那些配置了多处理器的计算机。Java中没有异步行为的概念,这也正是线程对于Java而言如此重要的主要原因。一些特殊的情况,比如非阻塞I/O,警告和定时器,独立任务,并行算法,这些都需要异步的行为,或者说异步行为,使用多线程会使得程序更加优美。
一个Java程序可以包含多个线程,当你写一个Java程序时,就已经有一个初始的线程从main()方法处开始执行了。如果想要进行非阻塞I/O处理,独立任务等操作,就必须要考虑使用一个单独的线程来完成这些任务。
第二章 Java线程API
Thread类
利用Thread类创建一个线程有两个步骤:首先,在我们的子类中使用自己希望在新的线程中运行的代码来覆盖run()方法,然后通过类的构造函数来创建我们子类的一个对象,并调用子类的start()方法来执行线程。
sleep()方法是Thread类的一部分,它使得当前线程(也就是调用sleep()方法的线程)暂停运行制定毫秒长的时间。关于停止线程,线程从run()方法返回来结束运行是终止线程的最好方法,可以设置volatile变量标志,设置标志就意味着线程要周期性地检查这个标志,那么是否有更加简洁的办法可以立即停止一个线程的运行而不是去检查其标志呢?Thread类的stop()方法可以做到这一点,但是在Java2中,这个方法已经不被推荐使用了,使用这个方法可能会导致安全异常,不能依赖这个方法。当run()方法结束后,线程会自动清理一些与终止线程相关的事情。
Runnable接口
在java中,类仅仅可以从另外一个类来继承它们的行为,这就意味着继承是“稀缺”资源,Java中使用接口解决不能多重继承的问题,我们可以实现Runnable接口来代替继承Thread的行为,再利用Thread(Runnable)的构造函数创建一个新的线程对象即可。
线程的生命周期
在调用start()方法后,Java虚拟机需要一段时间才能够真正启动线程;同样,当线程从run()方法返回后,Java虚拟机也需要一段时间才能完成清理工作,因此我们需要isAlive()方法来告诉我们一个线程是不是真正被启动并且没有真正结束。
当启动几个线程去执行长时间的运算时,主线程就有时间完成自己的工作。假设主线程结束了其他的工作,需要对其子线程的运算结果进行处理,此时就需要等待子线程执行完成才能继续工作,这种等待行为被称为线程连接。Thread类提供了join()方法来处理这个问题,在要等待的线程上执行join()方法,就可以在那个线程执行完成后得到通知。
Thread类提供了设置名字并获取一个名字的方法,使用名字的最大好处是便于调试。默认情况下,Thread类会选择一个唯一的名字,Thread-后加一个唯一的数字。
任何一个Thread类的子类,它的方法既可以被线程对象自己调用,也可以被其他线程对象调用,因此,不要假设一个线程对象的代码会仅仅被该线程表示的特定线程调用。在方法中执行Thread.currentThread()方法可以对调用的线程对象进行判断。
对于一个线程来说,在start()和stop()方法之间,isAlive()方法认定其是活动的,我们可以认为一个“活着的”线程就是活动的。当一个线程停止后,线程对象的状态被置为不可重新启动,如果我们试图通过调用已经停止的线程start()方法重新启动线程,什么都不会发生,虽然start()方法不会返回异常,但是run()方法是不会被调用的,isAlive()方法也不会返回true,一个线程实例能且只能使用一次。可以再次停止一个已经停止的线程,这是为了避免竞态条件的发生。如果对于一个已经停止很久的线程调用join()方法,就会立刻返回。
对于一个正在运行的线程来说,解除对线程对象的引用并不会引起什么副作用。为了对Thread类的currentThread()和enumerate()方法提供支持,线程系统会对所有的正在系统中运行的线程保持一个引用。因此,当一个线程没有停止运行之前,线程系统是不会对线程对象进行解除引用的,因此垃圾回收机制也不会对这些线程进行回收。
第三章 同步技术
这一章中,我们要开始了解在线程之间共享数据引起的问题,因为线程共享数据或多或少地导致竞态条件的发生(指多个线程试图访问同一块数据),所以在线程之间共享数据常常是有限制的。
原子操作和volatile关键字
一个原子性的操作是指在执行过程中不可被中断的操作,在我们编写的程序中,原子性常常是指那些不能停留在中间状态的例程。
设置变量值的操作(除了long和double类型)是原子操作,也就是说,对读/写变量值的简单访问没有必要进行同步。然而,Java内存模型要复杂地多,线程可以在本地内存中保存变量,这个时候如果一个线程改变变量值,另外一个线程可能看不见值的变化。处理这种情况有很多办法,可以对包含控制变量的对象进行同步,或者进一步提供一个对控制变量的存取器方法,或者,可以把变量标记为volatile,这就意味着,每次用到这个变量的时候,都必须从主存中读取。
同步机制、互斥锁和synchronized关键字
Java规范提供了一种机制来特别处理这个问题。与其他的多线程系统相比,Java语言提供的synchronized关键字,允许程序员以一种互斥的方式来访问某个资源。从表面上看,同步的概念是简单的:当一个方法被定义为同步时,它就必须拥有一个被称为锁的令牌,一旦获取该锁后,就可以执行这个方法了,当运行结束后,无论是正常返回还是发生异常进行返回都会释放该锁。一个对象只有一个锁,因此如果两个线程试图调用同一个对象的某个标记为同步的方法,那么只有一个线程能够立刻调用它,另外一个线程只有等待前一个线程对该方法的调用结束后并释放锁的时候才能够调用该方法。
互斥锁也成为互斥独占锁,许多的线程系统都提供这种类型的锁作为一个同步方法。它的功能就是使得在同一时间内只允许一个线程占有一个互斥体:如果两个线程试图获取同一个互斥体,那么只有一个会成功。另外一个线程只有等待前一个线程释放互斥锁后才能获取该互斥体,并继续其操作。在Java中,系统中的每个对象都会创建一个锁。当一个方法被声明为同步时,线程只有获取到该方法的锁才能调用它,而当调用结束后,锁机制会自动释放该锁。
同步方法使得对这个方法的调用只能以线性的方式进行,这意味着当一个线程在调用这个方法时,另一个线程是不可能执行该方法的,这种机制是通过加锁来实现的。即使有两个或者多个方法同时被不同的线程调用,它们也不可能在不同的线程并行执行。锁是基于一个特定的对象,而不是一个特定的方法的。
锁的作用域就是从锁被获取到其被释放的时间。同步方法的作用域为整个方法,而使用同步块则可以更精确地控制锁的作用域。如果锁的作用域过大,则会使得程序无法运行。Java中提供了对代码块而不是整个方法进行同步的方法,使用同步块机制,可以灵活地控制锁的作用域,我们不仅可以更加精确地控制对象锁,而且可以选择要获取哪个对象的对象锁。在选择对象时要注意,同步是基于实际对象而不是对象引用的。多个变量可以引用同一个对象,变量也可以改变其值从而指向其它的对象,因此当选择一个对象锁时,我们要根据实际对象而不是其引用来考虑。作为一个原则,不要选择一个可能在锁的作用域中改变值的实例变量作为锁对象。
一个同步区域并不是盲目地在进入该区域时获取锁,在离开时释放锁。如果当前线程已经拥有了改对象锁,就没有必要去等待该锁被释放或者再次获取该锁。实际上,同步区域中的代码会直接运行。
死锁就是两个或者多个线程等待两个或者多个锁对象释放,而程序又处于一个这些锁永远无法释放的情况中。死锁问题很难被发现而且Java系统也不能够自动解决此类问题,在第八章中将要介绍通过程序设计来防止死锁的发生。
静态方法的同步
对于静态方法,当一个静态同步方法被调用时,程序会事先获取类锁(每个类都可以获取一个锁,对应的类对象的对象锁)。除了是不同类型的锁之外,这种方法和调用非静态方法其实是一样的。如果一个非静态同步方法调用一个静态同步方法,它就会拥有这两种锁,因为一个静态方法在没有对象引用的情况下是不能调用一个非静态方法的,这两种锁之间发生死锁的可能性较小。
第四章 等待和通知
wait()/notify机制
如同任何对象都有一个可以被获取和被释放的锁一样,每一个对象都提供一个等待该对象的区域。如同锁机制一样,这种机制的目的是帮助线程间通信。和同步方法一样,等待和通知机制存在于Java系统中每一个对象里。等待和通知通过方法调用来实现,而同步机制是通过增加关键字来实现。wait()/notify()机制能够工作是因为它们是Object类的方法,等待和通知机制允许一个线程通知另外一个线程某种特定的条件发生了(但并不指定条件)。事实上,等待和通知机制并不能解决同步机制可以解决的竞态条件问题,等待和通知机制必须和同步锁机制配合使用才能够避免在等待和通知机制本身中可能出现的竞态条件。
wait()方法在进入等待前会释放锁,但在wait()方法返回前会再次获取该锁,这样做是为了消除竞态条件。wait()方法得到通知后,仍然要再次验证条件是否满足,因为有可能多个线程都在等待某个条件的发生,当得到通知后,有可能其他的线程将条件进行更改,当wait()方法返回时,仅仅表示在过去的某个时刻条件被满足了,因此,还是需要将wait()方法调用放在一个循环中来。
notifyAll()方法来通知所有等待的线程
当有多个线程在等待某个通知时会有什么情况发生呢?当调用notify()时,到底哪个线程会受到通知呢?答案是不确定的,到底哪个线程会收到通知依赖于Java虚拟机的实现,调度程序和程序运行时的计时等因素。Object类提供了一个来处理有多个线程等待同一个条件的情况,使用notifyAll()可以帮助我们避免只有一个线程得到通知。该方法类似于notify()方法,但是它通知所有在该对象上等待的线程而不是其中随便的一个。通过唤醒全部的等待线程,我们就可以在程序中决定这些线程中到底哪一个应该继续运行下去。
实际上,我们是在将所有线程都唤醒之后才来决定到底哪个线程能够继续运行的,如果有大量线程处于等待状态,那么可能许多线程在被唤醒之后发现它们期望的条件并没有满足,只好再次进入等待状态,这样可能会导致低效,但是如果要详细地对唤醒的线程进行控制,就要实现一个对象数组保存每个线程的条件,而该数组作为通知到达的目标。对于这种有针对性的通知方式而言,程序员可以在效率高的优点和其复杂性之间作出选择。
使用wait(long)方法来设置超时时间
wait(long)方法支持在等待一段时间后进入超时状态,这些方法是支持外部事件的,当我们仅仅关注通知是否到达,通常不必使用这些方法。假如一个连接服务器的程序,它期望在30s内能够连接服务器否则就连接备用的。如果知道通知notify()不会到来,wait(long)和sleep(long)之间也是存在着一定的区别的,wait(long)还能释放然后再次获得锁,这也是其优势所在。
interrupt()中断方法
interrrupt()方法向指定的线程发出中断信号,如果目标线程是阻塞在与线程相关的方法中(如wait(), notify(), sleep()),则目标线程就会转到一个非阻塞状态,否则会设置一个布尔标志来指明该线程已经被中断。
在某些情况下,直接在目标线程中设置一个布尔标志来停止目标线程比发送中断信号更管用,但是interrupt()方法可以中断sleep()和wait()方法。
本章中介绍的一些同步方法(wait, notify, notifyAll, interrupt),使得我们可以能够在线程之间进行有效率的通信了,除了可以防止竞态条件之外,这些机制还使得线程不必求助于轮询或者超时就可以在线程之间通过事件或者条件来交互,虽然等待和通知机制是本章中应用最广泛的技术,但我们仍然可以中断一个线程而不管它正在做什么。
第六章 线程调度
在大多数Java程序中,线程个数都是多于该程序运行的机器所配置的CPU的个数,不是程序中的所有线程都能够运行,因此Java线程调度的主要目的是在某一时刻决定运行程序中的哪个线程。只有当一个或者多个线程在很长一段时间内都是占用CPU较多时才需要考虑Java线程调度。
用Java或其他语言编写的程序可以分为三类:占用CPU较多型;占用I/O较多型;交互型。一个程序的运行过程在不同的阶段可能属于不同的类别,从而可能属于这三种类别。
从概念上来说,Java虚拟机中的每个线程都处于下面四种状态之一:初始状态,一个线程从创建到start()方法被调用之间都是出于初始状态;可运行状态,从start()方法执行后,就进入了可运行状态;阻塞状态,当一个线程等待某个特定的事件发生时即处于阻塞状态;退出状态,当一个线程的run()方法返回或者stop()方法被调用时就是处于退出状态。
基于优先级的线程调度
在Java程序中,经常会有多个线程同时处于可运行状态。这种情况下,会从处于可运行状态的线程池中挑选出一个作为当前运行线程,因此问题的关键就是池中的哪一个线程会被挑选出来作为当前运行线程。Java实现是抢占式的,基于优先级的调度程序。Java虚拟机遵循的调度原则是:当前运行线程是所有处于可运行状态的线程中具有最高优先级的线程。通常Java虚拟机不会因为某个线程缺少运行的机会而调整其优先级,所以Java程序员要负责保证程序中的每个线程都不会产生CPU饥饿。
当一个高优先级的线程试图去获得一个低优先级线程拥有的锁时,因为高优先级被阻塞,使得它实际上以低优先级来运行,这就是优先级倒置。优先级倒置经常通过优先级继承来解决。通过优先级继承,如果一个高优先级的线程希望获取一个由低优先级线程所拥有的锁时,则该低优先级的线程就会暂时提高它的优先级,它的优先级会跟那个高优先级的线程一样。当该线程释放锁时,它的优先级就会降低到原来的级别上。使用优先级继承技术是为了使得高优先级的线程能够尽快地运行结束。
具有相同优先级的线程互相抢占的情况称为循环调度。在一个使用了循环调度的系统平台上,具有相同优先级的线程会周期性地将控制权交给其他的线程。
但是Java规范中并没有规定优先级继承以及循环调度是否得到实现,并且在不同的虚拟机上来说,基于优先级调度也并不是绝对的。要了解Java线程最终是如何被调度的,就需要理解所涉及的特定虚拟机。Java虚拟机可以分为两种不同的类型:绿色线程模型(Java虚拟机调度)和本地线程模型(运行虚拟机的操作系统调度)。
什么情况下我们需要关心线程的调度机制
当程序中有一个或者多个占用CPU较多的线程;对计算的中间结果感兴趣时;线程之间不是合作地完成一个任务,而是希望公平地完成单独的任务。
具有同样优先级的Java线程并不是自动地由循环调度算法来实行时间片调度的,事实上,什么是最好的调度机制其实是一个很困难的问题,在不同的情况下也会有着不同的答案。
在Java的API中,通过setPriority(int)和getPriority()方法可以设置和获取线程的优先级。何时使用这些基于优先级的调用?有且只有一个占用CPU较多的线程,并且对于用户而言,取得中间结果是有意义的。
suspend()方法将一个特定的线程从可运行状态转换为阻塞状态,resume()方法将一个阻塞状态的线程转换为可运行状态。这两个方法是存在着漏洞的,因此在Java2中不推荐使用这两个方法。suspend()方法的问题是它可能会导致“锁饥饿”,甚至会导致由于锁饥饿而导致整个虚拟机停机的情况,这是由于suspend()方法是不释放锁的。stop()方法也有同样的问题,在stop()方法中,不是锁会被无限期拥有,事实上当线程终止时,其锁会被释放,问题是复杂的数据结构可能会处于一个不稳定的状态。
因此这里我们的结论是一个线程不应当去终止另一个线程,线程仅仅可以终止自己(通过run()方法返回)或者挂起自己(通过调用wait()方法)。
yield()方法的有用性体现在它允许同样优先级的其它线程运行,但值得注意的是,它是一个静态方法,仅仅影响当前运行线程。
守护线程
在Java系统中存在着两种类型的线程:守护线程和用户线程。一个守护线程在很多方面和用户线程是一样的,有优先级,用同样的方法,经过同样的状态转换,对于调度而言两者是一同进行调度的。仅仅是当一个用户线程结束后,Java虚拟机才检查系统中的线程是否为守护线程,检查系统中是否还有其他的用户线程存在。如果仅仅有守护线程存在,Java虚拟机会退出,程序也会终止。setDaemon()和isDaemon()方法可以将线程设置为守护线程和获取该线程是否为守护线程,但是如果一个线程运行了就不能进行设置。默认情况下,一个由用户线程创建的线程就是用户线程,由守护线程创建的就是守护线程。
【附】Effective Java中关于线程调度的说明
第72条 不要依赖于线程调度器
任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的。
要编写健壮的、响应良好的、可移植的多线程应用程序,最好的办法是确保可运行线程的平均数量不明显多于处理器的数量,可以让每个线程做些有意义的工作,然后等待更多有意义的工作来尽量满足上面的需求。如果线程没有在做有意义的工作,就不该运行。
线程优先级是Java平台中最不可移植的特性了,同样yield()方法也一样,它根本不做实质性的工作,只是将控制权返回给它的调用者。这些设施仅仅对调度器做些暗示,线程优先级可以用来提高一个已经能够正常工作的程序的服务质量,但永远不应该用来“修正”一个原本并不能工作的程序。
第八章 和同步相关的高级主题
如何避免死锁
当编写一个要使用多线程的Java程序时,和数据同步相关的问题是程序设计中最困难的部分,而产生数据同步错误的原因与程序中事件发生顺序相关,因此很难被检测出来。
在任何线程系统中,因为线程竞争同一个锁集合而造成的死锁都是最难解决的问题。死锁是由多个类之间以一种隐秘的顺序调用其中的同步方法或者同步块而造成的,所以是很难检测的。仔细地检查代码还是唯一可能用来检测死锁的方法。避免死锁的最简单方法是遵循这样一个原则:一个同步方法永远不要调用另外一个同步方法。但是因为下面两个原因,这不是一个理想的方法:1. 这是不切实际的,很多有用的Java方法都是同步的,我们希望在自己的同步方法代码中包含这些方法;2. 要求过于严格,如果你要调用的一个同步方法不会调用另外一个同步方法,就不会产生死锁。尽管如此,如果你能遵循这个原则,你的程序就一定不会存在死锁。
还有一种经常使用的技术可以避免死锁,就是对于我们要使用的对象相关联但是更高级的对象加锁,但是这种技术产生的问题就是锁的粒度大小不合适。避免死锁的最有用原则就是确保以同样的顺序来获取锁,这也暗示了在类之间存在着一种锁的层次结构,锁层次与Java类的层次无关,它是对象的层次而不是类的层次关系。
同步块中的代码如果碰到运行时异常,Java的synchronized块会保证在退出同步块的时候会释放锁,这可以避免死锁的情况,却有可能导致其数据处于不一致的状态。解决这个问题我们可以将一些清理工作放在finally子句中,但是如果此部分产生异常,就无能为力了,正是这个问题导致stop()方法被不推荐使用,stop()是通过抛出异常来实现的,这就可能到这Java虚拟机中的一些关键资源处于不一致的状态。因此,我们不可能完全解决这个问题,是发生死锁还是出现数据可能会导致的不一致状态,程序员在两者之间需要作出权衡。
锁饥饿
如果使用了错误的调度选项,某些线程就永远不会有成为当前运行线程的机会,这会导致CPU饥饿;同样,当使用synchronized关键字来获取锁时,理论上也会发生这种情况。锁饥饿就是指一个特定的线程试图去获取一个锁,但是因为该锁一直被另外一个线程拥有,导致它永远无法获取到该锁。
获取锁的过程并不是排队的,当多个线程具有相同的优先级时,尽管有一个线程已经在等待该锁,但该过程不能防止将锁给予另外一个线程的情况发生;同时,释放锁并不会导致线程调度,当一个锁被释放时,所有在该锁上等待的线程都由等待转为可运行状态,但是并不会发生线程调度,那个释放锁的线程仍然是当前运行线程。因此,当以下条件满足时,会发生锁饥饿:多个线程竞争同一个锁;像CPU饥饿一样,只有在关心某段时间内是否会不公平地分配锁才会关注锁饥饿;这些线程都具有相同的优先级;这些线程都必须处于循环调度程序的控制之下(如果不处于控制之下就是CPU饥饿而不是锁饥饿)。解决的办法就是实现一个等待锁的队列,在锁被释放时通知队列中的第一个元素,这样就可以避免随机分配带来的锁饥饿现象。
非线程安全的类
一个可以用于非线程安全的技术就是保证只有一个线程能够访问这些类,这是一个困难的任务,JFC是Java平台上最大的类集,而且也因为其属于为数不多的几个非线程安全的类集而著名。无论何时使用这些类,都要注意只能从一个线程中访问JFC对象,这个线程通过执行任何一个监听器方法(例如actionPerformed())来响应用户的事件。所有的JFC对象都不是线程安全的,这也意味着我们不能够通过自己的线程来调用这些对象的方法,一定要通过Java虚拟机的分派机制。在JFC中包含了很多在事件分派线程中执行的方法,比如invokeLater(), invokeAndWait(), repaint()。
大多数对Swing组件的访问都是通过事件分派线程(Event-Dispatching)来调用的,因此对于大部分GUI代码都不需要使用下面列出的方法:只有在通过自己的线程来访问Swing组件时才需要调用invokeAndWait()和invokeLater()方法。要保证对于Swing组件的访问时通过事件分派线程进行的,最简单的方法就是使用invokeAndWait()方法,如果一个线程执行了这个方法,它就阻塞直到事件派发线程执行完某些代码为止。但是要注意不能在事件分派线程内部调用invokeAndWait()方法,会抛出一个异常,可以通过SwingUtilities.isEventDispatchThread()方法来检查当前线程是否在事件分派线程中,对于invokeLater()方法来说是没有这个限制的,invokeLater()方法类似于invokeAndWait(),但是它是不阻塞的,它不会等待目标对象的run()方法执行完成,所以此方法是不适合从JFC对象中去获取数据,但是可以用来向其中设置数据。Repaint()方法是线程安全的,任何线程都可以在任意时刻调用特定组件的repaint()方法,它仅仅安排事件派发线程调用对应的paint()方法。
invokeAndWait()方法提供了一个简单的外部同步机制无能为力时处理非线程安全类的方法,我们可以类似地,通过队列来使得有且仅有一个线程可以对发生的动作进行响应。事实上,invokeAndWait()方法也是这么做的,虚拟机仅仅创建新的事件,将它们放到队列中然后等待事件派发线程处理它们。
总结&题外话
由于篇幅原因,这里就没有把本书中的示例程序都列举出来,事实上,本书的例子都非常值得一看,尤其是作者精心编写的Busiflag等小工具类,可以使我们更加了解锁机制,同步机制的运行。而且从线程安全性的角度来看,ThreadGroup线程组API非常弱,因此推荐避免使用线程组,因为它们的安全价值已经差到根本不在Java安全模型的标准工作中提及的地步,所以本书中介绍的线程组一章也没有被提及列出。
相关推荐
总结起来,“JAVA多线程编程技术PDF”涵盖了多线程的基本概念、同步机制、线程通信、死锁避免、线程池以及线程安全的集合类等内容。通过深入学习这份资料,开发者可以全面掌握Java多线程编程技术,提升程序的并发...
Linux多线程编程是现代操作系统编程的重要组成部分,尤其是在Linux环境下,多线程编程更是成为了高性能应用不可或缺的技术之一。本文档主要涉及Linux多线程编程的一些关键知识点,包括pthread线程库的使用、线程的...
Sun Microsystems公司的《Sun多线程编程指南》是一本经典的多线程编程书籍,详细阐述了多线程编程的基础理论、API接口使用以及相关的编程技巧。 在标题《Sun多线程编程指南》中,关键词“Sun”指的是Sun ...
MFC中的`CWinThread`类便是多线程编程的重要组成部分,它提供了一系列便于管理和控制线程生命周期的方法。 #### 线程同步机制 在多线程环境中,线程间的同步至关重要,以防止数据竞争和死锁等问题。Win32 API提供了...
总结来说,多线程编程是利用操作系统提供的机制,在一个进程中同时执行多个任务,以提升程序的执行效率和用户体验。理解和掌握线程的概念、创建与管理,以及同步与通信机制,是开发高效并发程序的关键。在Windows...
### Delphi多线程编程详解 #### 一、Delphi多线程编程概述 多线程编程是现代软件开发中的关键技术之一,它能够显著提升应用程序的性能和用户体验。Delphi作为一种广泛使用的编程语言,提供了丰富的多线程支持,...
线程同步是多线程编程中最关键的部分之一,用于防止数据竞争和死锁等问题。 ##### 4.1 同步工具 本节介绍了常用的同步工具及其使用方法: - **4.1.1 原子操作**:提供原子读写操作以简化同步逻辑。 - **4.1.2 内存...
Posix多线程编程总结 Posix多线程编程是指在一个程序中创建多个执行路线的技术,称为线程。每个线程都可以独立地执行任务,而不影响其他线程的执行。多线程编程可以简化异步事件的处理,提高程序的吞吐量,提高...
Java多线程编程是Java语言中的一个重要组成部分,它允许程序在单个进程内同时执行多个线程,从而实现多任务处理。在Java 5版本之前,Java对多线程的支持相对有限,编写复杂的多线程程序具有一定的挑战性。然而,从...
第二部分:多线程编程 * 使用 `CreateThread` 函数来创建线程 * 使用 `ThreadProc` 函数作为线程函数 * 使用 `HANDLE` 变量来存储线程句柄 * 使用 `WaitForSingleObject` 函数来等待线程结束 第三部分:网络编程 ...
Java多线程编程是Java语言中极其重要的一部分。从Java 5开始,Java引入了大量的新特性,显著增强了多线程编程的能力。通过学习上述各个方面,我们可以更好地理解和利用Java的并发机制,从而开发出更高效、更稳定的多...
本文主要围绕Linux下C语言实现的多线程编程进行知识点总结,涉及线程与进程的区别、多线程的优势、Pthreads API以及线程安全和死锁预防。 首先,线程和进程是操作系统中两种基本的执行单元。线程是进程内部的执行流...
多线程编程是一种允许多个线程同时执行的编程方法,旨在提高应用程序的执行效率和响应性。在本指南中,我们将详细探讨多线程编程的基础知识、设计技巧以及同步工具,并提供MacOSX平台上使用线程包的示例。本指南还将...
Java中的多线程编程则基于这种操作系统级别的并发模型。 Java虚拟机(JVM)为每一个Java应用程序启动一个进程,而在这个进程中,所有的代码执行都是通过线程来完成的。默认情况下,Java程序的main方法在一个称为...
这个【标题】"VC多线程编程[总结].pdf"涵盖了如何在Visual C++环境中进行多线程编程的关键概念。以下是根据描述和部分内容提炼出的一些关键知识点: 1. **线程参数传递**:线程函数可以通过指针参数接收数据。在...
通过上述知识点的总结,我们可以看出多线程编程在现代软件开发中的重要性。它不仅能够提高程序的执行效率和响应能力,还能够在多处理器系统中充分利用硬件资源。掌握多线程编程的相关技术和原则对于开发高性能、高...
C++的多线程编程是提高程序效率的关键技术之一,特别是在需要高吞吐量、并发性和实时性的场景中。在C++中实现多线程,我们可以利用标准库中的`<thread>`头文件以及相关的同步机制,如互斥锁、条件变量等。 1. **...
Java多线程编程是Java开发中的重要组成部分,它允许程序同时执行多个任务,提升软件的效率和响应性。本文档详细介绍了Java中的线程概念、原理以及如何在Java中创建和启动线程。 首先,线程是操作系统实现多任务处理...