FutureTask is the implementation class of future. It implements two interfaces at the same time: Runnable and Future, so it can be executed by threads as Runnable and get the return value of Callable as Future.
So we can:
- Call the run() method of the FutureTask object to execute
- Call the get() method of the FutureTask object to get the result
- FutureTask objects can also be implemented as callable implementations using thread pools or Thread classes.
FutureTask has two important attributes: state and runner. FutureTask supports cancel because of these two attributes
Where state is an enumerated value:
NEW New 0
COMPLETING Execution 1
NORMAL Normal 2
EXCEPTIONAL anomaly 3
CANCELLED Cancel 4
INTERRUPTING interruption 5
INTERRUNPED was interrupted 6
There are four ways in which state changes
NEW->COMPLETING->NORMAL Normal Completion Process
NEW-> COMPLETING-> EXCEPTIONAL Abnormal Process
NEW - > CANCELED cancelled
NEW - > INTERRUNPING - > INTERRRUNPTED interrupted
Let's rewrite the previous example with FutureTask:
public class FutureDemo {
public static void main(String[] args) {
/* Define the producer: Callable for making mooncakes */
final Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
/*It takes 5 seconds to simulate time-consuming operations.*/
Thread.sleep(5000);
/*Return to a box of prepared mooncakes*/
return new Random().nextInt(10000);
}
};
/*Open Thread B - Consumer: Get Mooncakes*/
Runnable runnable=new Runnable() {
public void run() {
try {
ExecutorService tPool = Executors.newSingleThreadExecutor();
System.out.println("Boss, start making moon cakes for me...");
/*Start thread A - Producer: Run time-consuming operations to produce mooncakes
*Get a Mooncake Coupon Cook Ticket at the same time*/
//final Future<Integer> CookTicket = tPool.submit(callable);
FutureTask<Integer> CookTicket = new FutureTask<Integer>(callable);
tPool.submit(CookTicket);
//CookTicket.run(); another way of calling
//new Thread(CookTicket).run(); another way of calling
/*Get the mooncakes*/
System.out.println("5 Seconds later, the moon cake coupon is exchanged for the moon cake. The box number is:"+CookTicket.get());
System.out.println("Take the cake home...");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
};
new Thread(runnable).start();
}
}
FutureTask has a method void done() that calls back when each thread completes the return result.
If we need to implement each thread to execute the follow-up task actively after the task is executed.
public class FutureDemo {
public static void main(String[] args) {
/* Define the producer: Callable for making mooncakes */
final Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
/*It takes 5 seconds to simulate time-consuming operations.*/
Thread.sleep(5000);
/*Return to a box of prepared mooncakes*/
return new Random().nextInt(10000);
}
};
/*Open Thread B - Consumer: Get Mooncakes*/
Runnable runnable = new Runnable() {
public void run() {
System.out.println("Boss, start making moon cakes for me...");
/*Opening multiple threads simultaneously with a cache thread pool*/
ExecutorService tPool = Executors.newCachedThreadPool();
/*Start thread A - Producer: run time-consuming operation, three production lines start to produce moon cakes*/
for(int i=0;i<3;i++){
FutureTask<Integer> CookTicket = new FutureTask<Integer>(callable){
/*When a thread finishes its task, it calls back the done function to execute the consumption task.*/
protected void done() {
super.done();
try {
/*get()Is the method of extracting results*/
System.out.println("5 Seconds later, the moon cake coupon is exchanged for the moon cake. The box number is:"+get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
tPool.submit(CookTicket);
//new Thread(CookTicket).run();
}
}
};
new Thread(runnable).start();
}
}
FutureTask ensures that tasks are executed only once in a highly concurrent environment
There is an example on the internet, but it is not very clear in the middle. I combed it again.
In many highly concurrent environments, we often only need to perform certain tasks once. This feature of the use scenario FutureTask is just right. For example, suppose there is a pool of connections with keys, which returns directly the object corresponding to keys when keys exist, and creates connections when keys do not exist. For such application scenarios, the usual method is to use a Map object to store the corresponding relationship between key and connection pool. The typical code is as follows:
private Map<String, Connection> connectionPool = new HashMap<String, Connection>();
private ReentrantLock lock = new ReentrantLock();
public Connection getConnection(String key){
try{
lock.lock();
if(connectionPool.containsKey(key)){
return connectionPool.get(key);
}
else{
//Create Connection
Connection conn = createConnection();
connectionPool.put(key, conn);
return conn;
}
}
finally{
lock.unlock();
}
}
In the example above, we used locking to ensure thread security in high concurrency environments and to ensure that connection was created only once, at the expense of performance. In the case of Concurrent Hash, the operation of locking can be almost avoided and the performance is greatly improved.
private ConcurrentHashMap<String,Connection> connectionPool = new ConcurrentHashMap<String,Connection>();
public Connection getConnection(String key){
Connection conn=connectionPool.get(key);
if(conn!=null){
return conn;
}else {
conn=createConnection();
Connection return_conn = connectionPool.putIfAbsent(key,conn);
//Based on the return value of putIfAbsent, it is judged whether threads have been inserted in advance.
if(return_conn!=null){
conn=return_conn;
}
}
return conn;
}
//Create Connection
private Connection createConnection(){
return null;
}
However, in the case of high concurrency, Connection may be created many times.
Why? Because creating a Connection is a time-consuming operation, assuming that multiple threads flood into the getConnection method and find that the key does not exist, all inbound threads begin to execute conn=createConnection(), but only one thread can insert the connection into the map eventually. But since then, connections created by other threads have little value and waste system overhead.
The most important problem to be solved at this time is that when the key does not exist, the action of creating Connection (conn=createConnection();) can be executed after connection Pool. putIfAbsent (). This is the right time for FutureTask to play its role. The code of modification based on Concurrent HashMap and FutureTask is as follows:
private ConcurrentHashMap<String,FutureTask<Connection>>connectionPool =
new ConcurrentHashMap<String, FutureTask<Connection>>();
public Connection getConnection(String key) throws Exception{
FutureTask<Connection>connectionTask=connectionPool.get(key);
if(connectionTask!=null){
return connectionTask.get();
}
else{
Callable<Connection> callable = new Callable<Connection>(){
@Override
public Connection call() throws Exception {
// TODO Auto-generated method stub
return createConnection();
}
};
FutureTask<Connection> newTask = new FutureTask<Connection>(callable);
connectionTask = connectionPool.putIfAbsent(key, newTask);
if(connectionTask==null){
connectionTask = newTask;
connectionTask.run();
}
return connectionTask.get();
}
}
//Create Connection
private Connection createConnection(){
return null;
}