Java 线程池技术之一 自实现线程池

尽管自jdk1.5,Java已经自带了线程池实现,了解如何自己实现Java线程池有助于加深对操作系统和Java虚拟机的理解。

一,线程池的基本要素

线程池一般需要一个线程管理类: ThreadPoolManager,其作用有:

1)提供创建一定数量的线程的方法。主线程调用该方法,从而创建线程。创建的线程执行自己的例程,线程的例程阻塞在任务抓取上。

2)提供对任务队列的操作的方法。主线程调用初始化任务队列的方法,然后在有任务的时候,调用提供的任务添加方法,将任务添入等待队列。当主线程调用任务的添加方法时,会触发等待的线程,从而使得阻塞的线程被唤醒,其抓取任务,并执行任务。

线程池需要一个任务队列: List<Task>,其作用有:

提供任务的增删方法。而且该任务队列需要进行排他处理,防止多个工作线程对该任务队列进行同时的抓取操作或者主线程的加入与工作线程的抓取的并发操作。

线程池需要一个类似信号量的通知机制:wait -notify:

工作线程调用wait阻塞在任务抓取上。主线程添加任务后,调用notify触发阻塞的线程。

线程池需要一个线程类:WorkThread,其作用有:

提供线程的例程。创建线程WorkThread后,需要抓取任务,并执行任务。这是线程的例程。

线程池需要一个任务类:Task,其作用有:

提供线程抓取并执行的任务目标。

二,基础知识和策略选择

1,线程同步和通知机制

Object类的wait()方法、notify()方法、notifyAll()方法。

Object类的对象都有两个池: 监视池(monitor,或者称锁池)和等待池。

监视池: 如果一个线程想要调用一个对象的synchronized的方法,或者对对象的操作被synchronized块所包含,则线程必须获得该对象的对象锁才能执行这个方法或者代码块。如果获取不到,则被加入到该对象的监视池中,等待锁资源。

等待锁:如果一个线程调用对象的wait()方法,则线程会放弃持有的该对象的锁资源(因为wait()必须在获取到对象锁之后才能执行),并进入该对象的等待池。如果另外一个线程调用了相同对象的notify()/notifyAll()方法,就会唤醒在该对象的等待池中的一个或者所有线程。一旦被唤醒,线程就进入该对象的锁池,进行锁资源竞争,注意不是立即执行,也不能保证唤醒的是哪个线程。

另外由于wait()方法可能发生中断和虚假唤醒,以及时间timeout(带时间的wait方法),因此,应该在while循环中使用。

//在同步块或者synchronized方法中synchronized (obj) {//在循环中while (<condition does not hold>)obj.wait();… // Perform action appropriate to condition}

注: 我个人理解,这里的“虚假唤醒”的具体含义,应该包含如下这种情况: 一个被唤醒的线程发现其需要获取的条件已经被别的被唤醒的线程导致的不再成立。 比如在线程池的实现中,任务队列的任务: 当主线程向其中添加两个任务的时候,会唤醒第一个线程去抓取任务执行,而这个线程不会仅仅抓取一个,它执行完第一个立马抓取第二个。这个时候,第二个被唤醒的线程将无法抓取到,那么它不应该在被唤醒并获取锁后,对任务队列进行任务的获取并删除操作,即应该在获取锁后检查队列是否为空。由于获取锁后是继续wait()后的代码执行,因此必须使用while进行判断,而不能是if()判断,因为获取锁后,if的条件判断不会被再执行,而while语句则必须再进行循环判断看是否跳出循环。详细参见代码。

synchronized用来修饰一个非静态方法,表示执行这个方法,必须获取该方法所属对象的锁; synchronized用来修饰静态方法,表示要执行该方法必须获取该类的类锁;synchronized修饰代码块synchronized(obj) { //code…. }表示执行该代码块必须获取obj这个对象的对象锁。这样做的目的是减小锁的粒度,保证当不同块所需的锁不冲突时不用对整个对象加锁。利用零长度的byte数组对象做obj非常经济。

atomic action(原子操作):

在Java中,以下两点操作是原子操作。

1),对引用变量和除了long和double之外的原始数据类型变量进行读写。

2),对所有声明为volatile的变量(包括long和double)的读写。 另外:在java.util.concurrent和java.util.concurrent.atomic包中提供了一些不依赖于同步机制的线程安全的类和方法。

2,线程和任务

线程:

线程可以赋予名字、优先级、标识是否是守护线程等。

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)。

守护线程和用户线程唯一的区别就是守护线程会随着用户线程的(被守护的)结束而结束。

使用方法:

setDaemon(true);

这里有几点需要注意: (1) setDaemon(true)必须在start()之前设置,否则会抛出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (2) 在Daemon线程中产生的新线程也是Daemon的。 (3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。有可能在守护线程跑了一半的时候主线程跑完了,然后守护线程就也会停止了。

Java中的线程可以通过两种方式进行创建:继承Thread类,或者实现Runnable接口。继承Thread类应该重写其run()方法。实现Runnable类应该实现run()方法,并且将其作为Thread(Runnable target)的参数,即Thread的执行目标,创建线程,并开启其例程。例如:

1)继承Thread:

class PrimeThread extends Thread {long minPrime;PrimeThread(long minPrime) {this.minPrime = minPrime;}public void run() {// compute primes larger than minPrime…}} PrimeThread p = new PrimeThread(143); //开启线程的例程 p.start();纵然伤心,也不要愁眉不展,因为你不知是谁会爱上你的笑容

Java 线程池技术之一 自实现线程池

相关文章:

你感兴趣的文章:

标签云: