Java Thread Pool

介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用。

new Thread的弊端

执行一个异步任务我们一般都是这样:

new Thread(new Runnable() { 
    @Override
    public void run() {
        // TODO Auto-generated method stub
    }
}).start();

弊端如下:

  • 每次new Thread新建对象性能差。
  • 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
  • 缺乏更多功能,如定时执行、定期执行、线程中断。

相比new Thread,Java提供的四种线程池的好处在于:

  • 重用存在的线程,减少对象创建、消亡的开销,性能佳。
  • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
  • 提供定时执行、定期执行、单线程、并发数控制等功能。

Java 线程池

Java通过Executors提供四种线程池,分别为:

  • newCachedThreadPool: 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool: 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool: 创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

1. newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:

ExecutorService chacheThreadPool = Executors.newCachedThreadPool();
      for(int i = 0; i < 10; i++) {
           final int index = i;
           try{
                Thread.sleep(index * 1000);
           } catch (InterruptedException e) {
                e.printStackTrace();
           }
   
           chacheThreadPool.execute(new Runnable() {
    
            @Override
            public void run() {
                 // TODO Auto-generated method stub
                 System.out.println(index); 
            }
       });
  }

线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

2. newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
  for (int i = 0; i < 10; i++) {
      final int index = i;
      fixedThreadPool.execute(new Runnable() {
   
          @Override
          public void run() {
              try {
                  System.out.println(index);
                  Thread.sleep(2000);
              } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
          }
      });
  }

因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。 定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。

3. newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
  scheduledThreadPool.schedule(new Runnable() {
   
      @Override
      public void run() {
       System.out.println("delay 3 seconds");
      }
  }, 3, TimeUnit.SECONDS);

表示延迟3秒执行。

定期执行示例代码如下:

scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
 
    @Override
    public void run() {
        System.out.println("delay 1 seconds, and excute every 3 seconds");
    }
}, 1, 3, TimeUnit.SECONDS);

表示延迟1秒后每3秒执行一次。

ScheduledExecutorService比Timer更安全,功能更强大,后面会有一篇单独进行对比。

4. newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
  for (int i = 0; i < 10; i++) {
      final int index = i;
      singleThreadExecutor.execute(new Runnable() {
   
          @Override
          public void run() {
              try {
                  System.out.println(index);
                  Thread.sleep(2000);
              } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
          }
      });
  }

结果依次输出,相当于顺序执行各个任务。

现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作。

综上所述的4中方式,主要有以下几种见解:

  1. 第一种方式跟第四种方式,运行了之后,发现结果是一样的,那是不是说这两种线程池的使用是一样的呢?如果是一样的,为什么会有两种而不是一种?其实大家仔细看下一开始这两个方法的介绍就知道了,第一种:newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这里觉得它可能是并发且是无序的。第四种:newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。是一个单线程的线程池,个人觉得它是有序的,因为有优先级。

  2. 第二种方式,可指定线程池的大小,并且每隔一段时间执行多少数据。比如上面的例子就是:因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。相信大家都能够理解了。

  3. 第三种方式就更简单了,这里介绍了两种情况,一种是只执行一次的情况,一种是延迟几秒再每隔几秒执行一次。可能这样说大家不太明白,我举个简单的例子。比如现在很多应用的首页有一个广告部分,每隔几秒后就会自动播放下一张图片,这里用的线程就是此线程。其实很多Android开发遇到这种情况,都会首先想到用定时器(Timer),其实不然,很多帖子都说明了,这种方式比用Timer更好。


Builder Pattern In Android

看到一篇个人感觉不错的文章,分享出来。

When I started with android, I almost always asked Google how to do the smallest things. I want to highlight the examples I found for starting an activity.

Intent intent = new Intent(this, SomeOtherActivity.class);
startActivity(intent);

I always wondered why many examples used a variable called intent. You could easily create the Intent on-the-fly in the startActivity() parameter. Maybe it looks better, if you need to add some parameters to the intent like this?

Intent intent = new Intent(this, SomeOtherActivity.class);
intent.putExtra("param1", extraInfo1);
intent.putExtra("param2", extraInfo2);
startActivity(intent);

But then again, the method putExtra() returns an Intent. As described in the example for method-chaining, such chaining can be relatively useful. Androids call to startActivity() could be rewritten to this:

startActivity(new Intent(this, SomeOtherActivity.class)
       .putExtra("param1", extraInfo1)
       .putExtra("param2", extraInfo2));

Since this doesn’t need a variable, I like that approach more. In Android, this pattern is used quite often. The so called builder-pattern is used with AlertDialog for example. When built, you use the create() method in order to show the dialog itself (equals the build() method of the builder-pattern). To create URIs one can use the buildUpon() method to get a Uri.Builder. There are tons of other examples where Android uses such builders. Intents seem to be an exception, since there is no startActivity() function or similar to it. Returning the object itself was not even done consequently, to make chaining of some methods impossible. Examples for these are setSourceBounds() or setExtrasClassLoader().

I see two options to address these problems:

  • Creation of a sub-class of Intent, which provides a startAsActivity() function. It needs to return its own type in every putExtra() method, to preserve the startAsActivity() method. This class could look like this:
public class BuilderIntent extends Intent {
    private final Context context;
 
    public BuilderIntent(Context ctx, Class<?> cls) {
        super(ctx, cls);
        this.context = ctx;
    }
 
    @Override
    public BuilderIntent putExtra(String name, String value) {
        super.putExtra(name, value);
        return this;
    }
 
    // ... more putExtra-methods ...
 
    public void startAsActivity() {
        context.startActivity(this);
    }
}
 
// Usage:
new BuilderIntent(this, SomeOtherActivity.class)
        .putExtra("param1", "info1")
        .putExtra("param2", "info2")
        .startAsActivity();

The problem with this class is, that methods like public void setExtrasClassLoader() cannot be changed to return the BuilderIntent type.

  • An alternative would be the usage of an external builder, which uses its own Intent internally. Take all functions you want to provide and change them for your requirements, somehow similar to the Decorator-Pattern.
public static class ActivityBuilder {
    private final Context context;
    private final Intent intent;
 
    private ActivityBuilder(Context context, Class<? extends Activity> activity) {
        this.context = context;
        intent = new Intent(context, activity);
    }
 
    public ActivityBuilder put(String param, Long x) {
        intent.putExtra(param, x);
        return this;
    }
 
    // ... more required methods ...
 
    public void start() {
        context.startActivity(intent);
    }
}

The advantage is that you can setup a public ActivityBuilder setExtrasClassLoader() method without hassle. The problem is, that you cannot use methods from Intent at all. With the first option, that would be cumbersome but at least possible.

What I wanted to say is that a method like startAsActivity() seems to be missing. However, you can create your own builders around existing classes.


设计模式之建造者模式

Builder模式定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

Builder模式是一步一步创建一个复杂的对象,它允许用户可以只通过指定复杂对象的类型和内容就可以构建它们。用户不知道内部的具体构建细节。Builder模式是非常类似抽象工厂模式,细微的区别大概只有在反复使用中才能体会到。

为何使用建造者模式

是为了将构建复杂对象的过程和它的部件解耦。注意:是解耦过程和部件。

因为一个复杂的对象,不但有很多大量组成部分,如汽车,有很多部件:车轮、方向盘、发动机,还有各种小零件等等,部件很多,但远不止这些,如何将这些部件装配成一辆汽车,这个装配过程也很复杂(需要很好的组装技术),Builder模式就是为了将部件和组装过程分开。

如何使用建造者模式

首先假设一个复杂对象是由多个部件组成的,Builder模式是把复杂对象的创建和部件的创建分别开来,分别用Builder类和Director类来表示。

首先,需要一个接口,它定义如何创建复杂对象的各个部件:

public interface Builder {
 //创建部件A  比如创建汽车车轮
 void buildPartA();
 //创建部件B 比如创建汽车方向盘
 void buildPartB();
 //创建部件C 比如创建汽车发动机
 void buildPartC();
 //返回最后组装成品结果 (返回最后装配好的汽车)
 //成品的组装过程不在这里进行,而是转移到下面的Director类中进行.
 //从而实现了解耦过程和部件
 Product getResult();
}

用Director构建最后的复杂对象,而在上面Builder接口中封装的是如何创建一个个部件(复杂对象是由这些部件组成的),也就是说Director的内容是如何将部件最后组装成成品:

public class Director {
 private Builder builder;
 public Director( Builder builder ) {
  this.builder = builder;
 }
 // 将部件partA partB partC最后组成复杂对象
 //这里是将车轮 方向盘和发动机组装成汽车的过程
 public void construct() {
  builder.buildPartA();
  builder.buildPartB();
  builder.buildPartC();
 }
}

Builder的具体实现ConcreteBuilder: * 通过具体完成接口Builder来构建或装配产品的部件; * 定义并明确它所要创建的是什么具体东西; * 提供一个可以重新获取产品的接口。

public class ConcreteBuilder implements Builder {
 Part partA, partB, partC;
 public void buildPartA() {
  //这里是具体如何构建partA的代码
 };
 public void buildPartB() {
  //这里是具体如何构建partB的代码
 };
 public void buildPartC() {
  //这里是具体如何构建partB的代码
 };
 public Product getResult() {
  //返回最后组装成品结果
 };
}

复杂对象:产品Product:

public interface Product { }

复杂对象的部件:

public interface Part { }

我们看看如何调用Builder模式:

ConcreteBuilder builder = new ConcreteBuilder();
Director director = new Director( builder );
director.construct();
Product product = builder.getResult();

通过上面我们可以看到:建造者模式的好处就是使得建造代码与表示代码分离,由于建造者隐藏了该产品是如何组装的,所以若需要改变一个产品的内部表示,只需要再定义一个具体的建造者就可以了。


Java Ten Object Oriented Design Principles

看到一篇比较好的文章,故记录在此。

英文原文: 10-object-oriented-design-principles(友情提示,需翻墙)

面向对象理论是面向对象编程的核心,但是我发现大部分 Java 程序员热衷于像单例模式、装饰者模式或观察者模式这样的设计模式,而并没有十分注意学习面向对象的分析和设计。学习面向编程的基础(如抽象,封装,多态,继承等)是非常重要的,而运用它们来设计干净的模块也同样重要。我也认识很多不同等级的程序员,他们没有听过这些面向对象理论,或者不知道某个设计理论有什么好处,或者如何在编码中使用这些设计理论。

我们起码要设计出高度一致而且松散耦合的代码。Apache 和 Sun 的源代码就是学习 Java 面向对象理论的非常好的例子。JDK 遵循了一些设计模式,譬如在 BorderFactory 中使用工厂模式,Runtime 类中使用单例模式,java.io 中的许多类中使用装饰者模式。如果你真的对 Java 编程感兴趣,请先阅读 Joshua Bloch 的 Effective Java,正是他参与编写了 Java API。另外两本我喜欢的关于设计模式的书还有,Kathy Sierra 等编写的的 Head First Design Pattern 和 Head First Object Oriented Analysis and Design。这些书帮助理解面向对象理论,并帮助我写出更好的代码。

学习任何设计理论或模式最好的方法就是现实世界中的例子,这篇文章只是要给还在学习阶段的程序员介绍面相对象理论。我想以下每一条都需要用一篇文章来详细介绍,我以后也会逐一介绍的,只是现在先来个快速浏览一下。

避免重复,DRY (Don’t repeat yourself)

面相对设计理论的第一条就是避免重复,不要写重复的代码,尽量将共同的功能放在一个地方。如果你准备在不同地方写同一段代码,那么只写一个方法。如果你不止一次硬编码某个值,那么将其声明成 public final 常量。这么做的好处就是容易维护。但是不要滥用这一条,重复不是指代码的重复,而是指功能的重复。譬如你有一段相同的代码来验证 OrderID 和 SSN,但它们代表的意义并不相同。如果你将两个不同的功能合并在一起,当 OrderID 更改了格式之后,那么检验 SSN 的代码就会失效。所以要警觉这种耦合,不要讲任何相似但不相关的代码合并在一起。

将变化封装起来

在软件领域唯一不变的就是“变化”。所以最好将你觉得将来会有改变的代码封装起来。这样做的好处就是更容易测试和维护正确的被封装的代码。你应该先将变量声明成 private,然后有需要的话再扩大访问权限,如将 private 变成 protected。Java 中很多设计模式都使用了封装,工厂设计模式就是封装的一个例子,它封装了对象的创建,如果要引入新的“产品”,也不必更改现有的代码。

开放且封闭的设计原则(Open Closed Design Principle)

类、方法以及功能应该对扩展开放(新的功能),而对更改封闭。这是另一个优美的”SOLID”设计理论,这保证了有人更改已经经过测试了的代码。如果你要加入新的功能,你必须要先测试它,这正是开放且封闭的设计理论的目标。另外,Open Closed principle 正是 SOLID 中的O的缩写。

单一责任原则(Single Responsibility Principle (SRP))

单一责任原理是另外一条”SOLID”设计理论,代表其中的“S”。每次一个类只有一个更改的原因,或者一个类只应该完成单一的功能。如果你将多过一个功能放在一个类中,它会将两个功能耦合在一起,如果你改变了其中的一个功能,可能会破坏另外一个功能,这样便需要更多的测试以确保上线时不出现什么岔子。

依赖注入或反转原则

容器会提供依赖注入,Spring 非常好的实现了依赖注入。这条原理的美妙之处在于,每个被注入的类很容易的测试和维护,因为这些对象的创建代码都集中在容器中,有多种方法都可以进行依赖注射,譬如一些 AOP 框架如 AspectJ 使用的字节码注入(bytecode instrumentation),以及 Spring 中使用的代理器(proxy)。来看看这个依赖注射的例子吧。这一条正是 SOLID 中的”D”。

多用组合,少用继承

如果可能的话,多用组合,少用继承。可能有的人会不同意,但我确实发现组合的灵活性高过继承。组合可以在运行时通过设置某个属性以及通过接口来组合某个类,我们可以使用多态,这样就能随时改变类的行为,大大提高了灵活性。Effective Java 也更倾向于使用组合。

Liskov 替代原则(Liskov Substitution Principle (LSP))

根据 Liskov 替代原理,子类必须可以替代父类,也就是使用父类的方法,也能够没有任何问题的和子类对象也兼容。LSP 和单一责任原则以及接口分离原则的关系紧密。如果一个类比子类的功能要多,子类不能支持父类中的某些功能的话,就违反了 LSP。为了遵循 LSP 原理,子类需要改进父类的功能,而不是减少功能。LSP 代表 SOLID 中的”L”。

接口分离原则(Interface Segregation principle (ISP))

接口分离理论强调,如果客户端没有使用一个接口的话,就不要实现它。当一个接口包含两个以上的功能,如果客户端仅仅需要其中某个功能,而不需要另外一个,那么就不要实现它。接口的设计是件非常复杂的工作,因为一旦你发布了接口之后,就再也无法保证不破坏现有实现的情况下更改接口。分离接口的另一个好处就是,因为必须要实现方法才能使用接口,所以如果仅仅只有单一的功能,那么要实现的方法也会减少。

针对接口编程,而不是针对实现编程

尽量针对接口编程,这样如果要引入任何新的接口,也有足够的灵活性。在变量的类型、方法的返回类型以及参量类型中使用接口类型。很多程序员都建议这么做,包括 Effective Java 和 head first design pattern 等书。

代理原则(Delegation principle)

不要所有的事情都自己做,有时候要将任务代理给相应的类去做。运用代理模式最经典的例子就是 equals ()和 hashCode ()方法。为了比较两个对象的相等与否,我们没有用客户端代码去比较,而是让对象自己去比较。这么做的好处就是减少代码的重复,更容易更改行为。

  所有的这些面相对象理论都能帮助你写出更灵活、高度一致且低耦合的代码。理论是第一步,更重要的是运用这些设计理论的能力。找出违反这些设计理论的地方,但是就像这个世界上没有什么是完美的一样,不要尝试着用设计模式和理论解决一切问题,因为它们往往是针对大型的企业级项目,有着更长的运行周期。换句话说小型的项目不一定值得这么做。


Android调用系统图库

上面一篇讲到Android调用系统相机时遇到的兼容性问题,没想到选择系统图库的时候竟然也遇到了系统兼容性问题,郁闷之极无以言表,在这里记录下解决方案吧。

首先是调用系统默认图库代码:

Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, SELECT_PHOTOS_REQUEST_CODE);

下面是关键的拿到图片的处理代码:

public void onActivityResult(int requestCode, int resultCode, Intent data) {
	super.onActivityResult(requestCode, resultCode, data);
	switch (requestCode) {
	case SELECT_PHOTOS_REQUEST_CODE:
	    if (resultCode == RESULT_OK) {
			Uri uri = data.getData();
			// 取得返回的Uri,基本上选择照片的时候返回的是以Uri形式,但是在拍照中有得机子呢Uri是空的,所以要特别注意
			if (uri != null) {
				Bitmap image;
				try {
					// 这个方法是根据Uri获取Bitmap图片的静态方法
					image = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
					postImage.setImageBitmap(image);
					imageLayout.setVisibility(View.VISIBLE);
					mUri = uri;
				} catch (Exception e) {
					e.printStackTrace();
				}
			} else {
				Bundle extras = data.getExtras();
				if (extras != null) {
					// 这里是有些拍照后的图片是直接存放到Bundle中的所以我们可以从这里面获取Bitmap图片
					Bitmap image = extras.getParcelable("data");
					if (image != null) {
						postImage.setImageBitmap(image);
						imageLayout.setVisibility(View.VISIBLE);
						mUri = BitmapUtil.getImageUri(ctx, image);
					}
				}
			}
		}
		break;
	}
}