Worker那些事儿
2016-04-11 23:16
459 查看
Worker那些事儿
Worker之前世今生
不知道是谁发明了worker这个单词,百度解释如下:劳动者; 工人,员工; [虫] 工蜂,工蚁…,看名字都是一些受苦的角色。也不知道是谁把worker这个词引入了软件行业,几乎任何语言中都有worker线程的存在。有的语言中只是概念性的存在,有的语言则干脆定义的worker的API。如:Java GUI库Swing中的SwingWorker,C#中的BackgroundWorker,Android中的AsyncTask,HTML5中的Web Worker,…。
上文中提到了“worker线程”。是的,有了多线程,才有了worker这个词。编程语言的世界如此,其他行业亦如此。
众所周知,为了提高计算机资源的利用率,操作系统引入了进程/线程的概念。线程也被称作“轻量级进进程”,在大多数现代操作系统中,都是以线程为最基本的调度单位。通常来说,使用线程有如下的优势:
发挥多处理器的强大威力
简化问题建模方式
简化异步事件的处理方式
响应更灵敏的用户界面
看起来很美好,其实操作系统引入多线程是一把双刃剑。自从多线程出现后,就和另外一个词形影不离,那就是“线程安全”。看下最经典的非线程安全例子:
非线程安全的序列号生成器
public class UnSafeSessionGenerator { private int sessionID; public int getNextSessionID() { return sessionID++; } }
如果执行时机不对,那么两个线程在调用
getNextSessionID方法时会得到相同的值(100)。虽然自增运算
sessionID++看上去是单个操作,但事实上包含三个独立的操作:读取
sessionID,将
sessionID加1,并将运算结果写入
sessionID。
错误执行结果示意图
线程封闭技术
如何保证程序的线程安全性,不是本文讨论的重点。但是,从上面的例子中可以得出一个很朴素的结论:如果仅在单线程内部访问数据则可以保证线程的安全性。这种朴素的技术被称为“线程封闭”,它是实现线程安全性的最简单方式之一。在UI相关的程序设计中大量使用了线程封闭技术,UI使用一个事件分发线程对界面操作进行封闭,实现界面安全访问的同时减少用户界面的阻塞时间。用现在一句流行的话说就是“UI线程负责貌美如花,worker(s)负责赚钱养家”。下面,分别看下Swing、Android、Html5中Worker是怎么工作的。Swingworker
有如下需求:执行某耗时操作,每隔5秒更新一次界面上进度条通知客户。不使用SwingWorker的代码:
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class UpdateProgressBarInEDT extends JDialog implements ActionListener { private final JProgressBar progressBar; public UpdateProgressBarInEDT(){ setTitle("Unuse SwingWorker"); setLayout( new BoxLayout(getContentPane(),BoxLayout.Y_AXIS)); progressBar = new JProgressBar(1, 10); progressBar.setPreferredSize(new Dimension(200, 60)); add(progressBar); JPanel footerPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JButton okBtn = new JButton("OK"); footerPanel.add(okBtn); okBtn.addActionListener(this); okBtn.setActionCommand("OK"); JButton cancelBtn = new JButton("Cancel"); footerPanel.add(cancelBtn); add(footerPanel); cancelBtn.addActionListener(this); cancelBtn.setActionCommand("Cancel"); setSize(200, 100); setDefaultCloseOperation(DISPOSE_ON_CLOSE); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { UpdateProgressBarInEDT progressBarInEDTDemo = new UpdateProgressBarInEDT(); progressBarInEDTDemo.setVisible(true); progressBarInEDTDemo.setLocationRelativeTo(null); } }); } @Override public void actionPerformed(ActionEvent e) { if(e.getActionCommand().equals("OK")) { for (int i = 0; i < 10; i++) { proceed(i); snap(5000); } } if(e.getActionCommand().equals("Cancel")) { initProgress(); } } private void initProgress() { progressBar.setValue(0); } private void proceed(int process) { progressBar.setValue(progressBar.getValue() + process); } private void snap(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } }
尝试运行下这段代码会发现,点击【OK】按钮后,界面即失去响应,进度条没有按照我们的预期每隔5秒前进10%。并且【Cancel】按钮也无法响应。
使用SwingWorker的代码:
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; public class UpdateProgressBarInEDT extends JDialog implements ActionListener { private final JProgressBar progressBar; private SwingWorker<Void, Integer> swingWorker; public UpdateProgressBarInEDT() { setTitle("Unuse SwingWorker"); setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); progressBar = new JProgressBar(0, 10); progres 10eca sBar.setPreferredSize(new Dimension(200, 60)); add(progressBar); JPanel footerPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JButton okBtn = new JButton("OK"); footerPanel.add(okBtn); okBtn.addActionListener(this); okBtn.setActionCommand("OK"); JButton cancelBtn = new JButton("Cancel"); footerPanel.add(cancelBtn); add(footerPanel); cancelBtn.addActionListener(this); cancelBtn.setActionCommand("Cancel"); swingWorker = buildSwingWorker(); setSize(200, 100); setDefaultCloseOperation(DISPOSE_ON_CLOSE); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { UpdateProgressBarInEDT progressBarInEDTDemo = new UpdateProgressBarInEDT(); progressBarInEDTDemo.setVisible(true); progressBarInEDTDemo.setLocationRelativeTo(null); } }); } @Override public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("OK")) { if(swingWorker.isCancelled() || swingWorker.isDone()) { swingWorker = buildSwingWorker(); } swingWorker.execute(); } if (e.getActionCommand().equals("Cancel")) { swingWorker.cancel(true); initProgress(); } } private SwingWorker<Void, Integer> buildSwingWorker() { return new SwingWorker<Void, Integer>() { @Override protected Void doInBackground() throws Exception { for (int i = 1; i <= 10; i++) { publish(i); snap(5000); } return null; } @Override protected void process(List<Integer> chunks) { for (Integer chunk : chunks) { proceed(chunk); } } }; } private void initProgress() { progressBar.setValue(0); } private void proceed(int process) { progressBar.setValue(progressBar.getValue() + process); } private void snap(int millis) throws InterruptedException { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); throw e; } } }
运行上面的代码,点击【OK】按钮后,进度条按照我们的预期每隔5秒前进10%。点击【Cancel】按钮后进度条归零。再次点击【OK】按钮后,进度条重新开始前进。
AsyncTask
上述的需求,我们在Andrioid里实现一次。不使用AsyncTask,在Actiity里直接控制进度条。
布局代码:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <ProgressBar android:id="@+id/progress" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="30dp" android:max="10" android:progress="0" /> <LinearLayout android:orientation="horizontal" android:gravity="end" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/ok" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ok_text" android:onClick="handleOK"/> <Button android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/cancel_text" android:onClick="handleCancel"/> </LinearLayout> </LinearLayout>
MainActivity:
import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.ProgressBar; public class MainActivity extends Activity { private ProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); progressBar = (ProgressBar) findViewById(R.id.progress); } public void handleOK(View view) { for (int i = 0; i < 10; i++) { progressBar.incrementProgressBy(1); snap(5000); } } public void handleCancel(View view) { progressBar.setProgress(0); } private void snap(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } }
与Swing的第一个例子表现一致,点击【OK】按钮后App界面直接无响应了。
使用AsyncTask改写:
import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.widget.ProgressBar; public class MainActivity extends Activity { private ProgressBar progressBar; private AsyncTask<Void,Integer,String> asyncTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); progressBar = (ProgressBar) findViewById(R.id.progress); asyncTask = buildAsyncTask(); } private AsyncTask<Void,Integer,String> buildAsyncTask() { return new AsyncTask<Void,Integer,String>() { @Override protected String doInBackground(Void... params) { for (int i = 0; i < 10; i++) { publishProgress(i); snap(5000); } return null; } @Override protected void onProgressUpdate(Integer... values) { for (Integer progress : values) { progressBar.setProgress(progress); } } }; } public void handleOK(View view) { if(asyncTask.isCancelled()) { asyncTask = buildAsyncTask(); } asyncTask.execute(); } public void handleCancel(View view) { asyncTask.cancel(true); progressBar.setProgress(0); } private void snap(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } }
这次,进度条可以正常前进和取消了。
Web Worker
当在HTML页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成。Web worker是运行在后台的JavaScript,独立于其他脚本,不会影响页面的性能。用户可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 在后台运行。
在html/js中,我们再次实现进度条需求。
不使用Webworker:
<!DOCTYPE HTML> <html> <head> <style type="text/css"> #center { margin: 50px auto; width: 400px; } #loading { width: 397px; height: 49px; background: url(back.png) no-repeat; } #loading div { width: 0px; height: 48px; background: url(proceed.png) no-repeat; color: #fff; text-align: center; font-family: Tahoma; font-size: 18px; line-height: 48px; } </style> <script type="text/javascript" src="jquery.js"> </script> <script type="text/javascript"> function setProgress(progress){ if (progress >= 0) { $("#loading > div").css("width", progress + "%"); $("#loading > div").html(progress + "%"); } } var i = 0; function doProgress(){ while (i < 10) { sleep(5000); setProgress((i+1)*10); i++; } } function sleep(numberMillis){ var now = new Date(); var exitTime = now.getTime() + numberMillis; while (true) { now = new Date(); if (now.getTime() > exitTime) return; } } function cancelProgress(){ setProgress(0); } </script> </head> <body> <div id="center"> <div id="loading"> <div> </div> <input type = "button" value="OK" style="width:100px" onclick="doProgress()"/> <input type = "button" value="Cancel" style="width:100px" onclick="cancelProgress()"/> </div> </div> </body> </html>
在浏览器中打开此页面,点击【OK】按钮,进度条没有按照我们的预期每隔5秒前进10%,而是50秒后一次冲到了100%。
使用Webworker改写:
worker.js
var i = 0; function doProgress(){ while (i < 10) { sleep(5000); postMessage((i + 1) * 10); i++; } } function sleep(numberMillis){ var now = new Date(); var exitTime = now.getTime() + numberMillis; while (true) { now = new Date(); if (now.getTime() > exitTime) return; } } doProgress();
页面代码:
<!DOCTYPE HTML> <html> <head> <style type="text/css"> #center { margin: 50px auto; width: 400px; } #loading { width: 397px; height: 49px; background: url(back.png) no-repeat; } #loading div { width: 0px; height: 48px; background: url(proceed.png) no-repeat; color: #fff; text-align: center; font-family: Tahoma; font-size: 18px; line-height: 48px; } </style> <script type="text/javascript" src="jquery.js"> </script> <script type="text/javascript"> function setProgress(progress){ if (progress >= 0) { $("#loading > div").css("width", progress + "%"); $("#loading > div").html(progress + "%"); } } function doProgress(){ var w = new Worker("worker.js"); w.onmessage = function(event){ setProgress(event.data) }; } function cancelProgress(){ setProgress(0); } </script> </head> <body> <div id="center"> <div id="loading"> <div> </div> <input type = "button" value="OK" style="width:100px" onclick="doProgress()"/> <input type = "button" value="Cancel" style="width:100px" onclick="cancelProgress()"/> </div> </div> </body> </html>
在浏览器中再次打开此页面,点击【OK】按钮,进度条按照我们的预期前进了。
也许你会说,实现Worker进程和UI进程交互,其实不需要Worker:
- Swing里,可以用自定义线程+
invokeLater(new Runnable(){...})方式而不使用SwingWorker;
- 在Andriod里,可以使用自定义线程+
handler.sengMessage或
runOnUiThread而不使用AsyncTask
- js的武器库就更强大了,各种开源库,
Ajax,
Web Worker好像更没有存在的必要。
除了网络上的各种资料对上述几种Worker好处的介绍,笔者认为
Worker存在以及使用
Worker的主要好处在于:
- 减少重复。Worker对后台进程和UI进程交互行为进行了建模、封装,避免语言的使用者到处实现;
- 减少犯错。毕竟这个行业里有很多蹩脚的程序员,能让程序员想的、做的少一点,就尽量少一点吧;
- 职责分离。使用Worker,将后台处理和UI处理分离,实现了SOLID中最重要的单一职责原则。
总结
不同的技术,解决相似问题的思路总是相似的;学习不同技术时,要善于比较,思考各种技术的异同
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- Python3写爬虫(四)多线程实现数据爬取
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序