欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

多线程的创建和使用

程序员文章站 2022-07-12 19:51:06
...
   最近做邮件发送功能时,客户端操作程序等待时长,有时候会很长甚至会死线程,这个很不利于用户的体验,为了解决这个问题,我想到了使用多线程,让系统在后台发送邮件,用户操作界面不受影响,从而保证用户体验软件的流畅度。事实上完成后的效果还不错,系统界面瞬间响应,邮件后台发送,其快慢程度取决于邮件服务器响应速率和网络!
下面说说我研究多线程的方法和心得。研究时我为了便捷,只做了文件复制的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"));
}