线程的挂起与数据的同步
7771字,阅读需时26分钟

文章导读

挂起线程就是让线程停止一段时间,等待资源满足或其它任务完成。线程挂起可以用Thread类的sleep方法,挂起线程的主要应用就是实现数据的同步。本文主要介绍利用sleep方法如何挂起线程,并实现简单的数据同步。


sleep方法是Thread类的一个静态方法,该方法可以把当前正在运行的线程挂起一段时间(时间值由参数传入),挂起时间到期后,JVM会在适当的时间再次唤醒该线程。

先看一个例子代码:

package com.milihua.threaddemo;
public class SleepDemo {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread thread = new Thread(new MyRunner());
        //调用start方法启动线程
        thread.start();
        //
        try {
            //主线程挂起1000毫秒
            System.out.println(Thread.currentThread().getName()+ "休眠1秒!");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

MyRunner类代码:

package com.milihua.threaddemo;
public class MyRunner implements Runnable {
    @Override
    public void run() {
        // 在线程中执行的代码
        for( int i = 0; i<100; i++ ) {
            System.out.println("MyRunner:" + i);
            try {
                System.out.println(Thread.currentThread().getName()+ "休眠0.5秒!");
                //子线程挂起500毫秒
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

在SleepDemo类主线程中,启动子线程后,应用Thread类的sleep方法将主线程挂起1000毫秒,因为sleep方法抛出InterruptedException异常,因此调用sleep方法时,需要包含在try-cahtch语句,用于捕获sleep方法可能抛出的异常;在MyRunner类子线程中,每次循环都会调用Thread类的sleep方法将子线程挂起500毫秒,然后再继续执行。程序输出结果如下图所示:

blob.png

图1  SleepDemo输出结果

从输出结果可以看出,主线程在子线程执行之前就被挂起,也验证了线程调用start方法后,JVM并没有直接启动子线程,而是在主线程执行完后再执行子线程的。子线程在执行过程中,每次循环都会等待0.5秒后再继续执行。

上面的例子代码让我们了解了sleep的具体用法,那么sleep方法的具体应用场景是什么?实际应用编程中,在什么情况下需要使用sleep方法呢?

考虑一个编程任务。要求模拟一个服务器端文档上传服务程序,10个用户并发上传不同格式的文档,该服务程序需要启动一个转换线程把用户上传的文档统一转换为PDF文档,转换完成后,需要将该文档存储路径设置到文档对象中,并输出该文档信息。另外,模拟程序不需要处理实际的文档,模拟处理过程即可。

既然是服务器端处理程序,需要采用多线程处理,对每个用户的上传请求创建一个线程A,在线程A中,考虑到文档格式转换操作时间较长,需要创建一个转换文档格式的线程B。程序处理框架如下图所示:

blob.png

图2 文档上传服务程序处理过程

用户发出上传文档请求,主服务线程接收到请求后,创建一个处理该文档的A线程,A线程启动后,创建一个文档转换线程B,B线程负责文档格式的转换工作,A线程需要等待B线程完成文档转换工作后,存储并输出文档信息。上述过程涉及到A线程和B线程的同步,A线程创建B线程后,需要等待B线程完成工作,才能继续执行。此时,A线程就可以调用sleep方法休眠一段时间,等待B线程完成工作。

(1)首先建立Document类,该类有两个属性,分别是docName、savePath,savePath属性值的设置在B线程进行。代码如下:

package com.milihua.document;
public class Document {
    String docName;
    String savePath;
    public Document(String inName)
    {
        this.docName = inName;
    }
    public String getDocName() {
        return docName;
    }
    public void setDocName(String docName) {
        this.docName = docName;
    }
    public String getSavePath() {
        return savePath;
    }
    public void setSavePath(String savePath) {
        this.savePath = savePath;
    }
}

(2)建立文档转换线程类(B线程),该类通过for循环模拟长时间处理操作,并设置传入doc对象的savePath属性,表示文档转换完成。代码如下:

package com.milihua.document;
public class Convert implements Runnable{
    Document doc;
    public Document getDoc() {
        return doc;
    }
    public void setDoc(Document doc) {
        this.doc = doc;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        //模拟长时间处理操纵
        System.out.println(doc.getDocName());
        for(int i = 0; i < 100000; i++)
        {
            ;
        }
        doc.setSavePath(doc.getDocName() + "_path");       
    }
}

(3)建立请求处理线程类(A线程),该类启动文档转换线程,并调用sleep方法自身休眠1000毫秒,等待文档转换工作的完成。

package com.milihua.document;
public class Process implements Runnable{
    Document doc;
    public Document getDoc() {
        return doc;
    }
    public void setDoc(Document doc) {
        this.doc = doc;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        Convert  convert = new Convert();
        convert.setDoc(doc);
        Thread thread = new Thread(convert);
        //调用start方法启动线程
        thread.start();
        while(true)
        {
         try {
                Thread.sleep(1000);
                if( !doc.getSavePath().equals("") )
                {
                    System.out.println(doc.getDocName() + "处理完成,存储路径为:" + doc.getSavePath() );
                    return;
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                return;
            }
        }
    }
}

(4)建立主线程类,该类首先初始化文档列表,然后启动线程对每个文档进行处理。

package com.milihua.document;
import java.util.ArrayList;
public class DocumentServer {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        DocumentServer  docServer = new DocumentServer();
        ArrayList<Document>  array = new ArrayList<Document>();
        docServer.InitDocument(array);
        for(int i = 0; i < array.size(); i++ )
        {
            Process  pro = new Process();
            pro.setDoc(array.get(i));
            Thread thread = new Thread(pro);
            thread.start();
 
        }
    }
   
    public void InitDocument(ArrayList<Document> inArray)
    {
        for( int i = 0;i < 10; i++ )
        {
            Document  temp = new Document("文档" + String.valueOf(i));
            inArray.add(temp);
        }
    }
}

 

程序输出结果如下图所示:

blob.png

图3  文档处理程序输出结果

文章小结

当线程需要等待资源或者信号到达才能继续执行时,可以采用sleep方法让线程自身休眠等待资源和信号。当线程唤醒时,需要线程自身判断等待的资源是否满足或信号是否到达,当资源仍然不能满足或信号仍未到达时,需要提供一种持续等待的机制,直到资源满足或信号到达。

思考与练习

生产者和消费者问题。创建A和B两个线程,A线程是生产者,B线程是消费者,A线程需要挂起一段时间生产产品,B线程需要等待A线程产出产品,B线程可以采用定时轮询的方式等待A线程产出产品。

请编程实现上述任务要求。

我要评论
全部评论
郎宏林
授课老师
授课老师简介
项目经理,系统分析和架构师,从事多年中文信息处理技术。熟悉项目管理、擅长项目需求分析和设计、精通Java、C#、Python等编程语言。
下载APP

手机、电脑同步学

用微信或手机浏览器扫描二维码,即可下载APP。

  • 备案号:鲁ICP备15001146号
  • @1997-2018 潍坊米粒花网络技术有限公司版权所有