基础篇(四)
wait(),notify()/notityAll()方法
关于这两个方法,有很多的内容需要说明.在下面的说明中可能会有很多地方不能一下子
明白,但在看完本节后,即使不能完全明白,你也一定要回过头来记住下面的两句话:
1、wait(),notify()/notityAll()方法是普通对象的方法(Object超类中实现),而不是线程对象的方法
2、wait(),notify()/notityAll()方法只能在同步方法中调用
[线程的互斥控制]
多个线程同时操作某一对象时,一个线程对该对象的操作可能会改变其状态,而该状态会影响
另一线程对该对象的真正结果.
这个例子我们在太多的文档中可以看到,就象两个操售票员同时售出同一张票一样.
线程A 线程B
1.线程A在数据库中查询存票,发现票C可以卖出
2.线程A接受用户订票请求,准备出票.
3.这时切换到了线程B执行
4.线程B在数据库中查询存票,发现票C可以卖出
5.线程B将票卖了出去
6.切换到线程A执行,线程A卖了一张已经卖出的票
所以需要一种机制来管理这类问题的发生,当某个线程正在执行一个不可分割的部分时,其它线程不能同时执行这一部分.
象这种控制某一时刻只能有一个线程执行某个执行单元的机制就叫互斥控制或共享互斥(mutual exclusion)
在JAVA中,用synchornized关键字来实现互斥控制(暂时这样认为,JDK1.5已经发展了新的机制)
synchornized关键字
把一个单元声明为synchornized,就可以让在同一时间只有一个线程操作该方法.
有人说synchornized就是一把锁,事实上它确实存在锁,但是是谁的锁,锁谁,这是一个非常复杂的问题.
每个对象只有一把监视锁(monitor lock),一次只能被一个线程获取.当一个线程获取了这一个锁后,其它线程就只能等待这个线程释放锁才能再获取.
那么synchornized关键字到底锁什么?得到了谁的锁?
对于同步块,synchornized获取的是参数中的对象锁:
synchornized(obj){
//...............
}
线程执行到这里时,首先要获取obj这个实例的锁,如果没有获取到线程只能等待.如果多个线程
执行到这里,只能有一个线程获取obj的锁,然后执行{}中的语句,所以,obj对象的作用范围不同,控制程序不同.
假如:
public void test(){
Object o = new Object();
synchornized(obj){
//...............
}
}
这段程序控制不了任何,多个线程之间执行到Object o = new Object();时会各自产生一个对象然后获取这个对象有监视锁,各自皆大欢喜地执行.
而如果是类的属性:
class Test{
Object o = new Object();
public void test(){
synchornized(o){
//...............
}
}
}
所有执行到Test实例的synchornized(o)的线程,只有一个线程可以获取到监视锁.
有时我们会这样:
public void test(){
synchornized(this){
//...............
}
}
那么所有执行Test实例的线程只能有一个线程执行.而synchornized(o)和
synchornized(this)的范围是不同的,因为执行到Test实例的synchornized(o)的线程等待时,其它线程可以执行Test实例的synchornized(o1)部分,但多个线程同时只有一个可以执行Test实例的synchornized(this).
而对于 synchornized(Test.class){
//...............
}这样的同步块而言,所有调用Test多个实例的线程只能有一个线程可以执行.
synchornized方法
如果一个方法声明为synchornized的,则等同于把在为个方法上调用synchornized(this).
如果一个静态方法被声明为synchornized,则等同于把在为个方法上调用synchornized(类.class).
wait方法和notify/notifyAll方法.
这两个(或叫三个)方法都是Object对象的方法,而不是线程对象的方法.如同锁一样,它们是在线程中调用某一对象上执行的.
class Test{
public synchornized void test(){
//获取条件,int x 要求大于100;
if(x < 100)
wait();
}
}
这里为了说明方法没有加在try{}catch(){}中,如果没有明确在哪个对象上调用wait()方法,则
为this.wait();
假如:
Test t = new Test();
现在有两个线程都执行到t.test();方法.其中线程A获取了t的对象锁,进入test()方法内.
这时x小于100,所以线程A进入等待.
当一个线程调用了wait方法后,这个线程就进入了这个对象的休息室(waitset),这是一个虚拟的
对象,但JVM中一定存在这样的一个数据结构用来记录当前对象中有哪些程线程在等待.
当一个线程进入等待时,它就会释放锁,让其它线程来获取这个锁.
所以线程B有机会获得了线程A释放的锁,进入test()方法,如果这时x还是小于100,线程B也进入了t的休息室.
这两个线程只能等待其它线程调用notityAll来唤醒.
但是如果调用的是有参数的wait(time)方法,则线程A,B都会在休息室中等待这个时间后自动唤醒.
为什么真正的应用都是用while(条件)而不用if(条件)
在实际的编程中我们看到大量的例子都是用
while(x < 100)
wait();go();而不是用if,为什么呢?
在多个线程同时执行时,if(x <100)是不安全的.因为如果线程A和线程B都在t的休息室中等待,
这时另一个线程使x==100了,并调用notifyAll方法,线程A继续执行下面的go().而它执行完成后,x有可能又小于100,比如下面的程序中调用了--x,这时切换到线程B,线程B没有继续判断,直接执行go();就产生一个错误的条件,只有while才能保证线程B又继续检查一次.
notify/notifyAll方法
这两个方法都是把某个对象上休息区内的线程唤醒,
notify只能唤醒一个,但究竟是哪一个不能确定,
notifyAll则唤醒这个对象上的休息室中所有的线程.
一般有为了安全性,我们在绝对多数时候应该使用notifiAll(),除非你明确知道只唤醒其中的一个线程.
那么是否是只要调用一个对象的wait()方法,当前线程就进入了这个对象的休息室呢?事实中,要调用一个对象的wait()方法,只有当前线程获取了这个对象的锁,换句话说一定要在这个对象的同步方法或以这个对象为参数的同步块中.
class MyThread extends Thread{
Test t = new Test();
public void run(){
t.test();
System.out.println("Thread say:Hello,World!");
}
}
public class Test {
int x = 0;
public void test(){
if(x==0)
try{
wait();
}catch(Exception e){}
}
public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
这个线程就不会进入t的wait方法而直接打印出Thread say:Hello,World!.
而如果改成:
public class Test {
int x = 0;
public synchornized void test(){
if(x==0)
try{
wait();
}catch(Exception e){}
}
public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
我们就可以看到线程一直等待,注意这个线程进入等待后没有其它线程唤醒,除非强行退出
JVM环境,否则它一直等待.
所以请记住:线程要想调用一个对象的wait()方法就要先获得该对象的监视锁,而一旦调用wait()后又立即释放该锁
分享到:
相关推荐
《Java多线程编程实战指南》这本书深入浅出地讲解了Java多线程的核心概念和实战技巧,分为核心篇和设计模式篇,旨在帮助开发者掌握并应用多线程技术。 1. **线程基础** - **线程的创建**:Java提供了两种创建线程...
《多线程编程实战指南-核心篇》是针对Java开发者深入理解并掌握多线程编程的一本实战性书籍。在当今的并发计算环境中,多线程技术是必不可少的知识点,它能够有效地利用多核处理器资源,提高程序的执行效率。本书以...
随着现代处理器的生产工艺从提升...《Java多线程编程实战指南(核心篇)》适合有一定Java语言基础的读者作为入门多线程编程之用,也适合有一定多线程编程经验的读者作为重新梳理知识结构以提升认知层次和参考之用。
在Java编程中,多线程是一项关键技能,它允许程序同时执行多个任务,极大地提高了程序的...通过阅读"Java多线程编程实战指南 设计模式篇.pdf",你将获得更深入的理论知识和实践技巧,为你的编程事业奠定坚实的基础。
在VC++环境中,多线程编程是开发高效应用程序的关键技术之一。它允许程序同时执行多个任务,提升系统资源利用率,特别是在处理I/O密集型或计算密集型任务时。本篇文章将详细阐述VC++中实现多线程的三种常见方法:...
总结来说,《Windows多线程编程及例程》这篇教程是学习Windows环境下多线程开发的宝贵资料,通过理论讲解和实战演练,能够帮助开发者建立起扎实的多线程编程基础,提升软件设计和性能优化的能力。遗憾的是,缺少第四...
《Java多线程编程实战指南》是一本深入探讨Java并发编程的书籍,涵盖了核心篇与设计模式篇。这本书旨在帮助开发者理解和掌握Java平台上的多线程编程,提升系统性能和可扩展性。在Java世界中,多线程是实现并发处理、...
总之,Java多线程基础篇讲解涵盖了多线程编程的基本原理和实践,通过学习这些内容,开发者可以掌握如何在Java环境中创建和管理线程,以及如何优化多线程程序以充分利用系统资源,从而编写出高效的并发应用程序。
本资源“Java多线程编程实战指南+设计模式篇”深入探讨了这两个主题,旨在帮助开发者提升其在并发编程和软件设计上的技能。 **一、Java多线程** 1. **线程基础**:Java中的线程是程序执行的最小单位。Java通过`...
随着CPU 多核时代的到来,多线程编程在充分利用计算资源、提高软件服务质量方面扮演了越来越重要的角色。...《Java多线程编程实战指南(设计模式篇)》适合有一定Java 多线程编程基础、经验的读者。
四、多线程的挑战与解决方案 1. **线程安全**:确保多线程环境下数据的一致性和完整性是关键问题,需要正确使用同步机制,如互斥锁、条件变量等。 2. **死锁**:多个线程相互等待对方释放资源可能导致死锁,需要...
.NET线程详解基础篇主要涉及了线程的创建、类型、异步操作、WinForm多线程编程、线程池以及同步策略等多个方面。以下是对这些知识点的详细说明: 1. **怎样创建一个线程**: - 使用`Thread`类:通过创建`Thread`...
本篇内容将基于《MFC多线程编程.pdf》的部分内容,详细介绍多线程的概念、Win32 API对多线程的支持以及MFC框架下的多线程编程技巧。 #### 二、多线程基础理论 ##### 2.1 问题提出 以一个简单的例子开始——一个名为...
学习"JAVA程序员必读基础篇"的内容,将帮助初学者建立起坚实的Java编程基础,为进一步深入学习Java高级特性,如Spring框架、JDBC数据库访问、多线程编程等打下坚实的基础。通过实践和不断练习,你将能够熟练掌握这个...
在本篇文章中,我们将深入探讨如何使用Qtcreator进行多线程编程,以及如何实现每秒输出一个内容的功能。 首先,我们要理解多线程的概念。在单线程环境中,程序执行是顺序的,一次只能处理一个任务。而多线程则允许...
4. **第九章多线程编程**: 这个章节可能详细介绍了如何在MFC中设计和管理多线程,包括线程的生命周期管理、异常处理、线程同步策略、线程间通信以及多线程环境下Socket编程的实践案例。 综上所述,MFC多线程...
在本篇论文“Linux下多线程编程的聊天软件”中,作者李杨探讨了如何利用Linux操作系统和多线程编程技术开发一款功能完备的网络聊天软件。在信息时代的背景下,网络聊天工具已经成为日常生活中不可或缺的通讯手段,而...
### Linux 下 C 语言多线程编程实例解析与关键技术点 #### 一、多线程基础知识 在Linux环境下,利用C语言进行多线程编程是一种常见的技术手段,它能够有效地提高程序的并发处理能力,充分利用现代多核处理器的计算...