负载是一个很大的话题,也是一个非常重要的话题。不管是在大的互联网软件中,还是在一般的小型软件,都对负载有一定的要求,负载过高会导致服务器压力过大;负载过低又比较浪费服务器资源,而且当高请求的时候还可能出现低效率的问题。多线程就是一种提高服务效率的方式。面对海量的用户请求,单线程肯定扛不住,那么多线程的需求也就应运而生,所以说掌握多线程的开发技术对于技术人员来说肯定是非常重要的。参考文档http://docs.oracle.com/javase/7/docs/api/。
一、Runnable使用
publicinterface Runnable { public abstract void run(); }
Runnable接口中,只有一个方法,就是run方法。该方法的主要作用是执行用户需要执行的代码。也就是说我们可以将我们需要在多线程环境下执行的代码放到run里面。
public class RunnableDemo { static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("子线程:" + Thread.currentThread().getName() + ":" + sdf.format(new Date())); try { Thread.sleep(1000);// 休息1s } catch (InterruptedException e) { } System.out.println("子线程:" + Thread.currentThread().getName() + ":" + sdf.format(new Date())); } }).start(); System.out.println("主线程:" + Thread.currentThread().getName() + ":" + sdf.format(new Date())); } } // 结果 /* 子线程:Thread-0:2015-08-27 18:25:14 主线程:main:2015-08-27 18:25:14 子线程:Thread-0:2015-08-27 18:25:15 */
二、Thread使用
这个类是Java中的线程基础类,基本上多线程开发不管是直接或者间接均需要依赖上该类。主要介绍几个方法:
1、start方法: 该方法是线程执行入口,如果你需要你新建的线程开始执行,那么请调用start方法。
2、run方法:线程执行具体代码,放用户希望执行的代码。和start的区别是,调用start方法是使用多线程方法执行,如果调用run方法,那就是单线程执行。也就是说start方法是启动线程,run方法是执行具体代码。在上面的那个例子中,如果是调用run,而不是start方法,那么一定是先打印出来两个子线程的输出值,再打印主线程的输出值。
3、sleep方法:使当前线程休眠指定时间,参数为休眠的时间,单位毫秒。
4、interrupt方法: 中断线程的休眠(sleep, join等操作),会产生InterruptedException异常。
public class InterruptThreadDemo { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("子线程:" + Thread.currentThread().getName() + ":" + TimeUtil.nowTime()); try { Thread.sleep(2000); // 休息2s } catch (InterruptedException e) { ThrowableUtil.logThrowable(e); // 打印异常 } System.out.println("子线程:" + Thread.currentThread().getName() + ":" + TimeUtil.nowTime()); } }); t1.start(); // 中断线程 t1.interrupt(); System.out.println("主线程:" + Thread.currentThread().getName() + ":" + TimeUtil.nowTime()); } }
5、isAlive&isInterrupted&isDaemon方法:检测是否处于运行状态或者是否被中断或者判断是否守护模式运行。
6、join方法:将其他线程的执行嵌入到当前线程中,等待嵌入线程执行完毕,该线程才会被唤醒进行执行。
public class JoinThreadDemo { public static void main(String[] args) { final Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("子线程:" + Thread.currentThread().getName() + ":" + TimeUtil.nowTime()); try { Thread.sleep(5000); // 休息5s } catch (InterruptedException e) { ThrowableUtil.logThrowable(e); // 打印异常 } System.out.println("子线程:" + Thread.currentThread().getName() + ":" + TimeUtil.nowTime()); } }, "1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("子线程:" + Thread.currentThread().getName() + ":" + TimeUtil.nowTime()); // 等待线程1执行完,再继续执行 try { t1.join(); } catch (InterruptedException e) { ThrowableUtil.logThrowable(e); } System.out.println("子线程:" + Thread.currentThread().getName() + ":" + TimeUtil.nowTime()); } }, "2"); t1.start(); t2.start(); System.out.println("主线程:" + Thread.currentThread().getName() + ":" + TimeUtil.nowTime()); } } // 结果 /* 子线程:2:2015-08-27 18:52:29 457 主线程:main:2015-08-27 18:52:29 457 子线程:1:2015-08-27 18:52:29 457 子线程:1:2015-08-27 18:52:34 458 子线程:2:2015-08-27 18:52:34 458 */
7、setDaemon方法:设置为守护模式,当主线程结束后,被设置为守护模式的线程自动结束。
8、currentThread方法:获取当前线程。
三、多线程的实际应用
多线程的应用是比较多的,比如说针对请求分别创建多个线程服务,下载等等。这里就弄一个下载美眉图片的小demo。
1 import java.io.BufferedReader; 2 import java.io.File; 3 import java.io.InputStream; 4 import java.io.InputStreamReader; 5 import java.net.URL; 6 import java.net.URLConnection; 7 import java.util.ArrayList; 8 import java.util.List; 9 import java.util.Map; 10 import java.util.concurrent.BlockingQueue; 11 import java.util.concurrent.ConcurrentHashMap; 12 import java.util.concurrent.ConcurrentMap; 13 import java.util.concurrent.LinkedBlockingDeque; 14 import java.util.regex.Matcher; 15 import java.util.regex.Pattern; 16 17 import javax.imageio.stream.FileImageOutputStream; 18 import javax.imageio.stream.ImageOutputStream; 19 20 import com.gerry.bd.util.ThrowableUtil; 21 22 public class BeautyGirlPhotoDownloadDemo { 23 public static void main(String[] args) { 24 String[] categorys = new String[] { "rihan", "yule", "dongm" }; 25 ConcurrentMap<String, BlockingQueue<String>> map = new ConcurrentHashMap<String, BlockingQueue<String>>(); 26 27 // 分别启用线程来获取图片的url 28 for (String category : categorys) { 29 BlockingQueue<String> queue = new LinkedBlockingDeque<>(); 30 map.put(category, queue); // 添加一个初始化 31 Thread thread = new Thread(new FetchImageUrlRunnable(category, queue), "fetchimg_" + category); 32 thread.start(); 33 } 34 35 File imagePath = new File("D:/image/"); 36 // 每一个品类其两个线程去下载 37 for (Map.Entry<String, BlockingQueue<String>> entry : map.entrySet()) { 38 for (int i = 0; i < 2; i++) { 39 Thread thread = new Thread(new DownloadImageRunnable(imagePath, entry.getKey(), entry.getValue()), "downloadimage_" + entry.getKey() + i); 40 thread.start(); 41 } 42 } 43 } 44 45 /** 46 * 解析页面代码,保存图片url链接 47 * 48 * @author jsliuming 49 * 50 */ 51 public static class FetchImageUrlRunnable implements Runnable { 52 private String category; 53 private BlockingQueue<String> imageUrlQueue; 54 55 public FetchImageUrlRunnable(String category, BlockingQueue<String> queue) { 56 this.category = category; 57 this.imageUrlQueue = queue; 58 } 59 60 @Override 61 public void run() { 62 try { 63 String url = ""; 64 BufferedReader br = null; 65 for (int i = 10; i < 100; i++) { 66 for (int j = 1; j < 20; j++) { 67 url = "http://www.mm8mm8.com/" + this.category + "/list_" + i + "_" + j + ".html"; 68 System.out.println(Thread.currentThread().getName() + ":" + url); 69 70 StringBuffer content = new StringBuffer(); 71 try { 72 URLConnection connection = new URL(url).openConnection(); 73 connection.connect(); 74 br = new BufferedReader(new InputStreamReader(connection.getInputStream())); 75 String line = null; 76 while ((line = br.readLine()) != null) { 77 content.append(line); 78 } 79 } catch (Exception e) { 80 ThrowableUtil.logThrowable(e); 81 } finally { 82 if (br != null) { 83 try { 84 br.close(); 85 } catch (Exception e) { 86 } 87 } 88 } 89 90 // 已经拿到内容,开始解析url 91 if (content.length() == 0) { 92 // empty 93 break; 94 } else { 95 // 开始解析 96 for (String u : this.getHtmlImageUrlList(content.toString())) { 97 this.imageUrlQueue.put(u); 98 } 99 } 100 } 101 } 102 } catch (Throwable e) { 103 ThrowableUtil.logThrowable(e); 104 } 105 } 106 107 /** 108 * 获取图片url 109 * 110 * @param htmlText 111 * @return 112 */ 113 private List<String> getHtmlImageUrlList(String htmlText) { 114 List<String> list = new ArrayList<String>(); 115 Pattern pattern = Pattern.compile("<img\s*src\s*=\s*"(?<imgUrl>[^\s"'<>]*)""); 116 Matcher matcher = pattern.matcher(htmlText); 117 while (matcher.find()) { 118 list.add(matcher.group("imgUrl")); 119 } 120 return list; 121 } 122 } 123 124 /** 125 * 下载图片用线程 126 * 127 * @author jsliuming 128 * 129 */ 130 public static class DownloadImageRunnable implements Runnable { 131 private String category; 132 private BlockingQueue<String> imageUrlQueue; 133 private File baseFile; 134 135 public DownloadImageRunnable(File path, String category, BlockingQueue<String> queue) { 136 this.category = category; 137 this.imageUrlQueue = queue; 138 baseFile = new File(path, this.category); 139 } 140 141 @Override 142 public void run() { 143 int index = 0; 144 InputStream input = null; 145 ImageOutputStream ios = null; 146 while (true) { 147 try { 148 String imgurl = this.imageUrlQueue.take(); 149 150 URLConnection connection = new URL(imgurl).openConnection(); 151 connection.connect(); 152 input = connection.getInputStream(); 153 ios = new FileImageOutputStream(new File(baseFile, Thread.currentThread().getId() + "_" + index++ + ".jpg")); 154 byte[] buf = new byte[2048]; 155 int n = -1; 156 while ((n = input.read(buf)) > 0) { 157 ios.write(buf, 0, n); 158 } 159 } catch (Throwable e) { 160 ThrowableUtil.logThrowable(e); 161 } finally { 162 if (input != null) { 163 try { 164 input.close(); 165 } catch (Exception e) { 166 } 167 } 168 if (ios != null) { 169 try { 170 ios.close(); 171 } catch (Exception e) { 172 } 173 } 174 } 175 } 176 } 177 178 } 179 }
这个代码没有关闭的设置,所有在下载完成后,需要手动关闭。后期会改吧 。。。。。
===========================================================
使用CountDownLatch来控制下载线程下载完数据后结束程序,代码如下:
1 import java.io.BufferedReader; 2 import java.io.File; 3 import java.io.InputStream; 4 import java.io.InputStreamReader; 5 import java.net.URL; 6 import java.net.URLConnection; 7 import java.util.ArrayList; 8 import java.util.List; 9 import java.util.Map; 10 import java.util.concurrent.BlockingQueue; 11 import java.util.concurrent.ConcurrentHashMap; 12 import java.util.concurrent.ConcurrentMap; 13 import java.util.concurrent.CountDownLatch; 14 import java.util.concurrent.LinkedBlockingDeque; 15 import java.util.concurrent.TimeUnit; 16 import java.util.regex.Matcher; 17 import java.util.regex.Pattern; 18 19 import javax.imageio.stream.FileImageOutputStream; 20 import javax.imageio.stream.ImageOutputStream; 21 22 import com.gerry.bd.util.ThrowableUtil; 23 24 public class BeautyGirlPhotoDownloadDemo { 25 static volatile boolean fetchTheradRunning = true; // 用于控制结束线程 26 static final CountDownLatch latch = new CountDownLatch(3); // 用于控制结束线程 27 28 public static void main(String[] args) { 29 String[] categorys = new String[] { "rihan", "yule", "dongm" }; 30 ConcurrentMap<String, BlockingQueue<String>> map = new ConcurrentHashMap<String, BlockingQueue<String>>(); 31 32 // 分别启用线程来获取图片的url 33 for (String category : categorys) { 34 BlockingQueue<String> queue = new LinkedBlockingDeque<>(); 35 map.put(category, queue); // 添加一个初始化 36 Thread thread = new Thread(new FetchImageUrlRunnable(category, queue), "fetchimg_" + category); 37 thread.start(); 38 } 39 40 File imagePath = new File("D:/image/"); 41 // 每一个品类其两个线程去下载 42 for (Map.Entry<String, BlockingQueue<String>> entry : map.entrySet()) { 43 for (int i = 0; i < 2; i++) { 44 Thread thread = new Thread(new DownloadImageRunnable(imagePath, entry.getKey(), entry.getValue()), "downloadimage_" + entry.getKey() + i); 45 thread.start(); 46 } 47 } 48 49 new Thread(new Runnable() { 50 @Override 51 public void run() { 52 try { 53 latch.await();// 等待完成 54 fetchTheradRunning = false; 55 } catch (Throwable e) { 56 ThrowableUtil.logThrowable(e); 57 } 58 } 59 }).start(); 60 } 61 62 /** 63 * 解析页面代码,保存图片url链接 64 * 65 * @author jsliuming 66 * 67 */ 68 public static class FetchImageUrlRunnable implements Runnable { 69 private String category; 70 private BlockingQueue<String> imageUrlQueue; 71 72 public FetchImageUrlRunnable(String category, BlockingQueue<String> queue) { 73 this.category = category; 74 this.imageUrlQueue = queue; 75 } 76 77 @Override 78 public void run() { 79 try { 80 String url = ""; 81 BufferedReader br = null; 82 for (int i = 10; i < 1024; i++) { 83 for (int j = 1; j < 21; j++) { 84 url = "http://www.mm8mm8.com/" + this.category + "/list_" + i + "_" + j + ".html"; 85 System.out.println(Thread.currentThread().getName() + ":" + url); 86 87 StringBuffer content = new StringBuffer(); 88 try { 89 URLConnection connection = new URL(url).openConnection(); 90 connection.connect(); 91 br = new BufferedReader(new InputStreamReader(connection.getInputStream())); 92 String line = null; 93 while ((line = br.readLine()) != null) { 94 content.append(line); 95 } 96 } catch (Exception e) { 97 ThrowableUtil.logThrowable(e); 98 } finally { 99 if (br != null) { 100 try { 101 br.close(); 102 } catch (Exception e) { 103 } 104 } 105 } 106 107 // 已经拿到内容,开始解析url 108 if (content.length() == 0) { 109 // empty 110 break; 111 } else { 112 // 开始解析 113 for (String u : this.getHtmlImageUrlList(content.toString())) { 114 this.imageUrlQueue.put(u); 115 } 116 } 117 } 118 } 119 120 // 完成后,通知 121 latch.countDown(); 122 } catch (Throwable e) { 123 ThrowableUtil.logThrowable(e); 124 } 125 } 126 127 /** 128 * 获取图片url 129 * 130 * @param htmlText 131 * @return 132 */ 133 private List<String> getHtmlImageUrlList(String htmlText) { 134 List<String> list = new ArrayList<String>(); 135 Pattern pattern = Pattern.compile("<img\s*src\s*=\s*"(?<imgUrl>[^\s"'<>]*)""); 136 Matcher matcher = pattern.matcher(htmlText); 137 while (matcher.find()) { 138 list.add(matcher.group("imgUrl")); 139 } 140 return list; 141 } 142 } 143 144 /** 145 * 下载图片用线程 146 * 147 * @author jsliuming 148 * 149 */ 150 public static class DownloadImageRunnable implements Runnable { 151 private String category; 152 private BlockingQueue<String> imageUrlQueue; 153 private File baseFile; 154 155 public DownloadImageRunnable(File path, String category, BlockingQueue<String> queue) { 156 this.category = category; 157 this.imageUrlQueue = queue; 158 baseFile = new File(path, this.category); 159 } 160 161 @Override 162 public void run() { 163 int index = 0; 164 InputStream input = null; 165 ImageOutputStream ios = null; 166 while (fetchTheradRunning || this.imageUrlQueue.size() > 0) { 167 try { 168 String imgurl = null; 169 while (true) { 170 imgurl = this.imageUrlQueue.poll(10, TimeUnit.SECONDS); // 阻塞10秒 171 if (imgurl != null || !fetchTheradRunning) { 172 break; 173 } 174 } 175 if (imgurl == null) { // 如果url为空,那么再次循环 176 continue; 177 } 178 179 URLConnection connection = new URL(imgurl).openConnection(); 180 connection.connect(); 181 input = connection.getInputStream(); 182 ios = new FileImageOutputStream(new File(baseFile, Thread.currentThread().getId() + "_" + index++ + ".jpg")); 183 byte[] buf = new byte[2048]; 184 int n = -1; 185 while ((n = input.read(buf)) > 0) { 186 ios.write(buf, 0, n); 187 } 188 } catch (Throwable e) { 189 ThrowableUtil.logThrowable(e); 190 } finally { 191 if (input != null) { 192 try { 193 input.close(); 194 } catch (Exception e) { 195 } 196 } 197 if (ios != null) { 198 try { 199 ios.close(); 200 } catch (Exception e) { 201 } 202 } 203 } 204 } 205 } 206 } 207 }