生产者消费者模式及实例
- 生产者消费者模式简介:在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。 单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。
- 生产者消费者具体的应用在操作系统里面用的很多,本身详细来说也很复杂,所以直接上代码吧
`public class MusicAdapter extends BaseAdapter{
private Context context;
private List<Music> musics;
private LayoutInflater inflater;
//生成任务集合
private List<ImageLoadTask> tasks=new ArrayList<ImageLoadTask>();
//声明工作线程
private Thread workThread;
private boolean isLoop=true;
private ListView listView;
//声明handler
private Handler handler=new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case HANDLER_IMAGE_LOAD_SUCCESS:
//更新ImageView
ImageLoadTask task=(ImageLoadTask)msg.obj;
//通过position做的tag 找到相应的ImageView
ImageView ivAlbum=(ImageView)listView.findViewWithTag(task.position);
if(ivAlbum!=null){
Bitmap bitmap=task.bitmap;
if(bitmap!=null){
ivAlbum.setImageBitmap(bitmap);
}else{
ivAlbum.setImageResource(R.drawable.ic_launcher);
}
}
break;
}
}
};
public static final int HANDLER_IMAGE_LOAD_SUCCESS=0;
public MusicAdapter(Context context, List<Music> musics, ListView listView) {
this.listView=listView;
this.context=context;
this.musics=musics;
this.inflater=LayoutInflater.from(context);
//初始化workThread
workThread=new Thread(){
public void run() {
//不断的查看集合中是否有数据
while(isLoop){
//如果集合中有图片下载任务
if(!tasks.isEmpty()){
//把第一个图片下载任务获取并执行
ImageLoadTask task=tasks.remove(0);
//直接下载图片
Bitmap bitmap=loadBitmap(task.path);
//把bitmap设置到相应的ImageView中
//需要在主线程中 (发消息给handler)
task.bitmap=bitmap;
Message msg=new Message();
msg.what=HANDLER_IMAGE_LOAD_SUCCESS;
msg.obj=task;
handler.sendMessage(msg);
}else{
//如果任务集合中已经没有任务了
//那么线程等待
synchronized (workThread) {
try {
workThread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
};
workThread.start();
}
/**
* 通过path 发送http请求 下载图片
* @param path
* images/junshengjinshi.jpg
* @return
* @throws IOException
* @throws ClientProtocolException
*/
public Bitmap loadBitmap(String path) {
try {
String url=GlobalConsts.BASEURL+path;
HttpEntity entity=HttpUtils.send(HttpUtils.METHOD_GET, url, null);
//把entity转成Bitmap
byte[] bytes=EntityUtils.toByteArray(entity);
Bitmap bitmap=BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
return bitmap;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public int getCount() {
return musics.size();
}
@Override
public Music getItem(int position) {
return musics.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder=null;
if(convertView==null){
convertView=inflater.inflate(R.layout.item_lv_music, null);
holder=new ViewHolder();
holder.ivAlbum=(ImageView)convertView.findViewById(R.id.ivAlbum);
holder.tvName=(TextView)convertView.findViewById(R.id.tvName);
holder.tvAuthor=(TextView)convertView.findViewById(R.id.tvAuthor);
holder.tvSinger=(TextView)convertView.findViewById(R.id.tvSinger);
holder.tvDuration=(TextView)convertView.findViewById(R.id.tvDuration);
convertView.setTag(holder);
}
holder=(ViewHolder)convertView.getTag();
//给holder中持有的控件 赋值
Music m=getItem(position);
holder.tvName.setText(m.getName());
holder.tvAuthor.setText(m.getAuthor());
holder.tvDuration.setText(m.getDurationtime());
holder.tvSinger.setText(m.getSinger());
holder.ivAlbum.setTag(position);
//向任务集合中添加任务
ImageLoadTask task=new ImageLoadTask();
task.path=m.getAlbumpic();//保存图片路径
task.position=position;
tasks.add(task);
//唤醒工作线程workThread 起来干活
synchronized (workThread) {
workThread.notify();
}
return convertView;
}
class ImageLoadTask{
String path;
Bitmap bitmap;
int position;
}
class ViewHolder{
ImageView ivAlbum;
TextView tvName;
TextView tvAuthor;
TextView tvSinger;
TextView tvDuration;
}
- 这个在写在线音乐播放器的时候遇到的一个问题,本身是一个listview,进行了优化会在每一栏view显示图片,图片从网络获取实时显示(也可以下载到本地,但是在显示的时候也可以使用这种方法,特别是图片很大的时候),由于listview的特性,滑动时会启动当前显示的view的图片下载任务,这个任务包括了链接服务器,发送请求和获取返回的数据。任务消耗的时间很长,listview的滑动又很快,这样会导致不能实时显示(并且会卡爆,因为开了很多线程),所以采用了生产者消费者模式(其实也可以采用异步任务,但是这个以后后面博客会说到)。
- 生产者是getview函数,在定位到当前的view时,将图片的路径和相应参数position存入缓冲区tasks,然后唤醒线程(线程在缓冲区无数据时是等待状态)。
- 下面是仔细分析,首先workthread线程,这个线程是消费者,
workThread=new Thread(){
public void run() {
//不断的查看集合中是否有数据
while(isLoop){
//如果集合中有图片下载任务
if(!tasks.isEmpty()){
//把第一个图片下载任务获取并执行
ImageLoadTask task=tasks.remove(0);
//直接下载图片
Bitmap bitmap=loadBitmap(task.path);
//把bitmap设置到相应的ImageView中
//需要在主线程中 (发消息给handler)
task.bitmap=bitmap;
Message msg=new Message();
msg.what=HANDLER_IMAGE_LOAD_SUCCESS;
msg.obj=task;
handler.sendMessage(msg);
}else{
//如果任务集合中已经没有任务了
//那么线程等待
synchronized (workThread) {
try {
workThread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
};
线程开始的时候就查看缓冲区,如果非空,则去除,并且获得图片,作为通过message传递给主线程,如果没有任务,则等待
//向任务集合中添加任务
ImageLoadTask task=new ImageLoadTask();
task.path=m.getAlbumpic();//保存图片路径
task.position=position;
tasks.add(task);
//唤醒工作线程workThread 起来干活
synchronized (workThread) {
workThread.notify();
这是针对缓冲区的操作,很好理解,存入缓冲区,唤醒线程。ImageLoadTask类型中存储了图片的路径,当前view的position和最后获得的数据,(有另外的方式,可以把position换为imageview,这样可以不用再次定位)。
- 补充部分:MainActivity activity =(MainActivity)context;activity.updateListView(musics);更新view的代码