在工作中很少使用java的多线程,以前一直以为java多线程很难,不是很理解其工作原理,前几天有时间上网学习了下java的多线程,以及线程池的一些知识,按照网上的例子也修改了下,弄了个线程池,这期间还是学到了很多知识的。
首先说下java实现多线程的两种方式,这个很多人应该都知道,简单的说说,一种是实现Runnable接口,另一种是继承Thread类,两个方法各有各的好处吧,实现Runnable接口的话可以做到多个线程之间的资源共享,同时java是单继承的,如果继承了Thread类就不能在继承其他类了,而实现了Runnable接口的话就可以同时继承其他类。而继承Thread类的优点呢是获取当前的线程信息比较简单,而我觉得继承Runnable接口的也不复杂,所有个人比较青睐用实现Runnable接口的这种方式。
再说下线程池的原理,线程池会让我联想起数据库连接池,数据库连接池的作用就是给你个数据库连接,然后我们用这个连接去执行操作,而不论我们执行什么操作,数据库连接都不会与我们的逻辑耦合,像一个工具类一样;同样线程池也要满足这样的要求,与我们的逻辑解耦。实现解耦的话就要用到‘多态’或‘接口回调’,就是通过调用父类或接口中的方法从而间接地调用我们所要执行的代码。
以上这些是思路吧,说说原理,不知道有没有人跟我一样,原来我一直以为线程池的话就是多个线程,某一个线程跑完了我们的逻辑方法就结束了,那既然结束了再怎么去接着执行别的任务,所以感觉多线程很难;其实多线程多线程就是通过线程调用线程实现的;打个比方来说就像“摆渡”,河的一岸有很多的人,他们想过河,过河的过程做什么就是他们自己的逻辑,只要他符合我的要求我就送你过河(线程池的要求就是实现Runnable或继承Thread类),然后我开了几条船去送人,只要河的这一岸有满足的人,我就送你过河。这个例子中河一岸的人就是我们要执行的任务,是一个集合,船就是线程池中的线程,由我们自己控制,过河这个动作就是要执行的逻辑,我们只负责把船调给你,怎么划桨怎么过河就是程序自己的逻辑了。
可能说了这么多还是不是很清楚(原谅我的表达能力!),看一个生产者和消费者的例子,生产者不停的生产产品,生产后放入仓库,消费者不停的从仓库里拿产品。我们用线程池将他们实现。
先是创建商品类,商品类中有商品信息:
/**
* 产品类
*/
class Product {
private String id;// 产品id
private String name;// 产品名称
public Product(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "(产品ID:" + id + " 产品名称:" + name + ")";
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后创建仓库类,仓库对外开放两个方法,存入和取出,由于是多线程,所以要用synchronized关键字修饰这两个方法。
/**
*仓库
*/
class Storage {
// 仓库容量为10
private Product[] products = new Product[10];
private int top = 0;
// 生产者往仓库中放入产品
public synchronized void push(Product product) {
while (top == products.length) {
try {
System.out.println("仓库满了");
wait();//仓库已满,等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//把产品放入仓库
products[top++] = product;
System.out.println(Thread.currentThread().getName() + " 生产了产品"
+ product);
notifyAll();//唤醒等待线程
}
// 消费者从仓库中取出产品
public synchronized Product pop() {
while (top == 0) {
try {
System.out.println("仓库空了");
wait();//仓库空,等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//从仓库中取产品
--top;
Product p = new Product(products[top].getId(), products[top].getName());
products[top] = null;
System.out.println(Thread.currentThread().getName() + " 消费了产品" + p);
notifyAll();//唤醒等待线程
return p;
}
}
然后生产者消费者的实例,实现Runnable接口,run方法中分别调用入库和出库的方法。
/**
* 消费者
*/
class Consumer implements Runnable {
private Storage storage;
public Consumer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
storage.pop();
}
}
/**
* 生产者
*/
class Producer implements Runnable {
private Storage storage;
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
Product product = new Product("090505105", "电话");
storage.push(product);
}
}
之后就是关键的线程池的实现,线程池首先是一个容器,放着执行的线程和待执行的任务,线程池也只有一个所以用单例的设计模式。对外公开存放作业的方法和设置最大线程数的方法,当外界有作业放进来的时候就执行,执行完了之后销毁。上代码。
package thread;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executor;
import org.apache.log4j.Logger;
/**
* 线程池
* 创建线程池,销毁线程池,添加新任务
*
* @author obullxl
*/
public final class ThreadPool {
/* 单例 */
private static ThreadPool instance = ThreadPool.getInstance();
/* 默认池中线程数 */
private static int worker_num = 5;
/* 等待任务队列 */
private static List<Runnable> taskQueue = Collections
.synchronizedList(new LinkedList<Runnable>());
/* 池中的所有线程 */
public PoolWorker[] workers;
/**
* 构造器
* */
private ThreadPool() {
workers = new PoolWorker[5];
for (int i = 0; i < workers.length; i++) {
workers[i] = new PoolWorker(i);
}
}
private ThreadPool(int pool_worker_num) {
worker_num = pool_worker_num;
workers = new PoolWorker[worker_num];
for (int i = 0; i < workers.length; i++) {
workers[i] = new PoolWorker(i);
}
}
/**
* 修改最大线程数
* */
public void setMaxThreadNum(int max){
worker_num = max;
}
/**
* 单例返回本对象
* */
public static synchronized ThreadPool getInstance() {
if (instance == null)
return new ThreadPool();
return instance;
}
/**
* 增加新的任务
* @param newTask
*/
public void addTask(Runnable newTask) {
synchronized (taskQueue) {
taskQueue.add(newTask);
/* 开始执行 */
this.startWork();
}
}
/**
* 批量增加新任务
* @param taskes
*/
public void batchAddTask(List<Runnable> taskes) {
if (taskes == null || taskes.size() == 0) {
return;
}
synchronized (taskQueue) {
for (int i = 0; i < taskes.size(); i++) {
if (taskes.get(i) == null) {
continue;
}
taskQueue.add(taskes.get(i));
}
/* 唤醒队列, 开始执行 */
// taskQueue.notifyAll();
}
for (int i = 0; i < taskes.size(); i++) {
if (taskes.get(i) == null) {
continue;
}
}
/* 开始执行 */
this.startWork();
}
/**
* 执行作业
* */
private void startWork(){
for (int i=0;i<workers.length;i++){
if(!workers[i].isRunning){
workers[i].start();
}
}
}
/**
* 销毁线程池
*/
public synchronized void destroy() {
for (int i = 0; i < worker_num; i++) {
workers[i].stopWorker();
workers[i] = null;
}
taskQueue.clear();
}
/**
* 池中工作线程
*
* @author obullxl
*/
private class PoolWorker extends Thread {
private int index = -1;
/* 该工作线程是否有效 */
private boolean isRunning = false;
/* 该工作线程是否可以执行新任务 */
private boolean isWaiting = true;
public PoolWorker(int index) {
this.index = index;
}
public void stopWorker() {
this.isRunning = false;
}
public boolean isWaiting() {
return this.isWaiting;
}
/**
* 循环执行任务
* 这也许是线程池的关键所在
*/
public void run() {
isRunning = true;
while (isRunning) {
Runnable r = null;
synchronized (taskQueue) {
if (taskQueue.isEmpty()) {
stopWorker(); // (1) 没有作业就不执行
// try { (2) 没有作业就等待,知道有作业加进来
// /* 任务队列为空,则等待有新任务加入从而被唤醒 */
// taskQueue.wait(20);
// } catch (InterruptedException ie) {
// logger.error(ie);
// }
}else{
/* 取出任务执行 */
r = taskQueue.remove(0);
}
}
if (r != null) {
r.run();
}
isWaiting = true;
r = null;
}
}
}
}
然后写一个main方法模拟调用
public class ProducersAndConsumers {
public static void main(String[] args) {
ThreadPool pool = ThreadPool.getInstance();
Storage storage = new Storage();
List<Runnable> conTasks = new ArrayList<Runnable>();
for(int i=0;i<100;i++){
Consumer con = new Consumer(storage);
Producer pro = new Producer(storage);
conTasks.add(con);
conTasks.add(pro);
}
pool.batchAddTask(conTasks);
}
}
将线程池中待执行任务容器中放入任务,然后线程池开启5个线程,去调用我们待执行的任务,从而实现了同一时间内只有5个线程在工作。
演示结果:
仓库空了
Thread-2 生产了产品(产品ID:090505105 产品名称:电话)
Thread-0 消费了产品(产品ID:090505105 产品名称:电话)
仓库空了
Thread-2 生产了产品(产品ID:090505105 产品名称:电话)
Thread-0 消费了产品(产品ID:090505105 产品名称:电话)
仓库空了
Thread-2 生产了产品(产品ID:090505105 产品名称:电话)
Thread-2 消费了产品(产品ID:090505105 产品名称:电话)
Thread-2 生产了产品(产品ID:090505105 产品名称:电话)
Thread-2 消费了产品(产品ID:090505105 产品名称:电话)
Thread-2 生产了产品(产品ID:090505105 产品名称:电话)
Thread-2 生产了产品(产品ID:090505105 产品名称:电话)
Thread-2 消费了产品(产品ID:090505105 产品名称:电话)
Thread-2 生产了产品(产品ID:090505105 产品名称:电话)
Thread-2 消费了产品(产品ID:090505105 产品名称:电话)
Thread-2 生产了产品(产品ID:090505105 产品名称:电话)
Thread-2 消费了产品(产品ID:090505105 产品名称:电话)
Thread-2 生产了产品(产品ID:090505105 产品名称:电话)
Thread-0 消费了产品(产品ID:090505105 产品名称:电话)
Thread-3 消费了产品(产品ID:090505105 产品名称:电话)
Thread-1 生产了产品(产品ID:090505105 产品名称:电话)
Thread-4 消费了产品(产品ID:090505105 产品名称:电话)
其实这里我们还可在封装下,就是taskQueue继承换成一个实习多线程的通用业务类Task,然后Task类中封装通用的字段和方法。