noprianto
noprianto
. 5 min read

Multithreading Java Menggunakan Thread Pool

Bismillah,
Jika pada tulisan yang sebelumnya saya telah menulis tentang thread dan socket pada Java, pada kali ini saya akan menulis masih dengan topik yang sama yaitu tentang thread. Kemudian apa yang membedakan? Sebelumnya saya pernah menyatakan bahwa aplikasi yang menggunakan konsep multithreading susah untuk dikontrol, selain itu juga ketika tidak menerapkan multithreading secara benar maka aplikasi kita akan overhead karena terlalu banyak membuat objek thread. Untuk mengatasi permasalah tersebut Java menyediakan Thread Pool. Thread Pool merupakan salah fitur yang disediakan oleh Java untuk programmer mengatur jumlah thread yang akan mengerjakan sebuah task, Thread pool juga menyediakan sarana-sarana untuk mengontrol/memantau thread. Misalkan mengetahui thread dalam pool atau juga mengetahui jumlah task yang sudah dikerjakan.

Pada gambar di atas memperlihatkan ada 3 bagian dalam Thread Pool, yaitu

  • Task Submitters, mengirimkan task yang akan dieksekusi oleh thread ke dalam sebuah antrian task.
  • Task Queue, sebuah antrian task hasil pengiriman oleh submitter.
  • Thread Pool merupakan kumpulan thread yang siaga mengerjakan task di dalam sebuah antrian task. Misalkan jumlah task lebih besar daripada jumlah thread berarti akan ada proses menunggu task di dalam antrian sebelum dieksekusi oleh thread.
public class ThreadPoolServer {

    private ServerSocket serverSocket;
    private ThreadPoolExecutor executor;

    public ThreadPoolExecutor getExecutor() {
        return executor;
    }

    public void start(int port, int nthreads) {
        try {
            System.out.println("Starting server on port " + port);
            serverSocket = new ServerSocket(port);
            executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(nthreads);
            while (true) {
                executor.execute(new EchoClientHandler(serverSocket.accept()));
            }
        } catch (IOException ex) {
            Logger.getLogger(ThreadPoolServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void stop() {
        try {
            executor.shutdown();
            executor.awaitTermination(10, TimeUnit.SECONDS);
            if (serverSocket != null) {
                serverSocket.close();
            }
        } catch (IOException | InterruptedException ex) {
            Logger.getLogger(ThreadPoolServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private static class EchoClientHandler implements Runnable {

        private Socket clientSocket;
        private PrintWriter out;
        private BufferedReader in;

        public EchoClientHandler(Socket socket) {
            this.clientSocket = socket;
            System.out.println("Request from " + socket.getInetAddress().getHostAddress());
        }

        public void run() {
            try {
                out = new PrintWriter(clientSocket.getOutputStream(), true);
                in = new BufferedReader(
                        new InputStreamReader(clientSocket.getInputStream()));

                String inputLine;
                while ((inputLine = in.readLine()) != null) {
                    if (".".equals(inputLine)) {
                        out.println("Jazzakalloh ^_^");
                        break;
                    }
                    out.println(inputLine);
                }
                in.close();
                out.close();
                clientSocket.close();
            } catch (IOException ex) {
                Logger.getLogger(EchoClientHandler.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

Server yang sebelumnya tidak banyak berubah, hanya menambahkan class ThreadPoolExecutor dan menambahkan parameter pada konstuktor untuk menentukan jumlah thread pool. Karena ThreadPoolExecutor adalah turunan class abstract sehingga untuk membuat instance tidak bisa langsung tetapi menggunakan class Executors dan memanggil method static yaitu newFixedThreadPool() dengan paramter jumlah thread pool.

Yang sebagai submitter adalah dengan memanggil method execute() pada object ThreadPoolExecutor, yang dilewatkan adalah sebuah Runnable. Yang terakhir perlu dimodifikasi adalah class EchoClientHandler. Class tersebut sebelum extends class Thread, diganti menjadi implement Runnable untuk menyesuaikan parameter yang dibutuhkan oleh class ThreadPoolExecutor ketika memanggi method execute().

Task yang dikerjakan pada server sebenarnya hanya berupa forward message yang dikirimkan oleh client, misalkan client mengirimkan message “hai” maka server akan meresponse dengan message “hai”. Untuk mengakhiri komunikasi dengan server yang dibutuhkan adalah mengirimkan message “.”.

public class ThreadPoolServerTest {

    ThreadPoolServer server;
    private static final int NTHREADS = 20;
    private final int PORT = 9000;
    private final String IP = "localhost";

    public ThreadPoolServerTest() {
    }

    @Before
    public void setUp() {
        server = new ThreadPoolServer();
    }

    @After
    public void tearDown() {
        server.stop();
    }

    @Test
    public void testSendMessage() {
        new Thread(() -> {
            server.start(PORT, NTHREADS);
        }).start();
        for (int i = 0; i < 100; i++) {
            GreetClient client = new GreetClient();
            client.startConnection(IP, PORT);

            String message = "What'up!";
            assertEquals(client.sendMessage(message), message);
            String endMessage = "Jazzakalloh ^_^";
            assertEquals(client.sendMessage("."), endMessage);
            System.out.println("Number of core pool : " + server.getExecutor().getCorePoolSize());
            System.out.println("Number of pool      : " + server.getExecutor().getPoolSize());
            System.out.println("Number of task count: " + server.getExecutor().getTaskCount());
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            Logger.getLogger(ThreadPoolServerTest.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}

Jika dilihat dari script test di atas thread yang akan dibuat sebanyak 20 thread, akan mengerjakan sebanyak 100 task. Jika menggunakan thread biasa, logika yang paling sederhana adalah dengan menggunakan single thread atau membuat thread sebanyak task yaitu 100 thread. Ketika menggunakan thread pool dalam contoh di atas, masing-masing thread akan menghandle atau mengerjakan 5 task. Beberapa method yang dicontohkan misalkan

  • getCorePoolSize() digunakan mengambil jumlah thread yang digunakan executor service, jumlahnya sama dengan jumlah thread pada konstruktor ketika membuat instance executor
  • getPoolSize() digunakan untuk menampilkan jumlah thread pool
  • getTaskCount() method untuk mengambil task count yang sudah tereksekusi.
T E S T S
Running com.odeng.maven.socket.threadpool.ThreadPoolServerTest
Starting server on port 9000
Request from 127.0.0.1
Number of core pool : 20
Number of pool : 1
Number of task count: 1
...
...
...
Request from 127.0.0.1
Number of core pool : 20
Number of pool : 20
Number of task count: 100
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.456 sec

Pada output dari script terlihat bahwa ketika pertama kali dijalankan, thread pada jumlah pool 1 dengan task 1. Jumlah thread pool akan bertambah sampai dengan jumlah thread yang digunakan, begitu juga dengan task. Untuk code bisa temen-temen dapatkan di github.

Demikianlah artikel saya mengenai thread pool di Java, semoga bertambah wawasan dan ilmu baru bagi temen-temen. Saya sangat mengharapkan kritik dan saran temen-temen agar blog ini semakin berkwalitas, memotivasi saya untuk dapat menulis artikel-artikel yang akan datang jauh menarik. 🙂

comments powered by Disqus