java多线程编程简介

1. Java多线程的实现方式

Java中提供如下三种方式的多线程实现方式,

a) 继承Thread类:实现run()方法。
b) 继承Runnable接口:实现run()方法,比起Thread类更加灵活,可以实现多个线程共享数据。
c) 继承Callable接口:实现call()方法,比起Thread和Runnable,可以获取各个线程的返回值。

代码样例如下(具体的演示样例请参见multithread项目中的demo1),

 // BaseThread继承自Thread类
 BaseThread t1 = new BaseThread("Thread-1");
 BaseThread t2 = new BaseThread("Thread-2");
 t1.start();
 t2.start();
 
 // BaseRunable继承自Runnable接口,线程r1/r2共享run对象的数据
 BaseRunable run = new BaseRunable();
 Thread r1 = new Thread(run, "Thread-run-1");
 Thread r2 = new Thread(run, "Thread-run-2");
 r1.start();
 r2.start();
 
 // BaseCallback继承自Callable接口,在执行完毕后可以获取执行结果
 ExecutorService exec = Executors.newCachedThreadPool(); 
 Future<Integer> result = exec.submit(new BaseCallback(100));
 
 while(!result.isDone()){
 App.sleep(1);
 }
 
 try {
 App.logMessage("result: " + result.get());
 } catch (Exception e) {
 e.printStackTrace();
 }
 exec.shutdown();

2. Java多线程的同步Synchronized

为了控制多个线程对同一代码区的访问,Java语言提供了Synchronized关键字给对象、方法或者代码块进行加锁,加了锁的代码块在同一时间只能允许一个线程执行。

在如下的类中run()方法中加了Synchronized关键字,这将会使得第一个进入run()方法的线程打印完所有的count数字(从10到1),而后进入的其它线程将不会输出任何信息(因为count为0),注:假设所有线程使用同一个run对象初始化。

public class BaseRunableSync implements Runnable {

 private int count = 10;
 
 public synchronized void run() {
 for( ; count>0; count--) {
 App.logMessage("count=" + count);
 }
 }

}

具体的演示样例请参见multithread项目中的demo2

3. Java多线程的阻塞(Sleep/Wait)

在多线程编程中,很多时候不仅需要利用多线程充分使用资源,也要需要释放资源。Thread.Sleep和Object.wait就是在线程运行时主动释放CPU资源或者同步锁资源。

Thread.Sleep:当前线程进入阻塞模式,释放CPU资源,但是不释放同步锁。

Object.Wait:当前线程进入阻塞模式,释放CPU资源并释放同步锁。注意Object.wait必须和Synchronized同步配合使用,否则因为没有同步锁释放而导致抛异常。

具体的演示样例请参见multithread项目中的demo3,分别有如下的演示,

a)  runnable + synchronized :同步区没有任何阻塞
b) runnable + synchronized + sleep:同步区加sleep,线程被阻塞到等待池
c)  runnable + synchronized + wait:同步区加wait,线程被阻塞到等待池

4. Java多线程的合并(join)

Java程序起来后,有一个main的主线程,其它线程都是从该主线程诞生出子线程,如果子线程运行时间比较长,很有可能的情况是,主线程已经结束但子线程还在运行。

为了能够让主线程能够等待子线程的结束,可以使用thread.join()方法,使得当前线程阻塞到等待池,一直等待子线程执行完毕,

public class App {

 public static void main(String[] args) throws InterruptedException {
 
 App.logMessage("start...");
 
 BaseRunable run = new BaseRunable();
 Thread r1 = new Thread(run);
 Thread r2 = new Thread(run);
 r1.start();
 r2.start();
 
 // add the join method, which wait for r1/r2 to be finished
 r1.join();
 r2.join();
 
 App.logMessage("the end.");
 
 }
 
}

上述代码中,如果没有r1.join()和r2.join(),主线程很有可能在r1和r2执行完之前就已经结束。

具体代码样例请参见multithread项目中的demo4

5. Java多线程的死锁

多线程中,如果执行代码有多个资源锁,如果执行顺序不当,就会形成死锁。比如两个子线程,互相握有对方需要的对象锁并不释放,则会形成死锁。

public class DeadlockRunable implements Runnable {

 private Object lock1 = new Object();
 private Object lock2 = new Object();
 private Boolean bLockFlag = Boolean.FALSE;
 
 public void run() {
   bLockFlag = !bLockFlag;
   if(bLockFlag){

     synchronized(lock1){
       App.logMessage("lock1 is locked...");
       App.sleep(2);
       synchronized(lock2){
         App.logMessage("lock2 is locked...");
       }
     }
 
   }
   else {
 
       synchronized(lock2){
         App.logMessage("lock2 is locked...");
         App.sleep(2);
         synchronized(lock1){
          App.logMessage("lock1 is locked...");
         }
       }
 
   }
 }

}

一个死锁代码样例请参见multithread项目中的demo5

6. Java多线程的状态

Java多线程有如下几种状态,

新建(NEW)
就绪/可运行/在运行(RUNNABLE)
阻塞(BLOCKED)
等待(WAITING/TIMED_WAITING)
结束(TERMINATED)

线程在各个状态的流转图见如下,

thread_status

代码样例

代码仓库地址:http://git.oschina.net/pphh/tools,可以通过如下git clone命令获取仓库代码,

git clone git@git.oschina.net:pphh/tools.git

上述代码样例在文件路径tools\java\multithread中。

设计模式之观察者模式(observer)

观察者模式定义

在对象之间定义定义一对多的依赖关系,使得一个对象的变化可以自动地通知到所依赖的其它对象。

该模式的UML类图可以表达如下,

designPattern图中被观察者(Observerable)和观察者(Observer)之间形成一对多的关系,其中,

Observerable:可被观察的对象,属于一对多关系中的一。
Observer:希望接受被观察的对象变化通知的对象,属于一对多关系中的多。

基本的交互过程为,

1、Observerable持有注册的观察者列表,观察者的注册和注销通过attach()和detach()完成。
2、当Observerable发生变化时,将会通过notify()通知注册的所有观察者。
3、观察者通过接口onNotification()来获取变化通知,如果不想接受变化通知,可以随时通过detach方法注销观察。

被观察者和观察者之间的消息通知通过如下几个基本接口定义来实现,

Observerable.attach():注册观察者,添加一个观察者到通知列表。
Observerable.detach():注销观察者,从通知列表中删除一个观察者。Observerable.notify():通知所有观察者,将会调用观察者的onNotification接口。
Observer.onNotification():观察者获取通知消息的接口。

除此之外,观察者模式并不需要其它更多接口,从而实现了被观察者和观察者的松耦合。

简单的Java接口实现

Java中可以通过接口来实现观察者模式,UML类图如下,

dp_sample1

代码样例见文章结尾。

通过Java自带的实用工具类库

在JDK自带的实用工具类库(Java.util.Observer)有对观察者模式的支持,UML类图可以表达如下,

dp_sample2

其中Observerable是一个抽象类,并不是一个接口,也意味着被观察的主题不能继承其它抽象类了,这也是其应用的缺点。

代码样例见文章结尾。

自定义观察者和监听者

为了方便的使用观察者,有时候需要自定义的观察者监听者,提供更加清晰易懂的方法接口。如下UML类图中,

dp_sample3x

其添加了自定义的监听者(AbstractAssertListener和AbstractLogListener),分别提供onAssert()和onLog()方法,用于接受当发生assert/log事件的通知消息。

代码样例见文章结尾。

代码样例

代码仓库地址:http://git.oschina.net/pphh/designPatterns,可以通过如下git clone命令获取仓库代码,

git clone git@git.oschina.net:pphh/designPatterns.git

上述代码样例在文件路径designPatterns\java\observer中。

参考资料

《设计模式-可复用面向对象软件的基础》

《Head First 设计模式》

百度百科:http://baike.baidu.com/view/1854779.htm

设计模式之单例模式(singleton)

单例模式的定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

一个简单Java实现

public class Singleton {
 private static Singleton instance;
 
 private Singleton() {
 }
 
 public static Singleton getInstance() {
 
   if(instance == null) {
     instance = new Singleton();
   }
   return instance;
 }
}

在上述Java实现中,单例对象保存在一个类静态对象,类的构造函数声明为私有,要想使用这个类,必须调用getInstance方法获取全局唯一的静态单例对象。

这个简单的方法主要的缺点是没有考虑到多线程的情况,在多线程运行过程中,很有可能多个线程一开始同时进入getInstance方法,从而使得单例对象被初始化多次,线程获取到的单例并不一致。

一个解决方法是将getInstance方法声明为线程同步,请看下面的样例。

多线程同步实现创建单例

public class Singleton {
 private static Singleton instance;
 
 private Singleton() {
 }
 
 public static synchronized Singleton getInstance() {
 
   if(instance == null) {
     instance = new Singleton();
   }
   return instance;
 }
}

上述Java实现中,getInstance方法声明为线程同步,其避免了多线程一开始同时调用该方法而导致的多次实例化问题。但是这个解决方案并不完美,问题就出在线程同步上,在简单应用上这个缺点表现不明显,但是如果是多线程应用,getInstance方法被频繁调用的话,所有线程会在进入getInstance方法排队等待,线程调用会被频繁切换,造成不必要的系统资源消耗。

其实线程同步主要是希望在最开始的几个线程访问getInstance方法时,一旦单例对象创建完毕后,就不再需要让每个线程等待依次进入该方法,换句话说,单例创建完毕后,各个进程就可以异步访问该方法,获取已创建的单例对象。

优化后的解决方案见下面的样例。

多线程的异步获取和同步创建单例

public class Singleton {
 private volatile static Singleton instance;
 
 private Singleton() {
 }
 
 public static Singleton getInstance() {
   if( instance == null ) {
     synchronized(Singleton.class) {
       if(instance == null) {
          instance = new Singleton();
       }
     }
   }
   return instance;
 }
}

上述Java实现中,静态单例对象被声明为volatile对象,其意义是这个对象为各个线程共享对象,JVM不再为每个线程执行时在内存复制该对象。getInstance方法的访问是异步的,各个线程的访问独立运行,如果已有单例对象,则直接获取并退出getInstance方法;如果单例对象为空,则创建过程对线程同步,保证只让第一进入该代码块的线程来初始化该单例。

通过JVM的静态初始化来创建单例

如果系统的启动资源足够用,可以让单例的创建放在JVM启动中,即通过静态初始化器(static initializer)来创建单例,

public class Singleton {
 private static Singleton instance = new Singleton();
 
 private Singleton() {
 }
 
 public static Singleton getInstance() {
   return instance;
 }
}

上述方法简单实用。

代码样例

代码仓库地址:http://git.oschina.net/pphh/designPatterns,可以通过如下git clone命令获取仓库代码,

git clone git@git.oschina.net:pphh/designPatterns.git

上述代码样例在文件路径designPatterns\java\singleton中。

参考资料

《设计模式-可复用面向对象软件的基础》

《Head First 设计模式》

百度百科:http://baike.baidu.com/view/1859857.htm