`

Java线程Step By Step(Step 3)

阅读更多

(See http://www.suneca.com/article.asp?id=55

六、线程同步
    多线程操作给我们带来了很多好处,但也不少问题我们需要去解决,这些问题主要是当多个线程共享数据时,我们应该如何去考虑同步的问题。线程同步问题,可能有些人做开发根本就不会发生,因为在技术框架上,它根本就不可能发生;有些人可能在开发一些应用系统的时候,需要考虑到线程同步的总理,但根本就不去考虑,再说,这种问题也是很少发生;有些人,对线程同步的问题想得太复杂,提到某一些类对象的时候,就马上提出,这个不是线程安全的,应该怎么样怎么样!其实,根本不会发生线程同步的问题,也就是说多线程没有共享数据的时候,你考虑线程安全干嘛?线程安全这一块控制不好,反而会影响整个系统的性能。
    多线程共享数据的时候,我们需要考虑到线程同步,以下演示了一个线程共享数据的问题。现在假设有两条线程,分别为线程t1跟线程t2,帐号的余额为1000元,这两条线程都是取钱。假如不考虑线程同步问题,线程1启动后,执行取钱操作,一共取六次,每次一百元;线程2启动后,执行取钱操作,一共也取六次,每次一百元。每次取钱之前都会先检查一下余额,如果余额度小于一百元,则线程退出。最后,在控制台上输出帐号的总额。程序的实现如下:
 程序代码
package zizz;

import java.util.Date;

/**
* 该类演示了多线程的数据共享问题.
* 
* @author <a href='http://www.suneca.com'>ZIZZ</a>
*
* @Create-Time:2008 下午08:41:38
*/

public class MultiThread {

    /**
     * @param args
     */

    public static void main(String[] args) {
        BankThread bank = new BankThread();
        Thread t1 = new Thread(bank);
        Thread t2 = new Thread(bank);
        t1.start();
        t2.start();
        try {
            //子线程执行完毕之后,主线程才继续执行.
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }        
        System.out.println("帐号余额为:" + BankThread.totalMoney);
    }

}

/**
* 共享的线程实例.
* 
* @author <a href='http://www.suneca.com'>ZIZZ</a>
*
* @Create-Time:2008 下午08:43:01
*/

class BankThread implements Runnable{

    /**
     * 帐号金额.
     */

    static int totalMoney = 1000;
    
    /**
     * 取钱或转帐每次的金额.
     * 
     */

    final static int FETCH_EACH_TIME = 100;
    
    /**
     * 线程执行内容.
     */

    public void run() {        
        for(int i=0;i<6;i++){
            //每次取钱先检查一下帐户的余额.
            if(checkAccountBalance()){
                //执行日志操作.
                logOperator();
                doFetchMoney();
            } else {
                //余额不足,退出循环
                break;
            }
        }
    }
    
    /**
     * 检查帐号状态.
     * 
     * @return
     */

    public boolean checkAccountBalance(){
        //如果余额不够,则返false.否则,返回true
        if(totalMoney < FETCH_EACH_TIME){
            return false;
        } else {
            return true;
        }
    }
    
    /**
     * 取钱.假设每次取一百块钱.
     */

    public void doFetchMoney(){
        System.out.println(Thread.currentThread().getName() + " 取出" + FETCH_EACH_TIME);
        totalMoney = totalMoney - FETCH_EACH_TIME;
    }    
    
    /**
     * 做日志,该方法目的是为了让线程多干一些事情.
     * 
     */

    public void logOperator(){
        for(int i=0;i<100000;i++){
            //不断让JVM分配内存,该操作目的是为了能让线程同步问题更快发生.
            Date date = new Date();
        }
    }
}


对程序一共执行了两次,第一次结果为:

该结果正确!
运行第二次,结果为:

该结果不正确!

为什么会出现这个问题呢?我们给出这样的解释:
1)线程t1跟线程t2他们的优先级别相同,他们在线程调度器的调度下,交叉着使用CPU资源。
2)当线程t1处于运行状态时,检查完帐号余额(checkAccountBalance)时,结果是满足条件的,帐号余额还有100元,此时,t1阻塞,CPU资源给线程t2,t2进入运行状态。
3)线程t2检查帐号余额,此时,还是满足条件,则进行日志后,进行扣款。扣款后的余额为0。继续执行循环,不够提款,线程t2退出。
4)线程t1继续执行,执行扣款操作,因为已经跳过了检查帐号途额的操作,所以直接扣款,扣款后的帐号余额为:-100元。

那我们应该如何去解决这个问题呢?此时,我们需要使用到锁的机制来进行相应的操作。
在Java语言中,引入了对象互斥锁(mutual exclusive lock,也简称为对象锁)的概念,来保证共享数据操作的完整性:
1)每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
2)关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。在Java中的两种使用synchronized的方式:
a)放在方法前面,这样,调用该方法的线程均将获得对象的锁。
b)放在代码块前面,它也有两种形式:
synchronized (this){… …}synchronized {… …}:代码块中的代码将获得当前对象引用的锁
synchronized(otherObj){… …}:代码块中的代码将获得指定对象引用的锁
对对象加了锁,用完之后,我们需要释放锁
1)如果一个线程一直占用一个对象的锁,则其他的线程将永远无法访问该对象,因此,需要在适当的时候,将对象锁归还。
2)当线程执行到synchronized()块结束时,释放对象锁。
3)当在synchronized()块中遇到break, return或抛出exception,则自动释放对象锁。
4)当一个线程调用wait()方法时,它放弃拥有的对象锁并进入等待队列。


对于银行存款的例子,我们可以使用锁机制,对其进行改进!
 程序代码
package zizz;

import java.util.Date;

/**
* 该类演示了多线程的数据共享问题.
* 
* @author <a href='http://www.suneca.com'>ZIZZ</a>
*
* @Create-Time:2008 下午08:41:38
*/

public class MultiThread {

    /**
     * @param args
     */

    public static void main(String[] args) {
        BankThread bank = new BankThread();
        Thread t1 = new Thread(bank);
        Thread t2 = new Thread(bank);
        t1.start();
        t2.start();
        try {
            //子线程执行完毕之后,主线程才继续执行.
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }        
        System.out.println("帐号余额为:" + BankThread.totalMoney);
    }

}

/**
* 共享的线程实例.
* 
* @author <a href='http://www.suneca.com'>ZIZZ</a>
*
* @Create-Time:2008 下午08:43:01
*/

class BankThread implements Runnable{

    /**
     * 帐号金额.
     */

    static int totalMoney = 1000;
    
    /**
     * 取钱或转帐每次的金额.
     * 
     */

    final static int FETCH_EACH_TIME = 100;
    
    /**
     * 线程执行内容.
     */

    public void run() {        
        for(int i=0;i<6;i++){
            //每次取钱先检查一下帐户的余额.
            synchronized(this){
                if(checkAccountBalance()){
                    //执行日志操作.
                    logOperator();
                    doFetchMoney();
                } else {
                    //余额不足,退出循环
                    break;
                }
            }
        }
    }
    
    /**
     * 检查帐号状态.
     * 
     * @return
     */

    public boolean checkAccountBalance(){
        //如果余额不够,则返false.否则,返回true
        if(totalMoney < FETCH_EACH_TIME){
            return false;
        } else {
            return true;
        }
    }
    
    /**
     * 取钱.假设每次取一百块钱.
     */

    public void doFetchMoney(){
        System.out.println(Thread.currentThread().getName() + " 取出" + FETCH_EACH_TIME);
        totalMoney = totalMoney - FETCH_EACH_TIME;
    }    
    
    /**
     * 做日志,该方法目的是为了让线程多干一些事情.
     * 
     */

    public void logOperator(){
        for(int i=0;i<100000;i++){
            //不断让JVM分配内存,该操作目的是为了能让线程同步问题更快发生.
            Date date = new Date();
        }
    }
}


在原来的:
 程序代码
                if(checkAccountBalance()){
                    //执行日志操作.
                    logOperator();
                    doFetchMoney();
                } else {
                    //余额不足,退出循环
                    break;
                }


增加锁,修改为:
 程序代码
            synchronized(this){
                if(checkAccountBalance()){
                    //执行日志操作.
                    logOperator();
                    doFetchMoney();
                } else {
                    //余额不足,退出循环
                    break;
                }
            }


问题解决,再次运行程序,结果正确!

使用锁机制也可能会导致死锁,在Step 4我们将会演示一个死锁的例子,这一节我们先了解一下概念!
1)是指两个线程,都相互等待对方释放lock
2)是不可测知或避开的
3)应采取措施避免死锁的出现
分享到:
评论

相关推荐

    Microsoft.Visual.C#.2010.Step.by.Step].(John.Sharp).中英文文字版

    《Visual C# 2010从入门到精通:Step by Step》以深受读者欢迎的“动手练习”(learn-by-doing exercise)风格,演示了如何、何时以及为何使用C认#快速应用程序开发环境的特性。通过《Visual C# 2010从入门到精通:...

    Visual C# Step by Step (中文版)+附带源码包

    《Visual C# Step by Step (中文版)》是一本针对初学者至中级程序员设计的教程,旨在帮助读者全面掌握Visual C#编程语言。这本书详细介绍了如何使用Microsoft的Visual Studio开发环境,尤其是针对C#语言的功能和特性...

    Assembly Language Step-by-Step

    ### 《Assembly Language Step-by-Step》知识点梳理 #### 一、书籍基本信息 - **书名**:《Assembly Language Step-by-Step》 - **作者**:Jeff Duntemann - **出版社**:Wiley Publishing, Inc. - **出版年份**:...

    Assembly Language Step-By-Step - (Wiley, 2009, 0470497025).pdf

    《Assembly Language Step-by-Step: Programming with Linux》(第三版)是一本深入讲解汇编语言及其在Linux环境下的应用的专业书籍。本书由Jeff Duntemann编写,并于2009年由Wiley出版社出版。该书不仅适合初学者...

    C# Step By Step 2010

    《C# Step By Step 2010》是一本针对初学者至中级程序员的C#编程教程,通过逐步学习的方式,全面介绍了Visual C# 2010的使用技巧和核心概念。这本书覆盖了从基础语法到高级特性的广泛主题,旨在帮助读者深入理解和...

    Microsoft Visual C# 2008 Step by step 源码及书

    《Microsoft Visual C# 2008 Step by Step》是一本专为初学者设计的C#编程教程,它深入浅出地介绍了C#语言的基础知识和.NET Framework的应用。这本书不仅涵盖了编程的基本概念,还提供了丰富的实例,使得学习者能够...

    ASP.NET 4.0Step.by.Step

    《ASP.NET 4.0 Step by Step》是一本由George Shepherd撰写的书籍,由Microsoft Press出版。本书详细介绍了最新的ASP.NET 4.0技术,并通过一系列循序渐进的教程帮助读者深入理解并掌握该技术。对于想要学习或提升ASP...

    VxWorks调试 step by step

    本文将基于提供的标题“VxWorks调试 step by step”以及描述,深入探讨VxWorks调试的过程,并结合可能包含的文档资源,如“打通VxWorks调试环境 Step by Step.doc”和“20087108422323967.pdf”,提供详尽的知识点...

    Visual C# 2010 Step By Step源码

    《Visual C# 2010 Step By Step》是一本深受程序员喜爱的教程书籍,它详细介绍了Microsoft的C#编程语言在2010版本中的使用。这本书通过一系列的实践步骤,帮助读者从零基础逐步掌握C#编程,涵盖了语言基础、面向对象...

    Java-step:一些java的东西

    7. **多线程**:Java内置对多线程的支持,Thread类和Runnable接口允许开发人员创建并发执行的任务,提高程序的效率。 8. **Swing和JavaFX**:这两个是Java的图形用户界面(GUI)库,用于构建桌面应用。Swing是老...

    VC.NET_Step_by_Step.zip_step by step

    《VC++.NET Step by Step》是一本针对微软的Visual C++ .NET编程环境的逐步学习指南。这本书通过一系列章节,详细介绍了如何使用这个强大的开发工具进行应用程序开发。压缩包中的文件名代表了书籍的不同章节,这表明...

    Microsoft Visual Basic 2008 Step by Step

    从给定的文件信息来看,我们讨论的主题是“Microsoft Visual Basic 2008 Step by Step”,这是一本由Michael Halvorson编写的书籍,由Microsoft Press出版,于2008年发行。本书旨在为读者提供逐步学习Visual Basic ...

    visual csharp 2010 step by step (从入门到精通)源码

    《Visual C# 2010 Step by Step》是一本专为初学者和有经验的开发者设计的教程,旨在帮助读者全面掌握C#编程语言及其在.NET Framework 4.0环境下的应用。这本书通过逐步指导的方式,深入浅出地讲解了C# 2010的各种...

    VC.net_Step_by_Step.rar_VC控制_step by step_vc.net MFC

    《VC++.net_Step_by_Step.rar》是一个深入学习Visual C++.net的教程资源,它旨在引导初学者从基础的控制台程序开发逐渐过渡到更高级的Windows SDK控制编程,最终掌握MFC(Microsoft Foundation Classes)框架的应用...

    Visual C# 2010 Step By Step 随书代码

    《Visual C# 2010 Step By Step》是一本专为初学者和有一定编程基础的读者设计的教程书籍,其随书代码是学习过程中的重要辅助资源。这本书旨在通过逐步指导的方式,帮助读者深入理解和掌握Visual C# 2010这门编程...

    vb教程 step by step

    VB教程Step by Step是针对初学者的一套详细指导,旨在帮助读者逐步掌握Visual Basic (VB)编程语言。VBScript虽然在名称中提及,但这里主要讨论的是VB,它是一种基于事件驱动的编程语言,广泛应用于Windows应用程序...

    Microsoft.Visual.Basic.2010.Step.by.Step

    ### Microsoft Visual Basic 2010 Step by Step #### 知识点概览 - **基础知识**:介绍Visual Basic 2010的基础概念、安装配置及开发环境。 - **编程基础**:涵盖变量、数据类型、流程控制(条件语句、循环)、函数...

Global site tag (gtag.js) - Google Analytics