`

多线程的创建和使用

    博客分类:
  • java
阅读更多
   最近做邮件发送功能时,客户端操作程序等待时长,有时候会很长甚至会死线程,这个很不利于用户的体验,为了解决这个问题,我想到了使用多线程,让系统在后台发送邮件,用户操作界面不受影响,从而保证用户体验软件的流畅度。事实上完成后的效果还不错,系统界面瞬间响应,邮件后台发送,其快慢程度取决于邮件服务器响应速率和网络!
下面说说我研究多线程的方法和心得。研究时我为了便捷,只做了文件复制的Demo,然后再将功能代码移植到开发系统的邮件发送功能中,这样做是为了保证系统的安全性。
首先要知道使用多线程有两种方法,一是继承Thread类,一种是实现Runnable接口。由于继承的局限性,只能单继承,所以一般情况下会先考虑实现Runnable接口这种。
我们要开启一个线程,那么必须先给他一个入口即run()方法,run()方法里可以写业务处理方法,这样一个线程就能流通并处理业务了。具体代码如下:
/**
* 所有线程任务接口
* 其他线程任务必须继承该类
*
* @author hadestage
*/
public abstract class Task implements Runnable {
    /**
    * 任务执行入口
    */
    public void run() {
    try {
taskCore();
} catch (Exception e) {
e.printStackTrace();
}
    }

    /**
    * 所有任务的核心业务逻辑执行,哪个业务需要开启多线程就继承该类,然后重写该方法的业务功能
    *
    * @throws Exception
    */
    public abstract Task[] taskCore() throws Exception;

    /**
    * 是否需要立即执行
    */
    protected abstract boolean needExecuteImmediate();
}
这个线程任务类是我们业务功能启用多线程时可继承的接口,继承时只需重写业务方法
taskCore()即可。

作为示例Demo,我创建了一个文件复制的线程任务,具体代码如下:
/**
* 创建文件线程任务
*
* @author hadestage
*/
public class FileCreatorTask extends Task {

// 源文件名和目标文件名 (Demo用的是全路径)
public String sourceFileName;
public String targeFileName;

public FileCreatorTask(String sourceFileName,String targeFileName){
this.sourceFileName = sourceFileName;
this.targeFileName = targeFileName;
}

/**
* 执行创建任务动作
*/
@Override
public Task[] taskCore(){
try {
FileUtil.copyFile(new File(sourceFileName), new File(targeFileName));
} catch (IOException e) {
this.taskCore();
}
return null;
}
/**
    * 是否需要立即执行
    *
    * @return false
    */
@Override
protected boolean needExecuteImmediate() {
return false;
}
}
其中FileUtil.copyFile(new File(sourceFileName), new File(targeFileName));
就是我们的关键业务代码,复制文件。

有时候,我们复制文件、上传文件、发送邮件等不仅仅只有一个任务,很有可能多个复制、上传、发送任务同时进行,这时我们开启一个线程好像有些不够用了,总不能一个一个的线程新建吧。那么我们就需要同时开启多个线程了,这就涉及到线程池的概念了,就像数据库连接池那样,建一个线程池,里面设定线程数,这样就能解决多线程任务了。
/**
* 线程池
* 创建线程池,销毁线程池,添加新任务
*
* @author hadestage
*/
public final class ThreadPool {
    /* 单例 */
    private static ThreadPool instance = null;
    /* 默认池中线程数 */
    public static int worker_num = 5;
    private static List<Task> taskQueue = Collections.synchronizedList(new LinkedList<Task>());
    /* 池中的所有线程 */
    public PoolWorker[] workers;

   ... ... ...
}
创建单例线程池,从而保证线程池只有一个且外部无法创建新的线程池,设置池中默认线程数,但支持手动设置线程数,PoolWorker是当前类的内部类,它充当线程池里的线程,下文会有介绍。

接下来就是在ThreadPool这个线程池类中添加方法,使它能在创建线程池时设置线程数。
// 默认线程5个
    private ThreadPool() {
        workers = new PoolWorker[worker_num];
        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 static synchronized ThreadPool getInstance() {
        if (instance == null)
            return new ThreadPool();
        return instance;
    }
    // 创建规定的线程个数
    public static synchronized ThreadPool getInstance(int threadNumber) {
        if (instance == null)
            return new ThreadPool(threadNumber);
        return instance;
}

    创建完线程池,开启了需要的线程,就得提供能往线程池里增加任务并执行任务的方法。
/**
    * 增加新的任务
    * 每增加一个新任务,都要唤醒任务队列
    * @param newTask
    */
    public void addTask(Task newTask) {
        synchronized (taskQueue) {
            taskQueue.add(newTask);
            /* 唤醒队列, 开始执行 */
            taskQueue.notifyAll();
        }
}
    /**
    * 批量增加新任务
    * @param taskes
    */
    public void batchAddTask(List<Task> 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;
            }
        }
    }
  
任务执行完成后,我们还需要做一项工作,总不能让线程池里的线程一直占着坑不放吧,你用完了,别人还等着用呢,所以我们还得销毁线程池,等要用了再调用就行了。
/**
    * 销毁线程池
    */
    public synchronized void destroy() {
        for (int i = 0; i < worker_num; i++) {
            workers[i].stopWorker();
            workers[i] = null;
        }
        taskQueue.clear();
    }

线程池ThreadPool类中的内部类,该类的作用是具体化线程池,一个任务是否执行,何时执行,怎么执行都是由这里面进行统一调配的。
    class PoolWorker extends Thread {
        private int index = -1;
        /* 该工作线程是否有效 */
        private boolean isRunning = true;
        /* 该工作线程是否可以执行新任务 */
        private boolean isWaiting = true;

        public PoolWorker(int index) {
        this.setName("eastCompeacePoolWorker-"+index);
            this.index = index;
            start();
        }
        public void setIsRunning(boolean isRunning){
        this.isRunning = isRunning;
        }
        public void stopWorker() {
            this.isRunning = false;
        }
        public boolean isWaiting() {
            return this.isWaiting;
        }
        /**
        * 循环执行任务
        * 这也许是线程池的关键所在
        */
        public void run() {
            while (isRunning) {
            System.out.println("index: "+index);
                Task r = null;
                synchronized (taskQueue) {
                    while (taskQueue.isEmpty()) {
                        try {
                            /* 任务队列为空,则等待有新任务加入从而被唤醒 */
                            taskQueue.wait(20);
                        } catch (InterruptedException ie) {
                        ie.printStackTrace();
                        }
                    }
                    /* 取出任务执行 */
                    r = (Task) taskQueue.remove(0);
                }
                if (r != null) {
                    isWaiting = false;
                    try {
                        /* 该任务是否需要立即执行 */
                        if (r.needExecuteImmediate()) {
                            new Thread(r).start();
                        } else {
                            r.run();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    isWaiting = true;
                    r = null;
                }
            }
        }
}

以上就是对线程池的创建和应用。现在有了业务功能,有了任务,也有了创建线程池和线程的方法,接下来就可以测试下了,可以新建一个带有main()函数的类,在main()方法里创建线程池,然后添加任务即可了。具体代码如下:
public static void main(String[] args) {
ThreadPool threadPool = ThreadPool.getInstance();
threadPool.addTask(new FileCreatorTask("D:\\pic.jpg","E:\\"+ System.currentTimeMillis()+".jpg"));
}
1
2
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics