zoukankan      html  css  js  c++  java
  • 多线程中的"断点"续传《notify()和wait()》

    眼下在做一个项目。关于软件管理与下载的,预计项目提交日期定在6月9号。项目做了有20天了,可是在一个功能上卡住了。在这个项目中有一个功能————APK的下载须要实现。

    相信大家都玩过非常多关于下载APK的软件,在这个下载功能上,应该可以清楚的知道有:断点续传;也就是当你点击下载之后的下载过程中,可以点击暂停来暂时控制此时要不要继续下载。当点击继续下载的时候。这个软件会接着暂停之前的进度继续下载。

    因为第一次接触这样的功能的实现。在网络上搜索到的都是使用Sqlite来记录下载的进度,然后通过sqlite所记录的下载进度控制。读了三天时间。没有什么进展。别人写的代码充斥着各种的嵌套、包装。看得我晕头转向。最后决定。还是靠自己写吧。

    最后突然想到我能够使用:wait notify(notifyall)   来控制。

    然后。思路瞬间清晰了非常多。当然,这个东西我不经常使用。不是非常熟。

    只是没关系。有网络嘛,自学一下就OK了

    首先介绍一下wait 和 notify(notifyall)的规范说明及用法:

    wait()、notify()、notifyAll()是三个定义在Object类里的方法,能够用来控制线程的状态。这三个方法终于调用的都是jvm级的native方法,随着jvm执行平台的不同可能有些许差异。

    1.假设对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去。然后处于等待状态。

    2.假设对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程能够继续执行。

    3.假设对象调用了notifyAll方法就会通知全部等待这个对象控制权的线程继续执行。

    当中wait方法有三个重载的方法:<wait 方法通过參数能够指定等待的时长。

    假设没有指定參数。默认一直等待直到被通知。

    >

      wait()、wait(long)、wait(long,int)


    下面是一个演示代码,以最简洁的方式说明复杂的问题:

    NotifyThread是用来模拟3秒钟后通知其它等待状态的线程的线程类;

    WaitThread是用来模拟等待的线程类;

    等待的中间对象是flag。一个String对象;

    main方法中同一时候启动一个Notify线程和三个wait线程;

    public class NotifyTest {
    	private  String flag = "true";
    
    	class NotifyThread extends Thread{
    		public NotifyThread(String name) {
    			super(name);
    		}
    		public void run() {		
    			try {
    				sleep(3000);//推迟3秒钟通知
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			
    				flag = "false";
    				flag.notify();
    		}
    	};
    
    	class WaitThread extends Thread {
    		public WaitThread(String name) {
    			super(name);
    		}
    
    		public void run() {
    			
    				while (flag!="false") {
    					System.out.println(getName() + " begin waiting!");
    					long waitTime = System.currentTimeMillis();
    					try {
    						flag.wait();
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					waitTime = System.currentTimeMillis() - waitTime;
    					System.out.println("wait time :"+waitTime);
    				}
    				System.out.println(getName() + " end waiting!");
    			
    		}
    	}
    
    	public static void main(String[] args) throws InterruptedException {
    		System.out.println("Main Thread Run!");
    		NotifyTest test = new NotifyTest();
    		NotifyThread notifyThread =test.new NotifyThread("notify01");
    		WaitThread waitThread01 = test.new WaitThread("waiter01");
    		WaitThread waitThread02 = test.new WaitThread("waiter02");
    		WaitThread waitThread03 = test.new WaitThread("waiter03");
    		notifyThread.start();
    		waitThread01.start();
    		waitThread02.start();
    		waitThread03.start();
    	}
    
    }
    假设你拿这段程序去执行下的话, 会发现根本执行不了,它会报以下的错误:

    java.lang.IllegalMonitorStateException:object not locked by thread before notify()

    首先,这儿要很注意的几个事实是:

    1. 不论什么一个时刻,对象的控制权(monitor)仅仅能被一个线程拥有。
    2. 不管是执行对象的wait、notify还是notifyAll方法,必须保证当前执行的线程取得了该对象的控制权(monitor)
    3. 假设在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常。
    4. JVM基于多线程,默认情况下不能保证执行时线程的时序性

    基于以上几点事实,我们须要确保让线程拥有对象的控制权。也就是说在waitThread中运行wait方法时,要保证waitThread对flag有控制权;在notifyThread中运行notify方法时。要保证notifyThread对flag有控制权。

    线程取得控制权的方法有三:

    1. 运行对象的某个同步实例方法。
    2. 运行对象相应类的同步静态方法。
    3. 运行对该对象加同步锁的同步块。
    我们用第三种方法来做说明:将以上notify和wait方法包在同步块中
    synchronized (flag) {
    				flag = "false";
    				flag.notify();
    			}

    synchronized (flag) {
    				while (flag!="false") {
    					System.out.println(getName() + " begin waiting!");
    					long waitTime = System.currentTimeMillis();
    					try {
    						flag.wait();
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					waitTime = System.currentTimeMillis() - waitTime;
    					System.out.println("wait time :"+waitTime);
    				}
    				System.out.println(getName() + " end waiting!");
    			}

    问题攻克了吗?当然没有。

    好像执行还是报错java.lang.IllegalMonitorStateException。

    怎么办?

    这时的异常是因为在针对flag对象同步块中。更改了flag对象的状态所导的。

    例如以下:

    flag = "false";    flag != "false";    flag.notify();

    对在同步块中对flag进行了赋值操作。使得flag引用的对象改变。这时候再调用notify方法时,由于没有控制权所以抛出异常。

    我们能够改进一下,将flag改成一个数组,然后更改它的属性不会影响到flag的引用。

    我们这里改成数组来试试。也能够达到相同的效果:

    private   String flag[] = {"true"};

    	synchronized (flag) {
    				flag[0] = "false";
    				flag.notify();
    			}

    synchronized (flag) {
    				while (flag[0]!="false") {
    					System.out.println(getName() + " begin waiting!");
    					long waitTime = System.currentTimeMillis();
    					try {
    						flag.wait();
    						
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}

    这时候再执行,不再报异常,可是线程没有结束是吧。没错。还有线程阻塞,处于wait状态。

    原因非常easy。我们有三个wait线程,仅仅有一个notify线程,notify线程执行notify方法的时候,是随机通知一个正在等待的线程。所以。如今应该还有两个线程在waiting。

    我们仅仅须要将NotifyThread线程类中的flag.notify()方法改成notifyAll()就能够了。

    notifyAll方法会通知全部正在等待对象控制权的线程。

    终于完毕版例如以下:

    public class NotifyTest {
    	private String flag[] = { "true" };
    
    	class NotifyThread extends Thread {
    		public NotifyThread(String name) {
    			super(name);
    		}
    
    		public void run() {
    			try {
    				sleep(3000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			synchronized (flag) {
    				flag[0] = "false";
    				flag.notifyAll();
    			}
    		}
    	};
    
    	class WaitThread extends Thread {
    		public WaitThread(String name) {
    			super(name);
    		}
    
    		public void run() {
    			synchronized (flag) {
    				while (flag[0] != "false") {
    					System.out.println(getName() + " begin waiting!");
    					long waitTime = System.currentTimeMillis();
    					try {
    						flag.wait();
    
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					waitTime = System.currentTimeMillis() - waitTime;
    					System.out.println("wait time :" + waitTime);
    				}
    				System.out.println(getName() + " end waiting!");
    			}
    		}
    	}
    
    	public static void main(String[] args) throws InterruptedException {
    		System.out.println("Main Thread Run!");
    		NotifyTest test = new NotifyTest();
    		NotifyThread notifyThread = test.new NotifyThread("notify01");
    		WaitThread waitThread01 = test.new WaitThread("waiter01");
    		WaitThread waitThread02 = test.new WaitThread("waiter02");
    		WaitThread waitThread03 = test.new WaitThread("waiter03");
    		notifyThread.start();
    		waitThread01.start();
    		waitThread02.start();
    		waitThread03.start();
    	}
    
    }

    事实上通过上面的就行学会怎么使用wait和notigy(notifyall)了,由于它的使用上就这么多。应用在断点续传上已经可以解决这个问题了。

    最后的最后来看看我的部分代码通过notify()和wait()实现断点续传:

    /**
     * DownloadTaskAsnc extends AsyncTask<String, Integer, LoadInfobean>
     * 该线程类是完毕对  DownFileTask extends AsyncTask<String, Integer, String> 线程类的调用。
     * 这个类在对下载调用之前推断。假设处于暂停状态则激活使其继续下载
     */
    	@Override
    	protected void onPostExecute(LoadInfobean loadInfobean) {
    		// TODO Auto-generated method stub
    		super.onPostExecute(loadInfobean);
    //		if(DownFileTask.stateDownload==DownFileTask.DOWNLOADING) return ;
    //		DownFileTask.stateDownload=DownFileTask.DOWNLOADING;
    
                         /**
                          *当下载文件的线程处于等待状态时。激活线程使其继续下载文件
                          * 看看notify()的使用就会懂得怎么用了。呵呵...
                          */
                        synchronized (DownFileTask.DOWNLOAD_PAUSE) {
    		      if(DownFileTask.stateDownload==DownFileTask.DOWNLOAD_PAUSE){
    			DownFileTask.stateDownload=DownFileTask.DOWNLOADING;
    			DownFileTask.DOWNLOAD_PAUSE.notifyAll();
    			return;
    			}
    		}
    		downFileTask.execute(url);
    		showProgress(loadInfobean,vi);
    	}


    	/**
    	 * DownFileTask extends AsyncTask<String, Integer, String>
    	 * 
    	 * 定义了属性: public static final String DOWNLOAD_INIT = "1",DOWNLOADING =
    	 * "2",DOWNLOAD_PAUSE = "3"; public static String stateDownload =
    	 * DOWNLOAD_INIT;
    	 * 
    	 */
    	@SuppressWarnings("resource")
    	@Override
    	protected String doInBackground(String... params) {
    
    		url = params[0];
    		if (!checkWorkNet(context)) {
    			Toast.makeText(context, "无网络连接", Toast.LENGTH_SHORT).show();
    			return "无网络联接";
    		}
    
    		if (!TextUtils.isEmpty(url)) {
    			URLConnection connection = null;
    			InputStream inputStream = null;
    			RandomAccessFile randomAccessFile = null;
    			try {
    
    				URL myUrl = new URL(url);
    				connection = myUrl.openConnection();
    				connection.setConnectTimeout(5 * 1000);
    				// 设置范围,格式为Range:bytes x-y;
    				int startPos = downloadBeans.get(0).getStartPos();
    				int endPos = downloadBeans.get(0).getEndPos();
    				connection.setRequestProperty("Range", "bytes="
    						+ (startPos + completeSize) + "-" + endPos);
    				connection.connect();
    				inputStream = connection.getInputStream();
    				fileSize = connection.getContentLength();
    				Log.d("vv", "downfiletask中的filezise==" + fileSize);
    
    				if (fileSize <= 0)
    					return "文件为空";
    
    				else {
    					downloadFileBean.setFileName(fileName);
    					downloadFileBean.setFileSize(fileSize);
    					downloadFileBean.setDownLoadAddress(url);
    					downloadFileBean.setDownloadState("下载");
    
    					randomAccessFile = new RandomAccessFile(localfilePath, "rw");
    					randomAccessFile.seek(startPos + completeSize);
    
    					// 存储文件缓存
    					byte buf[] = new byte[2 * 1024];
    					int length = 0;
    
    					while ((length = inputStream.read(buf)) != -1) {
    						// if (isDown) {
    						// outputStream.write(buf, 0, length);
    
    						randomAccessFile.write(buf, 0, length);
    						completeSize += length;
    
    						// 实时更新数据库下载信息
    						DataDbHelper.getInstance(context).updataInfos(threadId,
    								completeSize, url);
    						Log.d("vv", "读写数据while (completeSize)==" + completeSize);
    						// 原稿的进度保存
    						downloadFileBean.setDownloadSize(completeSize);
    						fileMap.put(fileName, downloadFileBean);
    
                              //通过wait()使下载文件的线程处于等待的状态
                                  synchronized (DOWNLOAD_PAUSE) {
    				if (stateDownload == DOWNLOAD_PAUSE) {
    
    				 DOWNLOAD_PAUSE.wait();
    					}
    				}
    					}
    				}
    			} catch (Exception e) {
    				Log.d("vv", "randomAccessFile.seek downloadtask 中出错了=="
    						+ completeSize);
    				return "出错了";
    			} finally {
    				try {
    
    					if (inputStream != null || randomAccessFile != null) {
    						randomAccessFile.close();
    						inputStream.close();
    					}
    				} catch (Exception e2) {
    				}
    			}
    
    		}
    
    		return "下载完毕";
    	}
    
    	
    

  • 相关阅读:
    StarUML中时序图
    HTML5/jQuery雷达动画图表 图表配置十分简单
    AspNetPager样式以及属性帮助文档
    IE浏览器img不显示解决
    php表单提交--文件
    javascript继承---组合式继承
    javascript继承--原型链的 继承
    【转】JavaScript 之arguments、caller 和 callee 介绍
    Array对象
    android 系统提示对话框(AlertDialog)的使用
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/5137215.html
Copyright © 2011-2022 走看看