Task And / Or Thread

Joe

Thành viên VIP
21/1/13
2,689
1,244
113
In my tutorial Java Essentials For Newbies And Developers I've briefly talked about the difference between Task and Thread. Today I discuss more about this issue.

As said, Task and Thread are independent processes which run along with their "owner". Thread is a lightweight of Task. Meaning: Thread rarely has its own working environment. Mostly thread works within its parents environment where data are shared between parents and threads. Because of sharing data data must be synchronized between parents and threads. Otherwise the data consistency would be unpredictable. If the parents data are declared volatile their actual values are taken by JVM directly from memory without "optimizing them" (i.e. using the copied or referenced value for processing). Example:
PHP:
// source: https://dzone.com/articles/java-volatile-keyword-0
// modified: change LOGGER to println and makes the volatile usage clearer
public class VolatileTest {
	private static int MY_INT = 0;
	public static void main(String[] args) {
		new ChangeListener().start();
		new ChangeMaker().start();
	}
	static class ChangeListener extends Thread {
		@Override
		public void run() {
			int local_value = MY_INT;
			while ( local_value < 5){
				if( local_value != MY_INT){
					System.out.println("local_value:"+local_value+"-Got Change for MY_INT :"+ MY_INT);
					local_value = MY_INT;
				}
			}
		}
	}
	static class ChangeMaker extends Thread{
		@Override
		public void run() {
 
			int local_value = MY_INT;
			while (MY_INT < 5){
				MY_INT = ++local_value;
				System.out.println("local_value:"+local_value+"->Incrementing MY_INT to "+MY_INT);
				try {
					Thread.sleep(100);
				} catch (Exception e) {  }
			}
		}
	}
}
The output with volatile declaration:
PHP:
C:\JFX\TestII>java VolatileTest
local_value:1->Incrementing MY_INT to 1
local_value:0->Got Change for MY_INT :1
local_value:2->Incrementing MY_INT to 2
local_value:1->Got Change for MY_INT :2
local_value:3->Incrementing MY_INT to 3
local_value:2->Got Change for MY_INT :3
local_value:3->Got Change for MY_INT :4
local_value:4->Incrementing MY_INT to 4
local_value:4->Got Change for MY_INT :5
local_value:5->Incrementing MY_INT to 5
 
C:\JFX\TestII>
As you see, Got and Incrementing have the same value. If the keyword volatile is removed the ChangeListener thread will loop forever because JVM tries to optimize the data with the "most actual" value and causes the troubles (here: local_value is assigned to the copied value of MY_INT which is now 1 and that makes them becoming equal).

Output without volatile
PHP:
C:\JFX\TestII>java VolatileTest
local_value:1->Incrementing MY_INT to 1
local_value:0->Got Change for MY_INT :1
local_value:2->Incrementing MY_INT to 2
local_value:3->Incrementing MY_INT to 3
local_value:4->Incrementing MY_INT to 4
local_value:5->Incrementing MY_INT to 5
Volatile data are always accessed by JVM from the main memory when they are referenced. Same to synchronized data. The only difference between volatile and synchronization is that volatile data could be inconsistent because they can be interfered by other threads, but synchronized data are kept away from such an interfering and therefore consistent. If you have to work with sub-processes and want to monitor their status you have the choice between Thread and Task.

THREAD
Thread is a stateless process. Meaning: thread won't return anything after termination. To monitor a thread, especially when it is run by ExecutiveService, the parents process has to devise some "state" for its thread. The devised "state" should be independent from other threads and can be only changed by the thread itself and accessed by its parents.

TASK
Task is on the other hand a stateful process. A task always returns something when it terminates. Object Void is used if it won't return anything (JAVA: null). Example:
PHP:
  class VoidTask implements Callable<Void> {
	public vTask(int i) {
	  ...
	}
	public Void call() {
	  ...
	  return null; // for Void
	}
	private int i;
  }
The parents can verify the returned value at any time for the processing status of the task using the API Future<V> where V is the generic form for any Java Object (e.g. int -> Integer, etc.).

SYNCHRONIZATION
If parents data are used in both cases (Thread and Task) the data have to be synchronized. The keyword synchronized is here for the usage.

Keyword this is for primitives or an expression block. Example: local: "int cnt", parents: "int counter"
PHP:
int cnt;
synchronized(this) {
  cnt = counter++;
}
Object ArrayList<String> of parents
PHP:
synchronized(this) {
  aList.add("Joe");
}
// or
synchronized(aList) {
  aList.add("Joe");
}
The following example shows you how Tasks and Threads work within Java ExecutiveService.
PHP:
import java.util.concurrent.*;
// Joe Nartca (C)
public class TaskAndThread {
  public TaskAndThread(int n) throws Exception {
	ExecutorService pool = Executors.newCachedThreadPool( );
	t = new long[n];
	s = new String[n];
	th = new boolean[n];
	for (int i = 0; i < n; ++i) th[i] = false;
	System.out.println("-----------Working with THREAD-----------");
	for (int i = 0; i < n; ++i) {
	  t[i] = System.nanoTime();
	  pool.submit(new aThread(i));
	}
	int i = 0;
	while (i < n) { // check for Thread termination
	  for (int l = 0; l < n; ++l) if (th[l]) {
		System.out.print(s[l]);
		th[l] = false;
		++i;
	  }
	}
	System.out.println("-----------Working with TASK-----------");
	java.util.ArrayList<Future<Boolean>> a = new java.util.ArrayList<Future<Boolean>>();
	for (i = 0; i < n; ++i) {
	  t[i] = System.nanoTime();
	  a.add(pool.submit(new aTask(i)));
	}
	i = 0;  // check for Task termination
	for (Future<Boolean> f : a) {
	  if (f.get()) {
		System.out.print(s[i++]);
	  }
	}
	pool.shutdown( );
  }
  public static void main(String... argv) throws Exception {
	new TaskAndThread(argv.length > 0? Integer.parseInt(argv[0]):10);
  }
  //------------------------ Task ------------------------
  private class aTask implements Callable<Boolean> {
	public aTask(int i) {
	  this.i = i;
	}
	public Boolean call() {
	  //
	  // your codes
	  //
	  int cnt;
	  synchronized(this) {
		cnt = counter++;
	  }
	  s[i] = String.format("Task_%d terminates. Time:%.3f milliSec. Counter:%d\n",
						   i,((double)System.nanoTime( )-t[i])/1000000, cnt);
	  return true;
	}
	private int i;
  }
  //------------------------ Thread ------------------------
  private class aThread implements Runnable {
	public aThread(int i) {
	  this.i = i;
	}
	public void run() {
	  //
	  // your codes
	  //
	  int cnt;
	  synchronized(this) {
		cnt = counter++;
	  }
	  s[i] = String.format("Thread_%d terminates. Time:%.3f milliSec. Counter:%d\n",
						   i,((double)System.nanoTime( )-t[i])/1000000, cnt);
	  th[i] = true; // saying "finished"
	}
	private int i;
  }
  private long[] t;
  private String[] s;
  private int counter;
  private boolean th[];
}
The output
PHP:
C:\JFX\TestII>java TaskAndThread
-----------Working with THREAD-----------
Thread_9 terminates. Time:0,531 milliSec. Counter:8
Thread_0 terminates. Time:16,063 milliSec. Counter:0
Thread_1 terminates. Time:0,760 milliSec. Counter:1
Thread_2 terminates. Time:1,184 milliSec. Counter:4
Thread_3 terminates. Time:0,896 milliSec. Counter:2
Thread_4 terminates. Time:0,782 milliSec. Counter:3
Thread_5 terminates. Time:1,078 milliSec. Counter:6
Thread_6 terminates. Time:0,856 milliSec. Counter:5
Thread_7 terminates. Time:1,136 milliSec. Counter:9
Thread_8 terminates. Time:0,709 milliSec. Counter:7
-----------Working with TASK-----------
Task_0 terminates. Time:2,162 milliSec. Counter:0
Task_1 terminates. Time:0,029 milliSec. Counter:1
Task_2 terminates. Time:0,023 milliSec. Counter:2
Task_3 terminates. Time:0,023 milliSec. Counter:3
Task_4 terminates. Time:0,022 milliSec. Counter:4
Task_5 terminates. Time:0,023 milliSec. Counter:5
Task_6 terminates. Time:0,021 milliSec. Counter:6
Task_7 terminates. Time:0,021 milliSec. Counter:7
Task_8 terminates. Time:0,024 milliSec. Counter:8
Task_9 terminates. Time:0,025 milliSec. Counter:9
 
C:\JFX\TestII>
As you see, task is in this case faster than thread. The reason is simple: Thread has to work with a global variable (here: boolean th[ ]) while Task simply return a (local) value.

The API Executors gives a variety of Service-choices. The newWorkStealingPool is purposed for parallelism. Precondition is that your computer supports parallelism. In case of parallelism, especially with parallelStream(), you should be very aware about "stateless" or "stateful"of the processes. Otherwise you could get wrong results. A stateless process is independent from any external event such as modification, waiting for a result, etc. In this case Parallelism is ideal. More about Parallelism: HERE or THIS.
 
Sửa lần cuối: