多线程设计模式:Thread-Per-Message模式

为每个命令或请求新分配一个线程,由这个线程来执行处理,这就是Thread-Per-Message模式。

举个例子:

名字 说明
Main 向Host发送字符,显示请求的类
Host 针对请求创建线程的类
Helper 提供字符显示功能的被动类

这里写图片描述

代码:
Main:

public class Main {
    public static void main(String[] args) {
        System.out.println("main BEGIN");
        Host host = new Host();
        host.request(10, 'A');
        host.request(20, 'B');
        host.request(30, 'C');
        System.out.println("main END");
    }
}

Host:

public class Host {
    private final Helper helper = new Helper();
    public void request(final int count, final char c) {
        System.out.println("    request(" + count + ", " + c + ") BEGIN");
        new Thread(() -> helper.handle(count, c)).start();
        System.out.println("    request(" + count + ", " + c + ") END");
    }
}

Helper:

public class Helper {

    // 用于按指定次数显示字符的handle方法
    public void handle(int count, char c){
        System.out.println("    handle(" + count + ", " + c + ") BEGIN");
        for (int i = 0; i < count; i++) {
            slowly();
            System.out.print(c);
        }
        System.out.println("");
        System.out.println("    handle(" + count + ", " + c + ") END");
    }

    private void slowly() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    }
}

执行结果:

main BEGIN
    request(10, A) BEGIN
    request(10, A) END
    request(20, B) BEGIN
    request(20, B) END
    request(30, C) BEGIN
    handle(10, A) BEGIN
    handle(20, B) BEGIN
    request(30, C) END
main END
    handle(30, C) BEGIN
BCAABCBCABACBACBCAABCBACBACCA
    handle(10, A) END
BBCBCCBBCCBBCCBBCCBCB
    handle(20, B) END
CCCCCCCCCC
    handle(30, C) END

可以看出调用request方法的线程会立即返回,而handle方法则交由其它线程来执行。
Main类相当于委托人Client,向Host发出请求request,Host接受到请求后会创建并启动一个线程。新创建的线程将使用Helper来处理handle请求。
Thread-Per-Message模式能够提高Client与Host之间的响应性,降低延迟时间,尤其当handle操作非常耗时,或者等待输入/输出时,效果明显。当然启动线程也会花费时间,所以想要提高响应性是否适用该模式取决于“handle操作花费的时间”与“线程启动花费的时间”之间的均衡。
该模式适用于操作顺序没有要求时,不需要返回值时。
该模式还适用于服务器,服务器本身的线程接收到客户的请求,将实际处理交由其它线程来执行,本身则返回,去等待客户端的其它请求。

举例:

例一:GUI应用程序
Main类:

public class Main {
    public static void main(String[] args) {
        new MyFrame();
    }
}

MyFrame 类:
一个弹框

public class MyFrame extends JFrame implements ActionListener{

    public MyFrame() throws HeadlessException {
        super("MyFrame");
        getContentPane().setLayout(new FlowLayout());
        getContentPane().add(new JLabel("Thread-Per-Message Sample"));
        JButton button = new JButton("Execute");
        getContentPane().add(button);
        button.addActionListener(this);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pack();
        setVisible(true);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        Service4.service();
    }
}

Service类:
Swing框架会调用actionPerformed方法,该方法会调用Service.service(),由于service()比较耗时,导致按钮的反应,以及应用程序对用户的响应都会变得非常慢.

public class Service {
    public static void service() {
        System.out.print("service");
        for (int i = 0; i < 50; i++) {
            System.out.print(".");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
        System.out.println("done.");
    }
}

解决方法一:

/**
 * 启动一个线程,由该线程来执行实际处理操作,这样线程便可以立刻从service方法返回
 * 不过在用户多次点击按钮时,多个线程同时执行doService
 */
public class Service1 {
    public static void service() {
        new Thread(Service1::doService).start();
    }

    private static void doService() {
        System.out.print("service");
        for (int i = 0; i < 50; i++) {
            System.out.print(".");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
        System.out.println("done.");
    }
}

运行结果:

service...............service...........................service..................
..................................................done.
...........................done.
.............done.

解决方法二:

/**
 * 线程会立即从service方法返回,同时执行doService方法的线程只有一个
 * 用户按几次,doService就会执行几次,但输出不会混在一起
 */
public class Service2 {
    public static void service() {
        new Thread(Service2::doService).start();
    }

    private static synchronized void doService() {
        System.out.print("service");
        for (int i = 0; i < 50; i++) {
            System.out.print(".");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
        System.out.println("done.");
    }
}

运行结果:

service..................................................done.
service..................................................done.
service..................................................done.

解决方法三:

/**
 * 线程立即从service方法返回,保证执行doService方法的只有一个线程
 */
public class Service3 {
    private static volatile boolean working = false;
    public static synchronized void service() {
        System.out.print("service");
        if (working){
            System.out.println(" is balked,");
            return;
        }
        working = true;
        new Thread(Service3::doService).start();
    }

    private static void doService() {
        try {
            for (int i = 0; i < 50; i++) {
                System.out.print(".");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
            }
            System.out.println("done.");
        } finally {
            working = false;
        }
    }
}

运行结果:

service...........service is balked,
..........service is balked,
.............service is balked,
................done.

解决方法四:

/**
 * 线程立即从service方法返回,当用户连续点击时,取消任务重新开始
 */
public class Service4 {
    private static Thread worker = null;
    public static synchronized void service() {
        if (worker != null && worker.isAlive()) {
            worker.interrupt();
            try {
                worker.join();
            } catch (InterruptedException e) {
            }
            worker = null;
        }
        System.out.print("service");
        worker  = new Thread(Service4::doService);
        worker.start();
    }

    private static void doService() {
        try {
            for (int i = 0; i < 50; i++) {
                System.out.print(".");
                Thread.sleep(100);
            }
            System.out.println("done.");
        } catch (InterruptedException e){
            System.out.println("cancelled.");
        }
    }
}

运行结果:

service..........cancelled.
service............cancelled.
service..................................................done.

例二:服务器
下面代码实现的服务器是单线程运作的,同时只能响应一个Web浏览器,在一个浏览器倒计时约10秒期间内,其它浏览器都需要等待。
Main类:

public class Main {
    public static void main(String[] args) {
        try {
            new MiniServer(8888).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

MiniServer类:

public class MiniServer {

    private final int port;

    public MiniServer(int port) {
        this.port = port;
    }

    public void execute() throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("Listening on " + serverSocket);
        try {
            while (true) {
                System.out.println("Accepting....");
                Socket clientSocket = serverSocket.accept();
                System.out.println("Connected to " + clientSocket);
                try {
                    Service.service(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            serverSocket.close();
        }
    }
}

Service类:

public class Service {

    public static void service(Socket clientSocket) throws IOException {
        System.out.println(Thread.currentThread().getName() + ": Service.service("
                + clientSocket + ") Begin");
        try {
            DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
            out.writeBytes("HTTP/1.0 200 OK\r\n");
            out.writeBytes("Content-type: text/html\r\n");
            out.writeBytes("\r\n");
            out.writeBytes("<html><head><title>Countdown</title></head></body>");
            out.writeBytes("<h1>Countdown start!</h1>");
            for (int i = 10; i >= 0; i--) {
                System.out.println(Thread.currentThread().getName() + ": Countdown i = " + i);
                out.writeBytes("<h1>" + i + "</h1>");
                out.flush();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }
            out.writeBytes("</body></html>");
        } finally {
            clientSocket.close();
        }
        System.out.println(Thread.currentThread().getName() + ": Service.service("
                + clientSocket + ") END");
    }
}

两个浏览器先后访问 http://127.0.0.1:8888查看效果。

使用gThread-Per-Message模式来修改程序,使其能响应多个Web浏览器。需要对MiniServer类进行修改:

public class MiniServer2 {

    private final int port;

    public MiniServer2(int port) {
        this.port = port;
    }

    public void execute() throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        ExecutorService executorService = Executors.newCachedThreadPool();
        System.out.println("Listening on " + serverSocket);
        try {
            while (true) {
                System.out.println("Accepting....");
                final Socket clientSocket = serverSocket.accept();
                System.out.println("Connected to " + clientSocket);
                executorService.execute(() -> {
                    try {
                        Service.service(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
            serverSocket.close();
        }
    }
}

该系列文章是《图解Java多线程设计模式》的读书笔记

猜你喜欢

转载自blog.csdn.net/sinat_34976604/article/details/82666756
今日推荐