Friday, August 12, 2016

TransferQueue

If someone is already waiting to take the element, we don't need to put it in the queue, just directly transfer it to receiver.

Java 7 introduced the TransferQueue. This is essentially a BlockingQueue with an
additional operation—transfer(). This operation will immediately transfer a work
item to a receiver thread if one is waiting. Otherwise it will block until there is a thread
available to take the item. This can be thought of as the “recorded delivery” option—
the thread that was processing the item won’t begin processing another item until it
has handed off the current item. This allows the system to regulate the speed at which
the upstream thread pool takes on new work.
It would also be possible to regulate this by using a blocking queue of bounded
size, but the TransferQueue has a more flexible interface. In addition, your code may
show a performance benefit by replacing a BlockingQueue with a TransferQueue.
This is because the TransferQueue implementation has been written to take into
account modern compiler and processor features and can operate with great efficiency.
As with all discussions of performance, however, you must measure and prove
benefits and not simply assume them. You should also be aware that Java 7 ships with
only one implementation of TransferQueue—the linked version.
In the next code example, we’ll look at how easy it is to drop in a TransferQueue as
a replacement for a BlockingQueue. Just these simple changes to listing 4.13 will
upgrade it to a TransferQueue implementation, as you can see here.



public class BlockingQueueTest1 {
 public static void main(String[] args) {

  final Update.Builder ub = new Update.Builder();
  final TransferQueue<Update> lbq = new LinkedTransferQueue<Update>();
  MicroBlogExampleThread t1 = new MicroBlogExampleThread(lbq, 10) {
   public void doAction() {
    text = text + "X";
    Update u = ub.author(new Author("Tallulah")).updateText(text)
      .build();
    boolean handed = false;
    try {
     handed = updates.tryTransfer(u, 100, TimeUnit.MILLISECONDS);//tryTransfer
    } catch (InterruptedException e) {
    }
    if (!handed)
     System.out.println("Unable to hand off Update to Queue due to timeout");
    else System.out.println("Offered");
   }
  };
  MicroBlogExampleThread t2 = new MicroBlogExampleThread(lbq, 200) {
   public void doAction() {
    Update u = null;
    try {
     u = updates.take();
     System.out.println("Took");
    } catch (InterruptedException e) {
     return;
    }
   }
  };

  t1.start();
  t2.start();
 }
}


abstract class MicroBlogExampleThread extends Thread {
 protected final TransferQueue<Update> updates;
 protected String text = "";
 protected final int pauseTime;
 private boolean shutdown = false;

 public MicroBlogExampleThread(TransferQueue<Update> lbq_, int pause_) {
  updates = lbq_;
  pauseTime = pause_;
 }

 public synchronized void shutdown() {
  shutdown = true;
 }

 @Override
 public void run() {
  while (!shutdown) {
   doAction();
   try {
    Thread.sleep(pauseTime);
   } catch (InterruptedException e) {
    shutdown = true;
   }
  }
 }

 public abstract void doAction();
}









Fine-Grained Control of BlockingQueue

We have learned BlockingQueue
http://gvace.blogspot.com/2016/08/blockingqueue.html

In addition to the simple take() and offer() API, BlockingQueue offers another way
to interact with the queue that provides even more control, at the cost of a bit of
extra complexity. This is the possibility of putting or taking with a timeout, to allow
the thread encountering issues to back out from its interaction with the queue and do something else instead.


import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class BlockingQueueTest1 {
 public static void main(String[] args) {

  final Update.Builder ub = new Update.Builder();
  final BlockingQueue<Update> lbq = new LinkedBlockingQueue<>(100);
  MicroBlogExampleThread t1 = new MicroBlogExampleThread(lbq, 10) {
   public void doAction() {
    text = text + "X";
    Update u = ub.author(new Author("Tallulah")).updateText(text)
      .build();
    boolean handed = false;
    try {
     handed = updates.offer(u, 100, TimeUnit.MILLISECONDS);//set timeout
    } catch (InterruptedException e) {
    }
    if (!handed)
     System.out.println("Unable to hand off Update to Queue due to timeout");
    else System.out.println("Offered");
   }
  };
  MicroBlogExampleThread t2 = new MicroBlogExampleThread(lbq, 1000) {
   public void doAction() {
    Update u = null;
    try {
     u = updates.take();
     System.out.println("Took");
    } catch (InterruptedException e) {
     return;
    }
   }
  };

  t1.start();
  t2.start();
 }
}
abstract class MicroBlogExampleThread extends Thread {
 protected final BlockingQueue<Update> updates;
 protected String text = "";
 protected final int pauseTime;
 private boolean shutdown = false;

 public MicroBlogExampleThread(BlockingQueue<Update> lbq_, int pause_) {
  updates = lbq_;
  pauseTime = pause_;
 }

 public synchronized void shutdown() {
  shutdown = true;
 }

 @Override
 public void run() {
  while (!shutdown) {
   doAction();
   try {
    Thread.sleep(pauseTime);
   } catch (InterruptedException e) {
    shutdown = true;
   }
  }
 }

 public abstract void doAction();
}




Followed a TransferQueue which is a little more efficient.
http://gvace.blogspot.com/2016/08/transferqueue.html





Thursday, August 11, 2016

BlockingQueue

The BlockingQueue is a queue that has two additional special properties:
■ When trying to put() to the queue, it will cause the putting thread to wait for
space to become available if the queue is full.
■ When trying to take() from the queue, it will cause the taking thread to block
if the queue is empty.
These two properties are very useful because if one thread (or pool of threads) is outstripping
the ability of the other to keep up, the faster thread is forced to wait, thus
regulating the overall system.


Two implementations of BlockingQueue
Java ships with two basic implementations of the BlockingQueue interface: the
LinkedBlockingQueue and the ArrayBlockingQueue. They offer slightly different
properties; for example, the array implementation is very efficient when an exact
bound is known for the size of the queue, whereas the linked implementation may be
slightly faster under some circumstances.

It can be better to have this,

BlockingQueue<WorkUnit<MyAwesomeClass>>

where WorkUnit (or QueueObject, or whatever you want to call the container class) is
a packaging interface or class that may look something like this:
public class WorkUnit<T> {
 private final T workUnit;

 public T getWork() {
  return workUnit;
 }

 public WorkUnit(T workUnit_) {
  workUnit = workUnit_;
 }
}

The reason for doing this is that this level of indirection provides a place to add additional
metadata without compromising the conceptual integrity of the contained type
(MyAwesomeClass in this example).
This is surprisingly useful. Use cases where additional metadata is helpful are
abundant. Here are a few examples:
■ Testing (such as showing the change history for an object)
■ Performance indicators (such as time of arrival or quality of service)
■ Runtime system information (such as how this instance of MyAwesomeClass has
been routed)

Example

public class Appointment<T> {
 private final T toBeSeen;

 public T getPatient() {
  return toBeSeen;
 }

 public Appointment(T incoming) {
  toBeSeen = incoming;
 }
}
abstract class Pet {
 protected final String name;

 public Pet(String name) {
  this.name = name;
 }

 public abstract void examine();
}
class Cat extends Pet {
 public Cat(String name) {
  super(name);
 }

 public void examine() {
  System.out.println("Meow!");
 }
}
class Dog extends Pet {
 public Dog(String name) {
  super(name);
 }

 public void examine() {
  System.out.println("Woof!");
 }
}




From this simple model, you can see that we can model the veterinarian’s queue as
LinkedBlockingQueue<Appointment<Pet>>, with the Appointment class taking the role
of WorkUnit.




import java.util.concurrent.BlockingQueue;

public class Veterinarian extends Thread {
 protected final BlockingQueue<Appointment<Pet>> appts;
 protected String text = "";
 protected final int restTime;
 private boolean shutdown = false;

 public Veterinarian(BlockingQueue<Appointment<Pet>> lbq, int pause) {
  appts = lbq;
  restTime = pause;
 }

 public synchronized void shutdown() {
  shutdown = true;
 }

 @Override
 public void run() {
  while (!shutdown) {
   seePatient();
   try {
    Thread.sleep(restTime);
   } catch (InterruptedException e) {
    shutdown = true;
   }
  }
 }

 public void seePatient() {
  try {
   Appointment<Pet> ap = appts.take();
   Pet patient = ap.getPatient();
   patient.examine();
  } catch (InterruptedException e) {
   shutdown = true;
  }
 }
}


The following is instance to start two threads, one is en-queue pets, one is examine cats in the queue.

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;


public class BlockingQueueTest {
 public static final BlockingQueue<Appointment<Pet>> QUEUE = new LinkedBlockingQueue<Appointment<Pet>>(10);
 public static void main(String[] args){
  new Thread(){
   @Override
   public void run(){
    for(int i=0;i<10;i++){
     Pet p1 = new Cat("cat"+i);
     Pet p2 = new Dog("dog"+i);
     Appointment<Pet> app1 = new Appointment<Pet>(p1);
     Appointment<Pet> app2 = new Appointment<Pet>(p2);
     try {
      QUEUE.put(app1);
      System.out.println("EnqueueCat"+i);
      QUEUE.put(app2);
      System.out.println("EnqueueDog"+i);
      Thread.sleep(300);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   }
  }.start();
  
  Veterinarian veter = new Veterinarian(QUEUE, 500);
  veter.start();
 }
}

We have investigate on BlockingQueue Usage
http://gvace.blogspot.com/2016/08/fine-grained-control-of-blockingqueue.html

CopyOnWriteArrayList

If the list is altered while a read or a traversal is taking place, the entire array
must be copied.

As the name suggests, the CopyOnWriteArrayList class is a replacement for
the standard ArrayList class. CopyOnWriteArrayList has been made threadsafe
by the addition of copy-on-write semantics, which means that any operations
that mutate the list will create a new copy of the array backing the list
(as shown in figure 4.8). This also means that any iterators formed don’t have to
worry about any modifications that they didn’t expect.
This approach to shared data is ideal when a quick, consistent snapshot of data
(which may occasionally be different between readers) is more important than perfect
synchronization (and the attendant performance hit). This is often seen in non-missioncritical
data.
Let’s look at an example of copy-on-write in action. Consider a timeline of microblogging
updates. This is a classic example of data that isn’t 100 percent mission-critical
and where a performant, self-consistent snapshot for each reader is preferred over
total global consistency. This listing shows a holder class that represents an individual
user’s view of their timeline.


public class MicroBlogTimeline {
 private final CopyOnWriteArrayList<Update> updates;
 private final ReentrantLock lock;
 private final String name;
 private Iterator<Update> it;

 public MicroBlogTimeline(String name_,
   CopyOnWriteArrayList<Update> updates_, ReentrantLock lock_) {
  this.name = name_;
  this.updates = updates_;
  this.lock = lock_;
 }

 public void addUpdate(Update update_) {
  updates.add(update_);
 }

 public void prep() { // Set up Iterator
  it = updates.iterator();
 }

 public void printTimeline() {
  lock.lock();
  try {
   if (it != null) {
    System.out.print(name + ": ");
    while (it.hasNext()) {
     Update s = it.next();
     System.out.print(s + ", ");
    }
    System.out.println();
   }
  } finally {
   lock.unlock();
  }
 }
}

class CopyOnWriteArrayListTest {
 public static void main(String[] args) {
  final CountDownLatch firstLatch = new CountDownLatch(1);
  final CountDownLatch secondLatch = new CountDownLatch(1);
  final Update.Builder ub = new Update.Builder();
  final CopyOnWriteArrayList<Update> l = new CopyOnWriteArrayList<>();
  l.add(ub.author(new Author("Ben")).updateText("I like pie").build());
  l.add(ub.author(new Author("Charles")).updateText("I like ham on rye").build());
  ReentrantLock lock = new ReentrantLock();
  final MicroBlogTimeline tl1 = new MicroBlogTimeline("TL1", l, lock);
  final MicroBlogTimeline tl2 = new MicroBlogTimeline("TL2", l, lock);
  Thread t1 = new Thread() {
   public void run() {
    l.add(ub.author(new Author("Jeffrey")).updateText("I like a lot of things").build());
    tl1.prep();
    firstLatch.countDown();
    try {
     secondLatch.await();
    } catch (InterruptedException e) {
    }
    tl1.printTimeline();
   }
  };
  Thread t2 = new Thread() {
   public void run() {
    try {
     firstLatch.await();
     l.add(ub.author(new Author("Gavin")).updateText("I like otters").build());
     tl2.prep();
     secondLatch.countDown();
    } catch (InterruptedException e) {
    }
    tl2.printTimeline();
   }
  };
  t1.start();
  t2.start();
 }
}



There is a lot of scaffolding in the listing—unfortunately this is difficult to avoid.
There are quite a few things to notice about this code:
■ CountDownLatch is used to maintain close control over what is happening
between the two threads.
■ If the CopyOnWriteArrayList was replaced with an ordinary List (B), the
result would be a ConcurrentModificationException.
■ This is also an example of a Lock object being shared between two threads to
control access to a shared resource (in this case, STDOUT). This code would be
much messier if expressed in the block-structured view.


As you can see, the second output line (tagged as TL1) is missing the final update (the
one that mentions otters), despite the fact that the latching meant that mbex1 was
accessed after the list had been modified. This demonstrates that the Iterator contained
in mbex1 was copied by mbex2, and that the addition of the final update was invisible
to mbex1. This is the copy-on-write property that we want these objects to display.


CuncurrentHashMap

The classic HashMap uses a function (the hash function) to determine which “bucket” it will store the key/value pair in. This is where the “hash” part of the class’s name comes from. This suggests a rather straightforward multithreaded generalization—instead of needing to lock the whole structure when making a change, it’s only necessary to lock the bucket that’s being altered.


The ConcurrentHashMap class also implements the ConcurrentMap interface, which contains some new methods to provide truly atomic functionality:
■ putIfAbsent()—Adds the key/value pair to the HashMap if the key isn’t already present.
■ remove()—Atomically removes the key/value pair only if the key is present and the value is equal to the current state.
■ replace()—The API provides two different forms of this method for atomic replacement in the HashMap.
As an example, you can replace the synchronized methods in listing with regular, unsynchronized access if you alter the HashMap called arrivalTime to be a ConcurrentHashMap as well. Notice the lack of locks in the following listing—there is no explicit synchronization at all.


Original HashMap
public class ExampleTimingNode implements SimpleMicroBlogNode {
 private final String identifier;
 private final Map<Update, Long> arrivalTime = new HashMap<>();

 public ExampleTimingNode(String identifier_) {
  identifier = identifier_;
 }

 public synchronized String getIdentifier() {
  return identifier;
 }

 public synchronized void propagateUpdate(Update update_) {
  long currentTime = System.currentTimeMillis();
  arrivalTime.put(update_, currentTime);
 }

 public synchronized boolean confirmUpdateReceived(Update update_) {
  Long timeRecvd = arrivalTime.get(update_);
  return timeRecvd != null;
 }
}

Replace with ConcurrentHashMap

import java.util.concurrent.ConcurrentHashMap;

public class ExampleMicroBlogTimingNode implements SimpleMicroBlogNode {

 private final ConcurrentHashMap<Update, Long> arrivalTime = new ConcurrentHashMap<Update, Long>();

 public void propagateUpdate(Update upd_) {
  arrivalTime.putIfAbsent(upd_, System.currentTimeMillis());
 }

 public boolean confirmUpdateReceived(Update upd_) {
  return arrivalTime.get(upd_) != null;
 }
}

CountDownLatch

CountDownLatch: Let's start together

This is achieved by providing an int value (the count) when constructing a new
instance of CountDownLatch. After that point, two methods are used to control the
latch: countDown() and await(). The former reduces the count by 1, and the latter
causes the calling thread to wait until the count reaches 0 (it does nothing if the count
is already 0 or less). This simple mechanism allows the minimum preparation pattern
to be easily deployed.



import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

public class ProcessingThreadTest {
 public static final int MAX_THREADS = 10;
 public static class ProcessingThread extends Thread {
  private final String ident;
  private final CountDownLatch latch;
  public ProcessingThread(String ident_, CountDownLatch cdl_) {
   ident = ident_;
   latch = cdl_;
  }
  public String getIdentifier() {
   return ident;
  }
  public void initialize() {
   latch.countDown();
   try {
    latch.await();
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   
  }
  @Override
  public void run() {
   initialize();
   while(true){
    System.out.println(getIdentifier());
    try {
     Thread.sleep(500);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
  }
 }
 public static void main(String[] args) {
  final int quorum = 1 + (int) (MAX_THREADS / 2);
  final CountDownLatch cdl = new CountDownLatch(quorum);
  final Set<ProcessingThread> nodes = new HashSet<>();
  try {
   for (int i = 0; i < MAX_THREADS; i++) {
    ProcessingThread local = new ProcessingThread("localhost:" + (9000 + i), cdl);
    nodes.add(local);
    local.start();
    Thread.sleep(1000);
    System.out.println("-----------------");
   }
  } catch (InterruptedException e) {
  } finally {
  }
 }
}









Wednesday, August 10, 2016

ReentrantLock usage and fix DeadLock

Test code:
Two MicroBlogNode to update themselves and treat each other as backup, then call confirm on the backup.

public class UpdateTest{
 public static void main(String[] args){
  Update.Builder ub = new Update.Builder();
  Author myAuthor = new Author();
  final Update u1 = ub.author(myAuthor).updateText("Hello1").build();
  final Update u2 = ub.author(myAuthor).updateText("Hello2").build();
  
  final MicroBlogNode mbn1 = new MicroBlogNode("11");
  final MicroBlogNode mbn2 = new MicroBlogNode("22");
  new Thread(){
   @Override
   public void run(){
    mbn1.propagateUpdate(u1, mbn2);
   }
  }.start();
  new Thread(){
   @Override
   public void run(){
    mbn2.propagateUpdate(u2, mbn1);
   }
  }.start();
 }
}

Look at a deadlock first, without ReentrantLock:

public class MicroBlogNode{
 private final String ident;
 public MicroBlogNode(String ident_) {
  ident = ident_;
 }
 public String getIdent() {
  return ident;
 }
 public synchronized void propagateUpdate(Update upd_, MicroBlogNode backup_) {
  System.out.println(ident + ": recvd: " + upd_.getUpdateText()
    + " ; backup: " + backup_.getIdent());
  backup_.confirmUpdate(this, upd_);
 }
 public synchronized void confirmUpdate(MicroBlogNode other_, Update update_) {
  System.out.println(ident + ": recvd confirm: "
    + update_.getUpdateText() + " from " + other_.getIdent());
 }
}
When Thread A in propagateUpdate and Thread B also in propagateUpdate, both of them are holding their own lock and waiting for each other to release the lock. This will cause deadlock.

Then we use ReentrantLock instead of synchronized, but this one is still deadlock in the same way.

public class MicroBlogNode1{
 private final String ident;
 private final Lock lock = new ReentrantLock();
 public MicroBlogNode1(String ident_) {
  ident = ident_;
 }
 public String getIdent() {
  return ident;
 }
 public void propagateUpdate(Update upd_, MicroBlogNode1 backup_) {
  lock.lock();
  try {
   System.out.println(ident + ": recvd: " + upd_.getUpdateText()
     + " ; backup: " + backup_.getIdent());
   backup_.confirmUpdate(this, upd_);
  }finally{
   lock.unlock();
  }
 }
 public void confirmUpdate(MicroBlogNode1 other_, Update update_) {
  lock.lock();
  try {
   System.out.println(ident + ": recvd confirm: "
    + update_.getUpdateText() + " from " + other_.getIdent());
  }finally{
   lock.unlock();
  }
 }
}

Trying to fix the deadlock, we can use tryLock with a timeout, and retry until we get the lock.
But this is still will cause  live busy DeadLock, because there will be a time Thread A and B keeping their own locks and retrying to acquire each other's.

public class MicroBlogNode2 {
 private final String ident;
 private final Lock lock = new ReentrantLock();
 public MicroBlogNode2(String ident_) {
  ident = ident_;
 } 
 private String getIdent() {
  return ident;
 }
 public void propagateUpdate(Update upd_, MicroBlogNode2 backup_) {
  boolean acquired = false;
  while (!acquired) {
   try {
    int wait = (int) (Math.random() * 10);
    acquired = lock.tryLock(wait, TimeUnit.MILLISECONDS);
    if (acquired) {
     System.out.println(ident + ": recvd: "
       + upd_.getUpdateText() + " ; backup: "
       + backup_.getIdent());
     backup_.confirmUpdate(this, upd_);
    } else {
     Thread.sleep(wait);
    }
   } catch (InterruptedException e) {
   } finally {
     // Attempts to lock other thread b Try and lock, with random
     // timeout Confirm on other thread
    if (acquired)
     lock.unlock();
   }
  }
 }
 public void confirmUpdate(MicroBlogNode2 other_, Update upd_) {
  lock.lock();// Attempts to lock other thread
  try {
   System.out.println(ident + ": recvd confirm: "
     + upd_.getUpdateText() + " from " + other_.getIdent());
  } finally {
   lock.unlock();
  }
 }
}

To really fix the DeadLock, the Thread need to release the lock when failed to acquire other's lock.
public class MicroBlogNode3 {
 private final String ident;
 private final Lock lock = new ReentrantLock();
 public MicroBlogNode3(String ident_) {
  ident = ident_;
 }
 public String getIdent() {
  return ident;
 }
 public void propagateUpdate(Update upd_, MicroBlogNode3 backup_) {
  boolean acquired = false;
  boolean done = false;
  while (!done) {
   int wait = (int) (Math.random() * 10);
   try {
    acquired = lock.tryLock(wait, TimeUnit.MILLISECONDS);
    if (acquired) {
     System.out.println(ident + ": recvd: "
       + upd_.getUpdateText() + " ; backup: "
       + backup_.getIdent());
     done = backup_.tryConfirmUpdate(this, upd_); //Examine return from tryConfirmUpdate()
    }
   } catch (InterruptedException e) {
   } finally {
    if (acquired)
     lock.unlock();
   }
   if (!done)
    try {
     Thread.sleep(wait);
    } catch (InterruptedException e) {
    }
  }
 }
 public boolean tryConfirmUpdate(MicroBlogNode3 other_, Update upd_) {
  boolean acquired = false;
  try {
   long startTime = System.currentTimeMillis();
   int wait = (int) (Math.random() * 10);
   acquired = lock.tryLock(wait, TimeUnit.MILLISECONDS);
   if (acquired) {
    long elapsed = System.currentTimeMillis() - startTime;
    System.out.println(ident + ": recvd confirm: "
      + upd_.getUpdateText() + " from " + other_.getIdent()
      + " - took " + elapsed + " millis");
    return true;
   }
  } catch (InterruptedException e) {
  } finally {
   if (acquired)
    lock.unlock();
  }
  return false;
 }
}





















Builder Pattern for Immutability

For immutability, we can use either Factory or Builder

If we have a lot parameters in the constructor, the code is kind of mess. And you may have 100 constructors because of this.

Builder pattern:
This is a combination of two constructs:

  • A static inner class that implements a generic builder interface
  • And a private constructor for the immutable class itself.

The static inner class is the builder for the immutable class, and it provides the only way that a developer can get hold of new instances of the immutable type.
One very common implementation is for the Builder class to have exactly the same fields as the immutable class, but to allow mutation of the fields.
This listing shows how you might use this to model a microblogging update (again, building on the earlier listings in this chapter).


public interface ObjBuilder {
 T build();
}


public class Update {

   //   Final fields must be initialized in constructor

 private final Author author;
 private final String updateText;

 private Update(Builder b_) {
  author = b_.author;
  updateText = b_.updateText;
 }

  // Builder class must be static inner

 public static class Builder implements ObjBuilder<Update> {
  private Author author;
  private String updateText;
  
  // Methods on Builder return Builder for chain calls

  public Builder author(Author author_) {
   author = author_;
   return this;
  }

  public Builder updateText(String updateText_) {
   updateText = updateText_;
   return this;
  }

  public Update build() {
   return new Update(this);
  }
   //hashCode() and equals() methods omitted
   
 }
}
class UpdateTest{
 public static void main(String[] args){
  Update.Builder ub = new Update.Builder();
  Author myAuthor = new Author();
  Update u = ub.author(myAuthor ).updateText("Hello").build();
 }
}



With this code, you could then create a new Update object like this:
 Update.Builder ub = new Update.Builder();
 Update u = ub.author(myAuthor).updateText("Hello").build();




















Tuesday, August 2, 2016

Reference Type Strong/Soft/Weak/Phantom

Strong
Regular reference

Soft
Used for memory cache, only gc when memory not enough

Weak
Used for storing data, only gc on the second time. Example: ClassLoader

Phantom
Used only for monitoring if object has already been gc

Reference Queue
When an object has been gc, we can poll it from ReferenceQueue


import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

public class ReferenceType {
 public static void main(String[] args) throws InterruptedException{
  String s1 = new String("aaa");
  String s2 = new String("bbb");
  String s3 = new String("ccc");
  
  ReferenceQueue<String> srq = new ReferenceQueue<String>();
  SoftReference<String> ss = new SoftReference<String>(s1,srq);
  ReferenceQueue<String> wrq = new ReferenceQueue<String>();
  WeakReference<String> ws = new WeakReference<String>(s2,wrq);
  ReferenceQueue<String> prq = new ReferenceQueue<String>();
  PhantomReference<String> ps = new PhantomReference<String>(s3,prq);
  
  s1 = null;
  s2 = null;
  s3 = null;
  
  System.gc();
    
  System.out.println("ss="+ss.get());
  System.out.println("srq="+srq.poll());
  System.out.println("ws="+ws.get());
  System.out.println("wrq="+wrq.poll());
  System.out.println("ps="+ps.get());
  System.out.println("prq="+prq.poll());
 }
}


Output result
ss=aaa
srq=null
ws=null
wrq=java.lang.ref.WeakReference@15db9742
ps=null
prq=java.lang.ref.PhantomReference@6d06d69c