Quy tắc đặt tên Class, Interface, Package và Project trong Java

Đặt tên Class và Interface.

  1. Class nên được đặt tên là danh từ, và bắt đầu các từ bằng 1 ký tự in hoa Example: Person
  2. Tên Interface nên có thêm chữ Iđằng trước. Example:: IFrame.
  3. Tên lớp trừu tượng (dẫn xuất) nên có từ Abstract làm tiền tố, Example:AbstractAnimal (chúng ta sẽ tìm hiểu về lớp dẫn xuất ở những bài sau).

Đặt tên Package.

  1. Package nên được đặt tên bằng toàn bộ chữ tên thường Example: jb12

Đặt tên Project.

  1. TênProject phải tuân theo quy tắc chung ở trên và chữ cái đầu tiên của mỗi từ phải viết hoa.

Quy tắc đặt tên biến và hằng số trong Java

Việc đặt tên thể hiện mức độ chuyên nghiệp của lập trình viên vì vậy quy tắc đặt tên biên trong Java là một kỹ năng rất quan trọng. Sau đây là các quy tắc đặt tên:

  1. Biến (Variable) nên được đặt theo quy tắc lạc đà(Camel Case) bắt đầu bằng một ký tự in thường, các từ tiếp theo được bắt đầu bằng một ký tự in hoa Example: yearOfBirth
  2. Ngoài ra, trong một số trường hợp, tên biến cần phải thể hiện rõ kiểu dữ liệu của biến đó. Example: biến có kiểu là List thì nên đặt tên là studentList, biến có kiểu là Set thì nên đặt tên là studentSet, biến có kiểu là Map thì nên đặt tên là studentMap, biến có kiểu là Array thì nên đặt tên là studentArray,…
  3. Hằng (Constant) phải đặt toàn bộ là chữ in hoa, các từ tách biệt nhau bởi ký tự gạch dưới “_”. Example: TOI_LA_CONST
  4. Các biến JFC (Java Swing) nên được đặt hậu tố là kiểu đối tượng: Example: widthScale, nameTextField, leftScrollbar, mainPanel, fileToggle, minLabel, printerDialog
  5. Tập hợp nhiều đối tượng nên được đặt tên ở số nhiều: Example: Collection< Person > persons;
  6. Những biến chỉ số lượng đối tượng nên có tiền tố “n”: Example: nCounter, nPersion…

Quy tắc đặt tên chung trong Java

Đặt tên là một trong những kỹ năng quan trọng đối với lập trình viên, ngoài việc đảm bảo tính trong sáng, ý nghĩa và gợi nhớ thì cần phải đảm bảo theo quy tắc nhất quán để tất cả mọi người đều có thể đọc và bảo trì (maintain) code một cách dễ dàng. Sau đây là một số những quy ước chung về cách đặt tên trong Java. Cách đặt tên đối với biến hoặc method các bạn xem tiếp ở loạt bài tiếp theo.

  1. Nên khai báo tên có ý nghĩa và thể hiện được mục đích, dễ hiểu
  2. Tên khai báo không nên dài quá 20 ký tự hoặc có thể ít hơn nhưng phải đảm bảo đầy đủ về mặt ý nghĩa của nó, và tên cũngkhông được đặt quá ngắn, trừ khi đó là tên tạm Example:: a, i, j,...
  3. Tránh đặt những tên tương tự nhau.

    Example: hai biến có tên là personOne và person1 không nên được sử dụng trong một Class vì sẽ dễ gây ra nhầm lẫn trong quá trình viết code.

  4. Hạn chế viết tắt trừ khi từ viết tắt đó phổ biến và được nhiều người biết đến.

    Example:: Chúng ta không nên đặt tên biến là soTBmà nên đặt tên là soThueBao. nhưng lại hoàn toàn có thể đặt tên là fileHTML thay vì fileHypertextMarkupLanguage vì tên này quá dài và từ HTML cũng là một từ khá phổ biến, ít nhất là trong giới lập trình viên chúng ta.

  5. Tên các biến cục bộ của lớp nên kết thúc bằng hậu tố “_” Example: count_
  6. Tất cả các tên nên được viết bằng Tiếng Anh, Tránh kết hợp nhiều ngôn ngữ khác nhau (Tiếng Anh + Tiếng Việt + …).
  7. Những biến phạm vi rộng nên đặt tên dài, những biến phạm vi hoạt động hẹp (cục bộ) nên đặt tên ngắn.
  8. Từ khóa “set/get” phải được đặt trong các phương thức truy cập trực tiếp đến thuộc tính: Example: getName(), setSalary(int)…
  9. Không trùng với các “từ khóa
  10. Không được bắt đầu bằng số, Example: 123Person.
  11. Tên phải đượcbắt đầu bằng một chữ cái, hoặc các ký tự như $, _, …
  12. Không được chứa khoảng trắng, các ký tự toán học. Nếu tên bao gồm nhiều từ thì phân cách nhau bằng dấu _.
  13. Trong Java có phân biệt chữ hoa chữ thường.

    Example: như hocSinh sẽ khác với hocsinh.

  14. Tiền tố “is” nên được sử dụng trong các phương thức, các biến kiểu boolean:

    Example
    : isEmpty, isOpen…
  15. Từ khóa “find” có thể được sử dụng trong các phương thức tìm kiếm:

    Example
    : person.findFriend();
  16. Những phần bổ sung thêm nên được sử dụng nhằm chỉ rõ hành động, mục đích hoặc tính chất:

    Example: get/set, add/remove, create/destroy, start/stop, insert/delete, increment/decrement, old/new, begin/end, first/last, up/down, min/max, next/previous, old/new, open/close, show/hide, suspend/resume, etc.

ThreadPool và ExecutorService trong Java

I. ThreadPool:

  1. Đặt vấn đề:
    Chúng ta có một chương trình theo dõi 1 forder, mỗi khi có file mới copy vào forder đó thì chương trình đọc file đó và in ra nội dung file, yêu cầu mỗi file được xử lý bằng 1 thread (Sử dụng multithreading).
    Vậy thì một vấn đề đặt ra đó là mỗi khi có 1 file mới copy vào thì chương trình của chúng ta sẽ tạo ra 1 thread để đọc và in ra nội dung file đó -> Ok. Nhưng hãy tưởng tượng nếu có rất nhiều file được copy vào thì lượng thread tạo ra sẽ lớn tương ứng  -> RẤT TỐN KÉM TÀI NGUYÊN !!!
  2. ThreadPool là cái gì ?
    Thay vì phải tạo mới thread cho mỗi task (nhiệm vụ) được thực hiện đồng thời, các task sẽ đưa vào 1 thread pool (nôm na là hồ bơi dành cho các thread)
    Nghĩa là thế này:
    – Bên trong ThreadPool sẽ có 1 BlockingQueue. Khi các task được thêm vào “bể bơi”, chúng sẽ được đẩy vào Queue này để đợi xử lý lần lượt.
    – ThreadPool của chúng ta sẽ gồm một số thread có sẵn bên trong nó (Cái này sẽ giải thích kỹ hơn ở phần dưới).
    – Số thread có sẵn trong pool đó sẽ lấy từng task trong Queue ra để xử lý. Sau khi xử lý  xong task đó, thì nó lại được dùng để xử lý các task còn lại ở
    (Ví dụ liên tưởng: Có 1 khu nhà trọ. Nếu xử lý theo cách thứ nhất (Không dùng ThreadPool)  thì cứ mỗi người đi vệ sinh, chủ nhà trọ sẽ phải xây 1 nhà vệ sinh mới -> Tốn kém vậy ai chơi :)) Vậy nên phải “sử dụng ThreadPool”, nghĩa là: Ban đầu chủ nhà sẽ xây 1 số lượng nhất định NVS( ví dụ 3 chiếc chẳng hạn). Vậy thì 3 người dậy sớm nhất để đi vs không có vấn đề gì cả, nhưng từ người thứ 4 trở đi sẽ phải đứng xếp hàng, hễ có 1 anh sử dụng xong thì lần lượt những người đang xếp hàng mới được vào sử dụng tiếp. )=> Tóm lại là thay vì “Tạo mỗi task 1 thread mới để xử lý” thì ThreadPool sẽ dùng đi dùng lại 1 số thread nhất định để xử lý những task đó.

Bài học về notify() và notifyAll()

Hôm nay tôi có cho lớp JC3 tại ActiveStudy làm một bài tập như sau:

Implements mô hình Producer – Consumer sử dụng synchonized, wait và notify

Và đây là một bài làm điển hình:


import java.util.LinkedList;
import java.util.Queue;
/**
*
* @author 404NotFound
*/
public class MyQueue<E> {
private final Object sync = new Object();
private final int capacity;
private Queue<E> queue = new LinkedList<>();
public MyQueue() {
this.capacity = 1024;
}
public MyQueue(int capacity) {
this.capacity = capacity;
}
public void put(E e) {
synchronized (sync) {
try {
while (queue.size() == capacity) {
sync.wait();
}
} catch (InterruptedException ignore) {
}
queue.offer(e);
sync.notify();
}
}
public E take() {
synchronized(sync){
try {
while (queue.size() == 0) {
sync.wait();
}
} catch (InterruptedException ignore) {
}
E e = queue.poll();
sync.notify();
return e;
}
}
public int size(){
return queue.size();
}
}

view raw

MyQueue.java

hosted with ❤ by GitHub

Class MyQueue trên có thể chạy tốt trong rất nhiều trường hợp. Tuy nhiên, đôi khi chương trình lại treo một cách bất thường…

 Để dễ hiểu, tôi tạo một class Main trong đó khởi tạo MyQueue<String> có capacity bằng 1, chương trình chạy với 1 producer P và 2 consumer C1, C2. Lúc đầu tôi nghĩ rằng chương trình treo do deadlock, nhưng khi dump thread thì mọi chuyện lại khác.

“P” #11 prio=5 os_prio=0 tid=0x000000001741f800 nid=0x2cac in Object.wait() [0x0000000017d1f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
– waiting on <0x00000000e102e270> (a java.lang.Object)
at java.lang.Object.wait(Object.java:502)
at Processor.produce(Processor.java:19)
– locked <0x00000000e102e270> (a java.lang.Object)
at App$1.run(App.java:14)
at java.lang.Thread.run(Thread.java:745)

Locked ownable synchronizers:
– None

“C2” #13 prio=5 os_prio=0 tid=0x000000001741f000 nid=0x2a2c in Object.wait() [0x0000000017c1f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
– waiting on <0x00000000e102e270> (a java.lang.Object)
at java.lang.Object.wait(Object.java:502)
at Processor.consume(Processor.java:37)
– locked <0x00000000e102e270> (a java.lang.Object)
at App$3.run(App.java:50)
at java.lang.Thread.run(Thread.java:745)

Locked ownable synchronizers:
– None

“C1” #12 prio=5 os_prio=0 tid=0x0000000017429000 nid=0x1728 in Object.wait() [0x0000000017b1f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
– waiting on <0x00000000e102e270> (a java.lang.Object)
at java.lang.Object.wait(Object.java:502)
at Processor.consume(Processor.java:37)
– locked <0x00000000e102e270> (a java.lang.Object)
at App$2.run(App.java:36)
at java.lang.Thread.run(Thread.java:745)

Locked ownable synchronizers:
– None

Như vậy, cả 3 thread cùng wait. sau khi debug để xem thời gian ở mức nanosecond thì chuyện là thế này (1 trong rất nhiều case):

  • P1: RUNNABLE,  C1: RUNNABLE, C2: RUNNABLE
  • C1 và C2 lần lượt lấy lock và WAITING
  • P1 put String vào queue, notify rồi ngay lập tức quay về BLOCKED
  • Giả sử P1 notify cho C1. Chú ý rằng khi một thread được notify thì nó sẽ chuyển về trạng thái BLOCKED và vẫn phải tranh chấp, cùng với đó nó phải được thread scheduler lập lịch để lấy lock trước khi chuyển về trạng thái RUNNABLE.  Do đó lúc này cơ hội để P1 và C1 lấy lock là như nhau.
  • P1 lấy được lock, chuyển về RUNNABLE. Do queue full, P1 nhả lock và wait.
  • C1 giữ lock, lấy object và in ra màn hình. Sau đó notify cho C2 (Chú ý rằng method notify() sẽ gọi một thread bất kỳ trong số những thread đang wait.).
  • C2 check điều kiện. Hiển nhiên queue trống. C2 tiếp tục wait.
  • C1 đang trong trạng thái BLOCKED, lấy được lock từ C2, check queue –> queue trống –> C1 wait.

–> 3 Thread cùng wait, không có thread nào notify! Mặc dù C2 đã nhả lock, nhưng cả 3 thread đều đang trong trạng thái WAITING, mà lại không có thread nào notify cả.

Đây chắc chắn không phải deadlock, cũng không phải livelock hay starvation. Đây là hiện tượng missing signal, khi một thread bị miss tín hiệu notify. Cuối cùng, producer đã không được nhận signal notify mà nó đáng ra nên được nhận.

Để giải quyết vấn đề này, tôi dùng notifyAll(). Tương tự như trên, nếu dùng notifyAll(), mỗi thread đang wait sẽ được phép bỏ qua synchonized block và execute tiếp sau lệnh wait() một lần (theo thứ tự nhất định chứ không phải đồng thời). Như vậy, sau khi C1 giữ lock và in ra màn hình, cả P và C2 đều có cơ hội được giữ lock. Do khi giữ lock C2 wait() ngay nên P tiếp tục được produce, do đó chương trình chạy ngon lành cành đào.

Chốt: Chỉ dùng notify khi các thread đang wait có cùng công việc, bản chất như nhau, hay nói cách khác thằng nào wakeup không quan trọng. Còn lại thì gọi tất notifyAll.

Chuyện hỏi đáp trên các forum/website/group

Trong những năm gần đây, với sự phổ biến của Internet tại Việt Nam, việc trở thành một coder chưa bao giờ dễ dàng hơn nữa. Hầu hết mọi vấn đề liên quan đến lập trình đều có thể được tìm thấy trong vài click. Khi chương trình xảy ra lỗi, chỉ cần ném thông báo lỗi lên google là đã có thể tìm thấy hàng trăm kết quả giải thích lỗi và cách sửa. Thường kết quả sẽ dẫn đến stackoverflow, hoặc các forum hay website đã có sẵn câu trả lời.

Nếu bạn muốn một câu trả lời dành cho riêng mình, rất đơn giản, bạn chỉ cần hỏi. Tại Việt nam, chúng ta có các Facebook groups, có daynhauhoc.com. Riêng đối với ngôn ngữ Java chúng ta có Cộng đồng Java Việt Nam. Bạn nào biết chút tiếng Anh có thể lên stackoverflow hỏi. Mọi người rất sẵn lòng giúp bạn. Họ đọc câu hỏi rồi bỏ công ra trả lời mà không cần biết bạn là ai.

Họ cũng phải suy nghĩ để trả lời câu hỏi của bạn. Họ nghĩ cách trả lời sao cho bạn có thể hiểu vấn đề dễ dàng nhất. Và họ không lấy của bạn một xu. Có thể họ thích giải quyết vấn đề mà bạn đưa ra, có thể họ thích giúp đỡ mọi người, hoặc họ thích nổi tiếng. Nhưng dù sao, bạn cũng chả tốn một đồng nào để được giúp đỡ.

Tôi thường thấy các coder trẻ vứt câu hỏi lên groups/website rồi ra đi không lời từ biệt. Thật sự sẽ lịch sự hơn nếu bạn báo lại cho người trả lời rằng bạn đã thử cách của họ và thành công, hoặc bạn báo lại cho họ rằng bạn đã tìm ra cách của riêng mình, và giải thích cách làm. Ở những trang như stackoverflow hay daynhauhoc chúng ta có nút “Solution”, báo rằng một câu trả lời nào đó được chấp nhận đã giải quyết được vấn đề trong câu hỏi.

Bạn ra đi không lời từ biệt không chỉ thể hiện thái độ phụ bạc khi bạn đọc câu trả lời của người khác rồi bỏ đi, mà còn thể hiện sự thiếu trách nhiệm với những người sau đó đọc câu hỏi của bạn. Họ có thể nghĩ rằng bạn chưa được giải đáp và lại mất công trả lời bạn. Hoặc những người đang tìm kiếm câu trả lời cũng rất thất vọng khi thấy một câu hỏi không có lời giải đáp. Xin hãy nhớ rằng, chúng ta là một cộng đồng, chúng ta học hỏi từ người khác, cùng với đó chúng ta giúp cộng đồng này trở nên tốt hơn. Bạn nhận được thứ gì đó từ cộng đồng, vậy bạn nên cống hiến lại một giá trị nhất định, kể cả khi giá trị đó rất khiêm tốn.

Không có ai rảnh để chửi mắng bạn khi bạn không trả lời lại những người đã giúp bạn (miễn phí). Họ dành thời gian để tiếp tục giúp đỡ những người khác, trả lời những câu hỏi khác, hoặc câu hỏi tiếp theo của bạn. Họ biết bạn sẽ lại đọc câu trả lời của họ rồi lặn mất, nhưng họ không quan tâm.

Nhưng bạn nên quan tâm.

Hãy trở thành một người lịch sự, ngay cả trên Internet.

Translated from this post.

XML Config API

 

Ngày trước tạo file config dùng class Properties và phải dùng file properties –> gõ nhiều mỏi tay quá nên viết cái thư viện này xài cho tiện 😀 Chuyển luôn qua XML nhìn cho clear.

Jar file: https://github.com/dangxunb/XmlConfig/releases

Source: https://github.com/dangxunb/XmlConfig

Ví dụ file RMQConfig.xml đặt trong thư mục ./config. Custom XML, chỉ theo quy định XML chứ không theo quy định nào khác.


<?xml version="1.0" encoding="UTF-8"?>
<rmq-config>
<connection>
<host>localhost</host>
<port>5672</port>
<username>guest</username>
<password>guest</password>
<retry-policy>
<max-attempt>1</max-attempt>
<interval>1</interval>
</retry-policy>
</connection>
<consumer>
<exchange-declare type="topic" durable="true" auto-delete="false">topic_exchange</exchange-declare>
<queue-declare durable="true" exclusive="false" auto-delete="false">Test Queue</queue-declare>
<queue-bind>
<queue-name>Test Queue</queue-name>
<exchange-name>topic_exchange</exchange-name>
<routing-key>java.share.com</routing-key>
</queue-bind>
<reply-to>
<exchange-name>topic_exchange</exchange-name>
<routing-key>aa.*.*.qee.tttt</routing-key>
</reply-to>
<pool-size>32</pool-size>
</consumer>
</rmq-config>

view raw

rmq-config.xml

hosted with ❤ by GitHub

Vào code tạo interface:


import org.dangnh.xmlconfig.annotation.*;
/**
*
* @author DangNH
*/
@Source("file:./config/RMQConfig.xml") /*URI đến file config*/
public interface RmqExchangeDeclareConfig{
@Key("rmq-config.consumer.exchange-declare")
@DefaultValue("tms-topic") /*giá trị mặc định*/
String name();
@Key("rmq-config.consumer.exchange-declare.type")
@DefaultValue("topic")
String excType();
@Key("rmq-config.consumer.exchange-declare.durable")
@DefaultValue("true")
boolean isDurable();
@Key("rmq-config.consumer.exchange-declare.auto-delete")
@DefaultValue("false")
boolean isAutoDelete();
}

Rồi xài:


import com.elcom.ifccore.config.RmqExchangeDeclareConfig;
import org.dangnh.xmlconfig.ConfigFactory;
/**
*
* @author DangNH
*/
public class TestConfig {
public static void main(String[] args) {
RmqExchangeDeclareConfig config = ConfigFactory.create(RmqExchangeDeclareConfig.class);
//auto convert type
System.out.println(config.excType());
System.out.println(config.isDurable());
System.out.println(config.name());
}
}

view raw

TestConfig.java

hosted with ❤ by GitHub

API tự convert từ String thành kiểu dữ liệu được khai báo. Hiện tại chỉ hỗ trợ các kiểu primitives và String.  Nếu muốn tự convert thành kiểu khác thì implement interface Converter:


import java.lang.reflect.Method;
import org.dangnh.xmlconfig.Converter.Converter;
/**
*
* @author DangNH
*/
public class CustomCvt implements Converter{
@Override
public Object convert(Method method, Class<?> type, String string) {
//custom code here
}
}

view raw

CustomCvt.java

hosted with ❤ by GitHub

Rồi xài:


import org.dangnh.xmlconfig.annotation.*;
/**
*
* @author DangNH
*/
@Source("file:./config/RMQConfig.xml")
public interface RmqConsumerConfig {
@CustomConverter(CustomCvt.class)
@Key("rmq-config.consumer.pool-size")
@DefaultValue("10")
int getConsumerPoolSize();
}

Sau này sẽ thêm mấy thứ linh tinh (auto reload, hỗ trợ thêm các định dạng khác ngoài XML…) vào nhưng vẫn keep simple thế này.

Kỳ lạ chuyện lost message khi dùng RabbitMQ auto ack

Hiện tại tôi đang dùng rabbitmq-java-client-3.6.1. Bài viết này có thể không còn đúng với các phiên bản mới hơn.

Edit: Đã có câu trả lời tại đây.

Tôi có một Publisher đơn giản gửi 1000 persistent message vào exchange topic_test với routing key foo.bar như sau:


import com.rabbitmq.client.*;
public class Publisher {
private static final String EXCHANGE_NAME = "topic_test";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String routingKey = "foo.bar";
String message = "TestMsg";
for (int i = 0; i < 1_000; i++) {
/*Persistent message*/
channel.basicPublish(EXCHANGE_NAME, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
System.out.println(" [x] Sent " + i);
}
connection.close();
}
}

view raw

Publisher.java

hosted with ❤ by GitHub

Tương ứng là một Subscriber đứng đợi tại durable queue Foo Queue


import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class Subscriber {
private static final String EXCHANGE_NAME = "topic_test";
private static final String queueName = "Foo Queue";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
//create a durable, non-autodelete, non-exclusive queue
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, EXCHANGE_NAME, "foo.bar");
System.out.println(" [*] Waiting for messages");
Consumer consumer = new DefaultConsumer(channel) {
private int i = 0;
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(" [x] Received " + (++i));
//gracefully exit (not kill -9) if received 10 message
if (i%10 == 0) {
System.exit(0);
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException ignore) {
}
}
};
channel.basicConsume(queueName, true /*auto-ack*/, consumer);
}
}

view raw

Subscriber.java

hosted with ❤ by GitHub

Mọi việc sẽ chẳng có gì nếu tôi để subscriber chạy bình thường. Nhưng tôi muốn test trường hợp subscriber mất kết nối sau khi nhận được 10 message.

Đầu tiên cho Subscriber lắng nghe trên queue: Ok.

Cho publisher chạy: ok.

Capture.PNG

Ngay lập tức Subscriber consume message:

 

Capture.PNG
Consume 10 message rồi ra đi không lời từ biệt

 

Và đây là điều kỳ diệu:

 

Capture.PNG
990 message còn lại cũng ra đi không lời từ biệt

 

Tôi thử chạy lại quá trình trên và xem chuyện gì đã xảy ra?

 

Capture.PNG
Message lên 1k cực nhanh và xuống cũng nhanh không kém

 

Vấn đề này cực kì khó hiểu vì chỉ cần tắt auto ack rồi thay bằng explicit ack là có thể giải quyết được (tốn thêm 1 dòng code)??


import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class Subcriber {
private static final String EXCHANGE_NAME = "topic_test";
private static final String queueName = "Foo Queue";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
final Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
//create a durable, non-autodelete, non-exclusive queue
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, EXCHANGE_NAME, "foo.bar");
System.out.println(" [*] Waiting for messages");
Consumer consumer = new DefaultConsumer(channel) {
private int i = 0;
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(" [x] Received " + (++i));
//explicit ack
channel.basicAck(envelope.getDeliveryTag(), false);
//gracefully exit (not kill -9) if received 10 message
if (i%10 == 0) {
System.exit(0);
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException ignore) {
}
}
};
channel.basicConsume(queueName, false /*turn-off auto-ack*/, consumer);
}
}

view raw

Subcriber.java

hosted with ❤ by GitHub

Exception Handling in Java – The Bad Parts

Trong bài lần trước, Exception Handling in Java – The Good Parts, tôi đã nêu ra các lợi ích của việc sử dụng Exception Handling trong Java. Tuy vậy, nếu sử dụng sai chỗ, các Exception có thể khiến performance của chương trình bị ảnh hưởng ít nhiều.

Chắc hẳn trong chúng ta ai cũng có lần phải check xem chuỗi (String) nhập vào có phải số nguyên hay không, dù là làm bài tập, hay làm project. Tôi đã từng rất vui khi tìm ra một mẹo có thể xử lý vấn đề này một cách nhanh gọn như sau:


public boolean checkInt(String input){
try {
Integer.parseInt(input);
return true;
} catch (NumberFormatException e) {
return false;
}
}

Cho đến khi tôi thấy method này:


public static boolean isInteger(String str) {
 if (str == null) {
 return false;
 }
 int length = str.length();
 if (length == 0) {
 return false;
 }
 for (int i = 0; i < length; i++) {
 char c = str.charAt(i);
 if (c <= '/' || c >= ':') {
 return false;
 }
 }
 return true;
 }

Vâng, tôi đã thử check xem cái method dài dài kia thì có gì hay hơn cái mẹo mà tôi vẫn hay dùng dù cùng công dụng là check xem có phải số nguyên hay không. Tôi tạo một vòng for và check 10 triệu lần:

Trường hợp chuỗi nhập vào là số nguyên:


public static void main(String[] args) {
 long startTime = System.currentTimeMillis();
 for (int i = 0; i < 10_000_000; i++) {
 checkInt("50");
 }
 long endTime = System.currentTimeMillis();
 System.out.println(endTime - startTime);
 
 startTime = System.currentTimeMillis();
 for (int i = 0; i < 10_000_000; i++) {
 isInteger("50");
 }
 endTime = System.currentTimeMillis();
 System.out.println(endTime - startTime);
 }

Kết quả là 16 và 4. Cái method dài kia nhanh hơn method của tôi chẳng đáng là bao, mà code thì lằng nhằng. Hihi.

Trường hợp chuỗi nhập vào không phải số nguyên:


public static void main(String[] args) {
 long startTime = System.currentTimeMillis();
 for (int i = 0; i < 10_000_000; i++) {
 checkInt("a");
 }
 long endTime = System.currentTimeMillis();
 System.out.println(endTime - startTime);
 
 startTime = System.currentTimeMillis();
 for (int i = 0; i < 10_000_000; i++) {
 isInteger("a");
 }
 endTime = System.currentTimeMillis();
 System.out.println(endTime - startTime);
 }

Tôi giật mình vì kết quả là 7166 và 3. Khoảng cách quá xa và chương trình chạy khá lâu. CPU i5 của tôi cũng sử dụng khoảng 40% trong quá trình chương trình chạy. Tại sao lại như vậy nhỉ?

1. Khái niệm Context-Switch

Khái niệm context-switch có thể rất lạ với một sinh viên chỉ học lập trình phần mềm mà không học gì về khoa học máy tính (như tôi). Bạn có thể hiểu như sau: Context-Switch là quá trình lưu và khôi phục luồng xử lý hiện tại của một process hoặc một thread. Như vậy luồng xử lý đó có thể chạy tiếp vào một thời điểm nào đó sau khi được lưu lại.

Context-switch có thể được sử dụng ở cả phần mềm và phần cứng. Nó là khái niệm cốt lõi của một hệ điều hành đa nhiệm. Trong các chương trình phần mềm, quá trình này sẽ xảy ra gián tiếp khi có bất kì process nào làm gián đoạn luồng xử lý của chương trình và chuyển qua một luồng xử lý khác.

2. Context-Switch trong Java và cái giả phải trả cho Exception-Handling

Dễ thấy, quá trình xử lý Exception của Java sẽ làm xảy ra gián tiếp quá trình Context-Switch, do flow của chương trình bị gián đoạn khi Exception được ném ra. Trong các chương trình Java, quá trình context-switch luôn luôn gây ra sự tốn kém: Non-Reentran Data, Heap + Stack paged out và được lưu lại để phục vụ cho việc quay trở lại ngẫu nhiên, và một môi trường mới sẽ được paged-in. (Bạn đọc hãy vào link wikipedia đọc để hiểu rõ nhất các khái niệm tôi đưa ra, vì khả năng dịch tiếng anh chuyên ngành của tôi có hạn. Rất xin lỗi các bạn).

Chốt lại: Rất tốn tài nguyên –> performance của chương trình bị ảnh hưởng.

3. Khi nào dùng Exception-Handling?

Đây là một câu hỏi không hề có câu trả lời cụ thể. Càng vào code nhiều, bạn sẽ càng hiểu được những trường hợp nào nên dùng, những trường hợp nào thì không. Tôi sẽ nêu ra một vài trường hợp bạn có thể nhận biết được sự nguy hiểm như sau:

  1. Có nhất thiết phải dùng try/catch block không?
  2. Có nhất thiết phải throws exception không?
  3. Đặt try catch trong vòng lặp for – Rất nguy hiểm, cần cân nhắc kĩ. Bạn hoàn toàn có thể đặt for trong try/catch block, nhưng điều ngược lại thì cần phải xem có thực sự cần thiết không.
  4. Nếu bạn buộc phải catch một exception và không làm gì với nó cả thì hãy dùng throws
  5. Xin phép được trích từ stackoverflow:

Capture

Mình còn gà, nên từ quan điểm của mình thì:

  1. Nếu lỗi là một lỗi phổ biến (check số nguyên, chia cho 0, người dùng nhập sai pass…blah…blah…) thì không catch exception
  2. Nếu không thể làm gì để xử lý exception thì không catch nó.

Hi vọng các tiền bối bổ sung thêm.

Java Share Club

Chia sẻ lại là học, đừng ngại chia sẻ

Trung Tuyến Nguyễn - Median Nguyen

TDD craftsman rong chơi giữa đời!