Archive for the ‘Java’ Category

The use of the implicit monitors in Java objects is powerful, but you can achieve a more subtle level of control through inter-process communication. As you will see, this is especially easy in Java.

Multithreading replaces event loop programming by dividing your tasks into discrete and logical units. Threads also provide a secondary benefit: they do away with polling. Polling is usually implemented by a loop that is used to check some condition repeatedly. Once the condition is true, appropriate action is taken. This wastes CPU time. For example, consider the classic queuing problem, where one thread is producing some data and another is consuming it. To make the problem more interesting, suppose that the producer has to wait until the consumer is finished before it generates more data. In a polling system, the consumer would waste many CPU cycles while it
waited for the producer to produce. Once the producer was finished, it would start polling, wasting more CPU cycles waiting for the consumer to finish, and so on. Clearly, this situation is undesirable.

To avoid polling, Java includes an elegant interrocess communication mechanism via the wait( )notify( ), and notifyAll( ) methods. These methods are implemented as final methods in Object, so all classes have them. All three methods can be called only from within a synchronized method. Although conceptually advanced from a computer science perspective, the rules for using these methods are actually quite simple:

  • wait( ) tells the calling thread to give up the monitor and go to sleep until some other
    thread enters the same monitor and calls notify( ).
  • notify( ) wakes up the first thread that called wait( ) on the same object.
  • notifyAll( ) wakes up all the threads that called wait( ) on the same object. The
    highest priority thread will run first.

These methods are declared within Object, as shown here:

final void wait( ) throws InterruptedException
final void notify( )
final void notifyAll( )

Additional forms of wait( ) exist that allow you to specify a period of time to wait. The following sample program incorrectly implements a simple form of the producer/consumer problem. It consists of four classes: Q, the queue that you’re trying to synchronize; Producer, the threaded object that is producing queue entries; Consumer, the threaded object that is consuming queue entries; and PC, the tiny class that creates the single QProducer, and Consumer.

// An incorrect implementation of a producer and consumer.
class Q {
int n;
synchronized int get() {
System.out.println(“Got: ” + n);
return n;
}
synchronized void put(int n) {
this.n = n;
System.out.println(“Put: ” + n);
}
}

class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, “Producer”).start();
}
public void run() {
int i = 0;
while(true) {
q.put(i++);
}
}
}

class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, “Consumer”).start();
}
public void run() {
while(true) {
q.get();
}
}
}

class PC {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
System.out.println(“Press Control-C to stop.”);
}
}

Although the put( ) and get( ) methods on are synchronized, nothing stops the producer from overrunning the consumer, nor will anything stop the consumer from consuming the same queue value twice. Thus, you get the erroneous output shown here (the exact output will vary with processor speed and task load):

Put: 1
Got: 1
Got: 1
Got: 1
Got: 1
Got: 1
Put: 2
Put: 3
Put: 4
Put: 5
Put: 6
Put: 7
Got: 7

As you can see, after the producer put 1, the consumer started and got the same 1 five times in a row. Then, the producer resumed and produced 2 through 7 without letting the consumer have a chance to consume them.

The proper way to write this program in Java is to use wait( ) and notify( ) to signal in both directions, as shown here:

// A correct implementation of a producer and consumer.
class Q {
int n;
boolean valueSet = false;
synchronized int get() {
if(!valueSet)
try {
wait();
} catch(InterruptedException e) {
System.out.println(“InterruptedException caught”);
}
System.out.println(“Got: ” + n);
valueSet = false;
notify();
return n;
}
synchronized void put(int n) {
if(valueSet)
try {
wait();
} catch(InterruptedException e) {
System.out.println(“InterruptedException caught”);
}
this.n = n;
valueSet = true;
System.out.println(“Put: ” + n);
notify();
}
}

class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, “Producer”).start();
}
public void run() {
int i = 0;
while(true) {
q.put(i++);
}
}
}

class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, “Consumer”).start();
}
public void run() {
while(true) {
q.get();
}
}
}

class PCFixed {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
System.out.println(“Press Control-C to stop.”);
}
}

Inside get( )wait( ) is called. This causes its execution to suspend until the Producer notifies you that some data is ready. When this happens, execution inside get( ) resumes. After the data has been obtained, get( ) calls notify( ). This tells Producer that it is okay to put more data in the queue. Insideput( )wait( ) suspends execution until the Consumer has removed the item from the queue. When execution resumes, the next item of data is put in the queue, and notify( ) is called. This tells theConsumer that it should now remove it.

Here is some output from this program, which shows the clean synchronous behavior:

Put: 1
Got: 1
Put: 2
Got: 2
Put: 3
Got: 3
Put: 4
Got: 4
Put: 5
Got: 5

This tutorial is an extract from the book “The complete Reference Java 2” by Herbert Schildt

Core Java (2) – So sánh 2 đối tượng

Giới thiệu:

– Trong java, có các kiểu dữ liệu như int, char, String, Date … đã được dựng sẵn … ta chỉ cần sử dụng. Nhưng đôi khi chính các đối tượng này được xây dựng sẵn, khiến cho ta hay bị lầm lẫn giữa các khái niệm như: bằng nhau, lớn – nhỏ hơn khi ta tự xây dựng các đối tượng.

– Bài viết này hi vọng có thể giúp các bạn phần nào điều đó.

Vấn đề:

– Với các kiểu dữ liệu có sẵn, mình không bàn tới, nếu nó là int, char… thì có thể dùng dấu == để so sánh. Còn với các đối tượng khác, như String hay Date thì gọi hàm equals(), hai cách đem lại cùng một kết quả.

– Nhưng với các đối tượng bạn tự tạo ra thì sao? Xét đoạn chương trình sau:

//vd1

import java.util.Comparator;
import java.util.HashSet;

public class HelloWorld {

public static void main(String[] agrs) {

Person a = new Person();
Person b = new Person();

a.name=new String(“hiepnq”);
b.name=”hiepnq”;

a.age=22;
b.age=22;

HashSet set = new HashSet();
set.add(a);
set.add(b);

System.out.println(set.size());
}
}

class Person {
public String name;
public int age;
}

-> mặc dù 2 instance a và b có các cặp thuộc tính tương ứng bằng nhau (a.name.equals(b.name) ==true và a.age==b.age), nhưng khi in ra màn hình, HashSet set lại coi a và b là 2 đối tượng khác nhau, nên set.size() in ra lại là 2.

– Vấn đề này thoạt nhìn thì tưởng như chẳng có gì, nhưng nếu sử dụng trong kĩ thuật lập trình hiện đại, thì lại là một điều vô cùng tai hại: bạn sẽ khó có thể kiểm soát được việc so sánh 2 đối tượng, điều này khiến cho một số chức năng khác có liên quan tới so sánh sẽ không thực hiện được (vd: sắp xếp – tìm kiếm).

Vấn đề: Tại sao lại có hiện tượng như trên? Cơ chế nào khiến cho 2 đối tượng được gọi là bằng nhau?

– Trả lời cho vấn đề này, chúng ta tìm hiểu kĩ một chút về java nói riêng và các ngôn ngữ lập trình hiện đại nói chung (C# cũng tuân theo nguyên tắc này…). Trong bộ nhớ, khác với C/C++, Java quản lí đối tượng theo mã băm (hashCode), có nghĩa là: địa chỉ bộ nhớ các đối tượng sẽ được “băm” (hash) theo một công thức nào đó, trở thành một số int duy nhất, không trùng lặp khi trên cùng 1 máy tính.

– Tất cả các đối tượng trong Java đều có 1 gốc đối tượng cha duy nhất là Object, bản thân đối tượng Object có 2 phương thức: equals() và hashCode(). equals() sẽ dùng để so sánh các đối tượng, các lớp dẫn xuất từ Object có thể định nghĩa thế nào là bằng nhau nhờ Override hàm này (vd: String, Date …) và nếu chúng ta sử dụng thông thường, chúng ta cũng vẫn có thể sử dụng hàm này để so sánh, tham khảo đoạn code sau:

//vd2

import java.util.Comparator;
import java.util.HashSet;

public class HelloWorld {

public static void main(String[] agrs) {

Person a = new Person();
Person b = new Person();

a.name=new String(“hiepnq”);
b.name=”hiepnq”;

a.age=22;
b.age=22;

System.out.println(a.equals(b));
}
}

class Person {
public String name;
public int age;

@Override
public boolean equals(Object obj){
if(obj instanceof Person){
if(((Person)obj).name.equals(this.name)){
return true;
}
}
return false;
}
}

-> kết quả trả về là true, tốt rồi…

– Vấn đề kế tiếp được đặt ra: trong vd1 thì sao? nếu ta override hàm equals()? Lớp Person sẽ có dạng:

class Person {
public String name;
public int age;

@Override
public boolean equals(Object obj){
if(obj instanceof Person){
if(((Person)obj).name.equals(this.name)){
return true;
}
}
return false;
}
}

-> nhưng nếu thay vào vd1, thì set.size() vẫn là 2 – có nghĩa là: khi ta sử dụng, thì ta có thể coi là bằng nhau khi Perso.name bằng nhau, nhưng với các collection Set và Map (cả HashTable) thì chúng không cho rằng 2 đối tượng trên đã bằng nhau? tại sao vậy?

– Như đã nói ở trên, Object còn có hàm hashCode() trả về mã băm của đối tượng, mặc định, hai đối tượng được coi là bằng nhau, nếu chúng có hashCode() bằng nhau, có nghĩa là, chúng trỏ tới cùng 1 đối tượng trên vùng nhớ HEAP… Vậy, để khắc phục điều này, ta cần phải override cả hàm hashCode() nữa…

import java.util.Comparator;
import java.util.HashSet;

public class HelloWorld {

public static void main(String[] agrs) {

Person a = new Person();
Person b = new Person();

a.name=new String(“hiepnq”);
b.name=”hiepnq”;

a.age=22;
b.age=22;

HashSet set = new HashSet();
set.add(a);
set.add(b);

System.out.println(set.size());
}
}

class Person {
public String name;
public int age;

@Override
public boolean equals(Object obj){
if(obj instanceof Person){
if(((Person)obj).name.equals(this.name)){
return true;
}
}
return false;
}

@Override
public int hashCode(){
return age;
}
}

-> giờ thì kết quả là 1 rồi

– Vậy, muốn đối tượng bằng nhau trọn vẹn khi và chỉ khi: hàm equals() trả về true, và hàm hashCode() trả về cùng một giá trị.

Kết luận:

– Định nghĩa 2 đối tượng bằng nhau được coi là 1 trong những vấn đề cốt lõi và then chốt nhất trong Core Java và cả C# căn bản nữa, nắm bắt được yếu tố này, các bạn có thể tự định nghĩa các đối tượng bằng nhau một các rất mềm dẻo

– Chúc các bạn thành công!