




免费预览已结束,剩余10页可下载查看
下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
毕业设计说明书英文文献及中文翻译Writing multithreaded Java applicationsLearn to avoid problems common in concurrent programmingAlex Roetter (aroetterCS.S), Software engineer, Teton Data Systems01 Feb 2001When you are writing graphical programs that use AWT or Swing, multithreading is a necessity for all but the most trivial programs. Thread programming poses many difficulties, and many developers often find themselves falling prey to problems such as incorrect application behavior and deadlocked applications.In this article, well explore the problems associated with multithreading and discuss solutions to the most common pitfalls.What are threads?A program or process can contain multiple threads that execute instructions according to program code. Like multiple processes that can run on one computer, multiple threads appear to be doing their work in parallel. Implemented on a multi-processor machine, they actually can work in parallel. Unlike processes, threads share the same address space; that is, they can read and write the same variables and data structures.When youre writing multithreaded programs, you must take great care that no one thread disturbs the work of any other thread. You can liken this approach to an office where the workers function independently and in parallel except when they need to use shared office resources or communicate with one another. One worker can speak to another worker only if the other worker is listening and they both speak the same language. Additionally, a worker cant use a copy machine until it is free and in a useable state (no half-completed copy jobs, paper jams, and so on). As we work through this article, youll see how you can get threads to coordinate and cooperate in a Java program much like workers in a well-behaved organization.In a multithreaded program, threads are obtained from the pool of available ready-to-run threads and run on the available system CPUs. The OS can move threads from the processor to either a ready or blocking queue, in which case the thread is said to have yielded the processor. Alternatively, the Java virtual machine (JVM) can manage thread movement - under either a cooperative or preemptive model - from a ready queue onto the processor, where the thread can begin executing its program code. Threads and the Java languageTo create a thread using the Java language, you instantiate an object of type Thread (or a subclass) and send it the start() message. (A program can send the start() message to any object that implements the Runnable interface.) The definition of each threads behavior is contained in its run() method. A run method is equivalent to main() in a traditional program: a thread will continue running until run() returns, at which point the thread dies.LocksMost applications require threads to communicate and synchronize their behavior to one another. The simplest way to accomplish this task in a Java program is with locks. To prevent multiple accesses, threads can acquire and release a lock before using resources. Imagine a lock on the copy machine for which only one worker can possess a key at a time. Without the key, use of the machine is impossible. Locks around shared variables allow Java threads to quickly and easily communicate and synchronize. A thread that holds a lock on an object knows that no other thread will access that object. Even if the thread with the lock is preempted, another thread cannot acquire the lock until the original thread wakes up, finishes its work, and releases the lock. Threads that attempt to acquire a lock in use go to sleep until the thread holding the lock releases it. After the lock is freed, the sleeping thread moves to the ready-to-run queue.In Java programming, each object has a lock; a thread can acquire the lock for an object by using the synchronized keyword. Methods, or synchronized blocks of code, can only be executed by one thread at a time for a given instantiation of a class, because that code requires obtaining the objects lock before execution. Continuing with our copier analogy, to avoid clashing copiers, we can simply synchronize access to the copier resource, allowing only one worker access at a time, as shown in the following code sample. We achieve this by having methods (in the Copier object) that modify the copier state be declared as synchronized methods. Workers that need to use a Copier object have to wait in line because only one thread per Copier object can be executing synchronized code.class CopyMachine public synchronized void makeCopies(Document d, int nCopies) /only one thread executes this at a time public void loadPaper() /multiple threads could access this at once! synchronized(this) /only one thread accesses this at a time /feel free to use shared resources, overwrite members, etc. Fine-grain locks Often, using a lock at the object level is too coarse. Why lock up an entire object, disallowing access to any other synchronized method for only brief access to shared resources? If an object has multiple resources, its unnecessary to lock all threads out of the whole object in order to have one thread use only a subset of the threads resources. Because every object has a lock, we can use dummy objects as simple locks, as shown here:class FineGrainLock MyMemberClass x, y; Object xlock = new Object(), ylock = new Object(); public void foo() synchronized(xlock) /access x here /do something here - but dont use shared resources synchronized(ylock) /access y here public void bar() synchronized(xlock) synchronized(ylock) /access both x and y here /do something here - but dont use shared resources These methods need not be synchronized at the method level by declaring the whole method with the synchronized keyword; they are using the member locks, not the object-wide lock that a synchronized method acquires.SemaphoresFrequently, several threads will need to access a smaller number of resources. For example, imagine a number of threads running in a Web server answering client requests. These threads need to connect to a database, but only a fixed number of available database connections are available. How can you assign a number of database connections to a larger number of threads efficiently? One way to control access to a pool of resources (rather than just with a simple one-thread lock) is to use what is known as a counting semaphore. A counting semaphore encapsulates managing the pool of available resources. Implemented on top of simple locks, a semaphore is a thread-safe counter initialized to the number of resources available for use. For example, we would initialize a semaphore to the number of database connections available. As each thread acquires the semaphore, the number of available connections is decremented by one. Upon consumption of the resource, the semaphore is released, incrementing the counter. Threads that attempt to acquire a semaphore when all the resources managed by the semaphore are in use simply block until a resource is free.A common use of semaphores is in solving the consumer-producer problem. This problem occurs when one thread is completing work that another thread will use. The consuming thread can only obtain more data after the producing thread finishes generating it. To use a semaphore in this manner, you create a semaphore with the initial value of zero and have the consuming thread block on the semaphore. For each unit of work completed, the producing thread signals (releases) the semaphore. Each time a consumer consumes a unit of data and needs another, it attempts to acquire the semaphore again, resulting in the value of the semaphore always being the number of units of completed work ready for consumption. This approach is more efficient than having a consuming thread wake up, check for completed work, and sleep if nothing is available.Though semaphores are not directly supported in the Java language, they are easily implemented on top of object locks. A simple implementation follows:class Semaphore private int count; public Semaphore(int n) this.count = n; public synchronized void acquire() while(count = 0) try wait(); catch (InterruptedException e) /keep trying count-; public synchronized void release() count+; notify(); /alert a thread thats blocking on this semaphore Common locking problemsUnfortunately, the use of locks brings with it many problems. Lets take a look at some common problems and their solutions:Deadlocking Deadlocking is a classic multithreading problem in which all work is incomplete because different threads are waiting for locks that will never be released. Imagine two threads, which represent two hungry people who must share one fork and knife and take turns eating. They each need to acquire two locks: one for the shared fork resource and one for the shared knife resource. Imagine if thread A acquires the knife and thread B acquires the fork. Thread A will now block waiting for the fork, while thread B blocks waiting for the knife, which thread A has. Though a contrived example, this sort of situation occurs often, albeit in scenarios much harder to detect. Although difficult to detect and hash out in every case, by following these few rules, a systems design can be free of deadlocking scenarios: Have multiple threads acquire a group of locks in the same order. This approach eliminates problems where the owner of X is waiting for the owner of Y, who is waiting for X. Group multiple locks together under one lock. In our case, create a silverware lock that must be acquired before either the fork or knife is obtained. Label resources with variables that are readable without blocking. After the silverware lock is acquired, a thread could examine variables to see if a complete set of silverware is available. If so, it could obtain the relevant locks; if not, it could release the master silverware lock and try again later. Most importantly, design the entire system thoroughly before writing code. Multithreading is difficult, and a thorough design before you start to code will help avoid difficult-to-detect locking problems. Volatile Variables The volatile keyword was introduced to the language as a way around optimizing compilers. Take the following code for example:class VolatileTest boolean flag; public void foo() flag = false; if(flag) /this could happen An optimizing compiler might decide that the body of the if statement would never execute, and not even compile the code. If this class were accessed by multiple threads, flag could be set by another thread after it has been set in the previous code, but before it is tested in the if statement. Declaring variables with the volatile keyword tells the compiler not to optimize out sections of code by predicting the value of the variable at compile time. Threads and AWT/SwingIn Java programs with GUIs that use Swing and/or the AWT, the AWT event handler runs in its own thread. Developers must be careful to not tie up this GUI thread performing time-consuming work, because it is responsible for handling user events and redrawing the GUI. In other words, the program will appear frozen whenever the GUI thread is busy. Swing callbacks, such as Mouse Listeners and Action Listeners are all notified (by having appropriate methods called) by the Swing thread. This approach means that any substantial amount of work the listeners are to do must be performed by having the listener callback method spawn another thread to do the work. The goal is to get the listener callback to return quickly, allowing the Swing thread to respond to other events.If a Swing thread is running asynchronously, responding to events and repainting the display, how can other threads modify Swing state safely? As I just mentioned, Swing callbacks run in the Swing thread; therefore, they can safely modify Swing data and draw to the screen.But what about other changes that dont happen as a result of a Swing callback? Having a non-Swing thread modify Swing data is not thread safe. Swing provides two methods to solve this problem: invokeLater() and invokeAndWait(). To modify the Swing state, simply call either of these methods, passing a Runnable object that does the proper work. Because Runnable objects are usually their own threads, you might think that this object is spawned off as a thread to execute. In fact, this is not the case, as that too would not be thread safe. Instead, Swing puts this object in a queue and executes its run method at an arbitrary point in the future. This makes the changes to Swing state thread safe.WrapupThe design of the Java language makes multithreading essential for all but the simplest applets. In particular, IO and GUI programming both require multithreading to provide a seamless experience for the user. By following the simple rules outlined in this article, as well as thoroughly designing a system - including its access to shared resources - before youve begun programming, you can avoid many common and difficult-to-detect threading pitfalls.编写多线程的 Java 应用程序如何避免当前编程中最常见的问题Alex Roetter (aroetterCS.S), Teton Data Systems 的软件工程师2001 年 2 月 01 日几乎所有使用 AWT 或 Swing 编写的画图程序都需要多线程。但多线程程序会造成许多困难,刚开始编程的开发者常常会发现他们被一些问题所折磨,例如不正确的程序行为或死锁。在本文中,我们将探讨使用多线程时遇到的问题,并提出那些常见陷阱的解决方案。线程是什么?一个程序或进程能够包含多个线程,这些线程可以根据程序的代码执行相应的指令。多线程看上去似乎在并行执行它们各自的工作,就像在一台计算机上运行着多个处理机一样。在多处理机计算机上实现多线程时,它们确实 可以并行工作。和进程不同的是,线程共享地址空间。也就是说,多个线程能够读写相同的变量或数据结构。 编写多线程程序时,你必须注意每个线程是否干扰了其他线程的工作。可以将程序看作一个办公室,如果不需要共享办公室资源或与其他人交流,所有职员就会独立并行地工作。某个职员若要和其他人交谈,当且仅当该职员在“听”且他们两说同样的语言。此外,只有在复印机空闲且处于可用状态(没有仅完成一半的复印工作,没有纸张阻塞等问题)时,职员才能够使用它。在这篇文章中你将看到,在 Java 程序中互相协作的线程就好像是在一个组织良好的机构中工作的职员。在多线程程序中,线程可以从准备就绪队列中得到,并在可获得的系统 CPU 上运行。操作系统可以将线程从处理器移到准备就绪队列或阻塞队列中,这种情况可以认为是处理器“挂起”了该线程。同样,Java 虚拟机 (JVM) 也可以控制线程的移动在协作或抢先模型中从准备就绪队列中将进程移到处理器中,于是该线程就可以开始执行它的程序代码。线程和 Java 语言为了使用 Java 语言创建线程,你可以生成一个 Thread 类(或其子类)的对象,并给这个对象发送 start() 消息。(程序可以向任何一个派生自 Runnable 接口的类对象发送 start() 消息。)每个线程动作的定义包含在该线程对象的 run() 方法中。run 方法就相当于传统程序中的 main() 方法;线程会持续运行,直到 run() 返回为止,此时该线程便死了。 上锁大多数应用程序要求线程互相通信来同步它们的动作。在 Java 程序中最简单实现同步的方法就是上锁。为了防止同时访问共享资源,线程在使用资源的前后可以给该资源上锁和开锁。假想给复印机上锁,任一时刻只有一个职员拥有钥匙。若没有钥匙就不能使用复印机。给共享变量上锁就使得 Java 线程能够快速方便地通信和同步。某个线程若给一个对象上了锁,就可以知道没有其他线程能够访问该对象。即使在抢占式模型中,其他线程也不能够访问此对象,直到上锁的线程被唤醒、完成工作并开锁。那些试图访问一个上锁对象的线程通常会进入睡眠状态,直到上锁的线程开锁。一旦锁被打开,这些睡眠进程就会被唤醒并移到准备就绪队列中。在 Java 编程中,所有的对象都有锁。线程可以使用 synchronized 关键字来获得锁。在任一时刻对于给定的类的实例,方法或同步的代码块只能被一个线程执行。这是因为代码在执行之前要求获得对象的锁。继续我们关于复印机的比喻,为了避免复印冲突,我们可以简单地对复印资源实行同步。如同下列的代码例子,任一时刻只允许一位职员使用复印资源。通过使用方法(在 Copier 对象中)来修改复印机状态。这个方法就是同步方法。只有一个线程能够执行一个 Copier 对象中同步代码,因此那些需要使用 Copier 对象的职员就必须排队等候。class CopyMachine public synchronized void makeCopies(Document d, int nCopies) /only one thread executes this at a time public void loadPaper() /multiple threads could access this at once! synchronized(this) /only one thread accesses this at a time /feel free to use shared resources, overwrite members, etc. Fine-grain锁 在对象级使用锁通常是一种比较粗糙的方法。为什么要将整个对象都上锁,而不允许其他线程短暂地使用对象中其他同步方法来访问共享资源?如果一个对象拥有多个资源,就不需要只为了让一个线程使用其中一部分资源,就将所有线程都锁在外面。由于每个对象都有锁,可以如下所示使用虚拟对象来上锁:class FineGrainLock MyMemberClass x, y; Object xlock = new Object(), ylock = new Object(); public void foo() synchronized(xlock) /access x here /do something here - but dont use shared resources synchronized(ylock) /access y here public void bar() synchronized(this) /access both x and y here /do something here - but dont use shared resources 若为了在方法级上同步,不能将整个方法声明为 synchronized 关键字。它们使用的是成员锁,而不是 synchronized 方法能够获得的对象级锁。信号量通常情况下,可能有多个线程需要访问数目很少的资源。假想在服务器上运行着若干个回答客户端请求的线程。这些线程需要连接到同一数据库,但任一时刻只能获得一定数目的数据库连接。你要怎样才能够有效地将这些固定数目的数据库连接分配给大量的线程?一种控制访问一组资源的方法(除了简单地上锁之外),就是使用众所周知的信号量计数 (counting semaphore)。 信号量计数将一组可获得资源的管理封装起来。信号量是在简单上锁的基础上实现的,相当于能令线程安全执行,并初始化为可用资源个数的计数器。例如我们可以将一个信号量初始化为可获得的数据库连接个数。一旦某个线程获得了信号量,可获得的数据库连接数减一。线程消耗完资源并释放该资源时,计数器就会加一。当信号量控制的所有资源都已被占用时,若有线程试图访问此信号量,则会进入阻塞状态,直到有可用资源被释放。 信号量最常见的用法是解决“消费者生产者问题”。当一个线程进行工作时,若另外一个线程访问同一共享变量,就可能产生此问题。消费者线程只能在生产者线程完成生产后才能够访问数据。使用信号量来解决这个问题,就需要创建一个初始化为零的信号量,从而让消费者线程访问此信号量时发生阻塞。每当完成单位工作时,生产者线程就会向该信号量发信号(释放资源)。每当消费者线程消费了单位生产结果并需要新的数据单元时,它就会试图再次获取信号量。因此信号量的值就总是等于生产完毕可供消费的数据单元数。这种方法比采用消费者线程不停检查是否有可用数据单元的方法要高效得多。因为消费者线程醒来后,倘若没有找到可用的数据单元,就会再度进入睡眠状态,这样的操作系统开销是非常昂贵的。尽管信号量并未直接被 Java 语言所支持,却很容易在给对象上锁的基础上实现。一个简单的实现方法如下所示:class Semaphore private int count; public Semaphore(int n) this.count = n; public synchronized void acquire() while(count = 0) try wait(); catch (InterruptedException e) /keep trying count-; public synchronized void release() count+; notify(); /alert a thread
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 知识传授培训的内容课件
- 田七痛经胶囊市场分析-洞察及研究
- 2024年新疆纪委监委公开遴选公务员笔试试题及答案解析
- 知识产权项目专员培训课件
- 地热热源探测方法-洞察及研究
- 知识产权知识培训课件
- 钳工基础知识培训课件笔记
- 知识产权基础知识培训会课件
- 电子销售工程师招聘面试题及回答建议(某大型集团公司)2025年
- 2025年工业互联网平台同态加密技术在智能工厂生产环境监测中的应用可行性
- 2025年秋季新学期第一次全体教师大会上校长讲话:四重人生境界一颗育人初心-新学期致每位教书人
- 精英人才管理办法
- 2023年经济法基础第四章税法概述及货物和劳务税法律制度课件讲义
- 蚊虫消杀培训课件
- 智能建筑中机器人的应用与装配式施工技术
- 支架术后护理常规课件
- 贝尔面瘫个案护理
- 急性主动脉综合征非外科强化治疗中国专家共识解读 2
- 教师培训Ai课件
- 妇产科子宫脱垂护理查房
- 肿瘤放射治疗护理常规
评论
0/150
提交评论