您的位置:首页 > 编程语言 > Java开发

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 语言 Worker 多线程