Intro
Normally, you would not have to encapsulate the IP2Proxy Java Component in a Singleton class. You could just create a global IP2Proxy object and instantiate that. After that, just have all threads query proxy data from that global object.
What we are doing in this article is more for those developers who can’t use the IP2Proxy as mentioned above. Or they have tried but ended up with a locked BIN file due to multiple IP2Proxy objects fighting over the file.
We will not cover what is a Singleton class so if you are not familiar, do yourself a favor and click on the link below to learn more.
https://en.wikipedia.org/wiki/Singleton_pattern
Why a Singleton?
When dealing with multiple threads, we have had developers telling us that it’s very common to encounter BIN file locking issues on occasion. This is due to the fact that they are creating a new IP2Proxy object for every query. This is a big no-no since instantiating the IP2Proxy object requires reading of metadata which not only slows down the queries but can cause file deadlocks.
We have come up with a Singleton class to prevent the creation of multiple IP2Proxy objects. To the end users, they will still be creating a new class object for every query but the Singleton will only be created once.
Pre-requisites
Before you can try out the sample code below, you will need to get the IP2Proxy Java Component from the following link. Then follow the instructions to compile the ip2proxy.jar file.
https://www.ip2location.com/development-libraries/ip2proxy/java
You will also need the IP2Proxy BIN files. Get them from the below links:
https://lite.ip2location.com/ip2proxy-lite (FREE LITE VERSION)
https://www.ip2location.com/database/ip2proxy (COMMERCIAL VERSION)
Our Singleton example code
For our example, we will have 3 classes inside our Main.java test file. We have the Main class, the MyRunnable class and the Singleton class.
Let’s take a look at each starting with the Main class.
public class Main { public Main() { } public static void main(String[] args) { List<Thread> threads = new ArrayList<Thread>(); // We will create 20 threads for (int i = 0; i < 20; i++) { Runnable task = new MyRunnable(i); Thread worker = new Thread(task); // We can set the name of the thread worker.setName(String.valueOf(i)); // Start the thread, never call method run() direct worker.start(); // Remember the thread for later usage threads.add(worker); } } }
Looks pretty straightforward, we will create 20 threads to simulate real-life threads and then launching the MyRunnable class in each thread.
Next, take a look at the Singleton class which encapsulates the IP2Proxy class.
class Singleton { // static variable single_instance of type Singleton private static Singleton single_instance = null; public static IP2Proxy ip2proxy = null; // private constructor restricted to this class itself private Singleton(String binfile) throws IOException { ip2proxy = new IP2Proxy(); if (ip2proxy.Open(binfile, IP2Proxy.IOModes.IP2PROXY_MEMORY_MAPPED) != 0) { ip2proxy = null; } } // static method to create instance of Singleton class public static Singleton Singleton(String binfile) throws IOException { // To ensure only one instance is created if (single_instance == null) { single_instance = new Singleton(binfile); } return single_instance; } }
Note that as a Singleton, the constructor is private and instead we use a static method to create an instance if it does not yet exist.
The last class is the MyRunnable class which implements the Runnable interface so the class can be called by a thread.
class MyRunnable implements Runnable { private final String binfile; private final int count = 1000; private final int tid; private String myip = ""; private ProxyResult all; private Random r = new Random(); private Singleton x; private FileWriter fw; private String filename = ""; MyRunnable(int threadid) { this.binfile = "C:\\mydata\\IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL-PROVIDER.BIN"; this.tid = threadid; } @Override public void run() { try { this.filename = "file-" + this.tid + ".txt"; this.fw = new FileWriter(this.filename, false); for (int i = 1; i < count; i++) { this.myip = this.r.nextInt(256) + "." + this.r.nextInt(256) + "." + this.r.nextInt(256) + "." + this.r.nextInt(256); this.x = Singleton.Singleton(this.binfile); if (this.x.ip2proxy != null) { this.all = this.x.ip2proxy.GetAll(this.myip); this.fw.write("Thread ID: " + this.tid + "\n"); this.fw.write("IP: " + this.myip + "\n"); this.fw.write("Is_Proxy: " + String.valueOf(this.all.Is_Proxy) + "\n"); this.fw.write("Proxy_Type: " + this.all.Proxy_Type + "\n"); this.fw.write("Country_Short: " + this.all.Country_Short + "\n"); this.fw.write("Country_Long: " + this.all.Country_Long + "\n"); this.fw.write("Region: " + this.all.Region + "\n"); this.fw.write("City: " + this.all.City + "\n"); this.fw.write("ISP: " + this.all.ISP + "\n"); this.fw.write("Domain: " + this.all.Domain + "\n"); this.fw.write("Usage_Type: " + this.all.Usage_Type + "\n"); this.fw.write("ASN: " + this.all.ASN + "\n"); this.fw.write("AS: " + this.all.AS + "\n"); this.fw.write("Last_Seen: " + this.all.Last_Seen + "\n"); this.fw.write("Threat: " + this.all.Threat + "\n"); this.fw.write("Provider: " + this.all.Provider + "\n"); this.fw.write("===========================================================================\n"); } else { System.out.println("Could not open BIN file"); } } this.fw.close(); } catch (Exception e) { e.printStackTrace(); } } }
In the MyRunnable class, we will query the Singleton class with 1000 random IP addresses. The results will be written to a file that is named for the thread ID.
The whole Main.java code in its entirety below:
import java.util.ArrayList; import java.util.List; import com.ip2proxy.*; import java.util.Random; import java.io.*; import java.io.FileWriter; public class Main { public Main() { } public static void main(String[] args) { List<Thread> threads = new ArrayList<Thread>(); // We will create 20 threads for (int i = 0; i < 20; i++) { Runnable task = new MyRunnable(i); Thread worker = new Thread(task); // We can set the name of the thread worker.setName(String.valueOf(i)); // Start the thread, never call method run() direct worker.start(); // Remember the thread for later usage threads.add(worker); } } } class MyRunnable implements Runnable { private final String binfile; private final int count = 1000; private final int tid; private String myip = ""; private ProxyResult all; private Random r = new Random(); private Singleton x; private FileWriter fw; private String filename = ""; MyRunnable(int threadid) { this.binfile = "C:\\mydata\\IP2PROXY-IP-PROXYTYPE-COUNTRY-REGION-CITY-ISP-DOMAIN-USAGETYPE-ASN-LASTSEEN-THREAT-RESIDENTIAL-PROVIDER.BIN"; this.tid = threadid; } @Override public void run() { try { this.filename = "file-" + this.tid + ".txt"; this.fw = new FileWriter(this.filename, false); for (int i = 1; i < count; i++) { this.myip = this.r.nextInt(256) + "." + this.r.nextInt(256) + "." + this.r.nextInt(256) + "." + this.r.nextInt(256); this.x = Singleton.Singleton(this.binfile); if (this.x.ip2proxy != null) { this.all = this.x.ip2proxy.GetAll(this.myip); this.fw.write("Thread ID: " + this.tid + "\n"); this.fw.write("IP: " + this.myip + "\n"); this.fw.write("Is_Proxy: " + String.valueOf(this.all.Is_Proxy) + "\n"); this.fw.write("Proxy_Type: " + this.all.Proxy_Type + "\n"); this.fw.write("Country_Short: " + this.all.Country_Short + "\n"); this.fw.write("Country_Long: " + this.all.Country_Long + "\n"); this.fw.write("Region: " + this.all.Region + "\n"); this.fw.write("City: " + this.all.City + "\n"); this.fw.write("ISP: " + this.all.ISP + "\n"); this.fw.write("Domain: " + this.all.Domain + "\n"); this.fw.write("Usage_Type: " + this.all.Usage_Type + "\n"); this.fw.write("ASN: " + this.all.ASN + "\n"); this.fw.write("AS: " + this.all.AS + "\n"); this.fw.write("Last_Seen: " + this.all.Last_Seen + "\n"); this.fw.write("Threat: " + this.all.Threat + "\n"); this.fw.write("Provider: " + this.all.Provider + "\n"); this.fw.write("===========================================================================\n"); } else { System.out.println("Could not open BIN file"); } } this.fw.close(); } catch (Exception e) { e.printStackTrace(); } } } class Singleton { // static variable single_instance of type Singleton private static Singleton single_instance = null; public static IP2Proxy ip2proxy = null; // private constructor restricted to this class itself private Singleton(String binfile) throws IOException { ip2proxy = new IP2Proxy(); if (ip2proxy.Open(binfile, IP2Proxy.IOModes.IP2PROXY_MEMORY_MAPPED) != 0) { ip2proxy = null; } } // static method to create instance of Singleton class public static Singleton Singleton(String binfile) throws IOException { // To ensure only one instance is created if (single_instance == null) { single_instance = new Singleton(binfile); } return single_instance; } }
Conclusion
This way of calling the IP2Proxy object is quite convoluted but depending on the developers’ codes, it may just be what they need to get the IP2Proxy proxy queries working in a heavy multi-threaded environment.