/**
* Utility function to determine if a four character expiration date is valid OR not!
*
* @param s
* <p>Date string in format MMyy. e.g., '0609' as date 'June 2009'.</p>
* @return <code>boolean</code> flag indicating if expiration date is valid or not.
*/
public static boolean isValidExpiration(String s) {
// use SimpleDateFormat to parse values like '0609' as date 'June 2009'
java.text.DateFormat sdf = new java.text.SimpleDateFormat("MMyy");
// establish current date as last day of previous month at 23:59:59
java.util.Calendar now = java.util.Calendar.getInstance();
now.set(now.get(java.util.Calendar.YEAR), now.get(java.util.Calendar.MONTH), 0, 23, 59, 59);
try {
// actual parsing of the date, wrapped in try/catch
// parses as 1st day of month at midnight since only providing month and year
// e.g., '0609' becomes 'June 01, 2009 00:00:00'
java.util.Date exp = sdf.parse(s);
// if parsed date is before current month, then return invalid!
if (exp.before(now.getTime())) return false;
} catch (java.text.ParseException e) {
return false; // if not MMYY then reject
}
// valid if made it this far, can do in reverse by using !exp.before and return true above.
return true;
}
Monday, July 27, 2009
Expiration Date Validation Example
Thursday, June 04, 2009
Tuesday, November 11, 2008
Java Custom Comparators: Sorting Text and Numbers Independently
String[] inputStringArray = { "1", "19", "2", "3pt1", "3", "5pt1", "6pt2", "7pt3", "7pt3", "10", "10x1", "15", "7", "A7", "B87", "10pt5" };
// The input is a String array. Make that an ArrayList:
java.util.List<String> inputList = new java.util.ArrayList<String>(java.util.Arrays.asList(inputStringArray));
Say we have the above input array of string values that we have converted to an ArrayList
public static java.util.List<String> sort(java.util.List<String> list) {
java.util.Collections.sort(list, new java.util.Comparator<String>() {
/**
* Custom compare to sort numbers as numbers.
* Strings as strings, with numbers ordered before strings.
*
* @param o1
* @param o2
* @return
*/
@Override
public int compare(String o1, String o2) {
boolean isFirstNumeric, isSecondNumeric;
isFirstNumeric = o1.matches("\\d+");
isSecondNumeric = o2.matches("\\d+");
if (isFirstNumeric) {
if (isSecondNumeric) {
return Integer.valueOf(o1).compareTo(Integer.valueOf(o2));
} else {
return -1; // numbers always smaller than letters
}
} else {
if (isSecondNumeric) {
return 1; // numbers always smaller than letters
} else {
isFirstNumeric = o1.split("[^0-9]")[0].matches("\\d+");
isSecondNumeric = o2.split("[^0-9]")[0].matches("\\d+");
if (isFirstNumeric) {
if (isSecondNumeric) {
int intCompare = Integer.valueOf(o1.split("[^0-9]")[0]).compareTo(Integer.valueOf(o2.split("[^0-9]")[0]));
if (intCompare == 0) {
return o1.compareToIgnoreCase(o2);
}
return intCompare;
} else {
return -1; // numbers always smaller than letters
}
} else {
if (isSecondNumeric) {
return 1; // numbers always smaller than letters
} else {
return o1.compareToIgnoreCase(o2);
}
}
}
}
}
});
return list;
}
The above does the following:
- Determines if the strings being compared are numeric values or not, using regular expression to match on \d which signifies a numeric digit.
- If both values are numeric, then we compare as integers using its already implemented compareTo function.
- If only one of the values is numeric, then it is the smallest as we want the numbers to sort first.
- If both are non-numeric, then we simply compare as strings. However, to show extended support for our third case of blended numbers and text, we go through same logic as above on the two string values: if both begin with numbers, then compare as integers with difference that equal numbers then need to sort by text; if one begins with a number, then it is smaller; if both are text, then just compare as strings.
Hopefully that is clear, but to test the results, you can use the following code:
java.util.List<String> sortedList = sort(inputList);
System.out.println("Numbers and text sorted!");
for (String s : sortedList) {
System.out.println(s);
}
Hope that helps.
Happy coding!
References:
Saturday, September 06, 2008
Range Searching Using Java
Background summary for those that haven't read the original article in VB, this started as research based on a question of finding the dates within a list that match a selected date range. The intent was to go beyond the simple iteration and match method and find a faster means through binary search only to find the VB FindAll and Predicate implementation eluded to above and slower performance from binary range functions created.
With that said, onward and upward!
The journey in Java begins with replicating our utility class Ranges that handles the simple match method utilized by looping through array as well as our binary search implementation based on a start and end of a range.
The code listing is below:
package com.blogspot.crossedlogic.rangesearching;
public class Ranges {
private static final int NOT_FOUND = -1;
/**
* Binary search through a sorted array to determine first index
* greater or equal to start of range.
*
* @param array
* @param rangePoint
* @return integer index or (-1) when not found
*/
public static <T extends Comparable<T>> int binaryStartOfRangeSearch(T[] array, T rangePoint) {
int low = 0;
int high = array.length - 1;
int mid;
int comparedToHigh = rangePoint.compareTo(array[high]);
if (comparedToHigh == 0) { return high; }
if (comparedToHigh < 0) {
if (rangePoint.compareTo(array[low]) <= 0) { return low; }
while (low != high) {
mid = (int)Math.floor((low + high) / 2);
if (rangePoint.compareTo(array[mid]) <= 0) {
if (rangePoint.compareTo(array[mid - 1]) <= 0) {
high = --mid;
} else {
return mid;
}
} else {
if (rangePoint.compareTo(array[mid + 1]) <= 0) {
return ++mid;
} else {
low = ++mid;
}
}
}
}
return NOT_FOUND;
}
/**
* Binary search through a sorted array to determine last index
* lesser or equal to end of range.
*
* @param array
* @param rangePoint
* @return integer index or (-1) when not found
*/
public static <T extends Comparable<T>> int binaryEndOfRangeSearch(T[] array, T rangePoint) {
int low = 0;
int high = array.length - 1;
int mid;
int comparedToLow = rangePoint.compareTo(array[low]);
if (comparedToLow == 0) { return low; }
if (comparedToLow > 0) {
if (rangePoint.compareTo(array[high]) >= 0) { return high; }
while (low != high) {
mid = (int)Math.ceil((low + high) / 2);
if (rangePoint.compareTo(array[mid]) >= 0) {
if (rangePoint.compareTo(array[mid + 1]) >= 0) {
low = ++mid;
} else {
return mid;
}
} else {
if (rangePoint.compareTo(array[mid - 1]) >= 0) {
return --mid;
} else {
high = --mid;
}
}
}
}
return NOT_FOUND;
}
/**
* Check if an object falls within a specified range.
*
* @param c
* @param rangeStart
* @param rangeEnd
* @return
*/
public static <T extends Comparable<T>> boolean matchRange(T c, T rangeStart, T rangeEnd) {
if (c.compareTo(rangeStart) >= 0 && c.compareTo(rangeEnd) < 1) { return true; }
return false;
}
}
As stated in my last post, the use of generics expanded my research beyond Date types and provided a framework for searching different types as long as their range principals were the same as implemented and they implemented the Comparable interface. For more information on Generics, please see the reference(s) below; however, the matchRange function reads like this for example: taking in any type T denoted by the parameter c which implements the Comparable interface (T extends Comparable
Comparable interface is used the same here as the compareTo function has the same usage and return values (at least in typical implementations of primitive/standard types): -1 when first object is smaller than the one being compared to; 0, the two equal; 1, first is larger.
As you will see, the usage of these functions are virtually the same from VB implementation:
// simple loop and match method
for (String s : arrayOfObjects) {
if (Ranges.matchRange(s, RANGE_START, RANGE_END)) {
lst.add(s);
}
}
// binary range search method
Arrays.sort(arrayOfObjects);
int idxStart = Ranges.binaryEndOfRangeSearch(
arrayOfObjects, RANGE_START);
int idxEnd = Ranges.binaryEndOfRangeSearch(
arrayOfObjects, RANGE_END);
if (idxStart >= 0 && idxEnd >= 0) {
for (int i = idxStart; i <= idxEnd; i++) {
lst.add(arrayOfObjects[i]);
}
}
What will be a little different is the FindAll implementation as this was done using some non-native interfaces similar to methodology discussed in the reference section from faqs{dot}com. To get the same functionality, an interface for Predicate had to be added. In addition, a Predicates class was created that implemented a FindAll equivalent that requires an implemented Predicate to return a matching List of objects. Then using Java's anonymous class syntax, we implement Predicate's required match function as we would with delegate.
/**
* Predicate interface used to emulate .NET <code>System.Predicate(Of T)</code>.
*/
package com.blogspot.crossedlogic.rangesearching;
public interface Predicate<T> {
/**
* @param object to test against predicate criteria.
* @return boolean flag indicating a match (true) or no match (false).
*/
public boolean isMatch(T obj);
}
/**
* Predicates class containing static implementation of FindAll.
*/
package com.blogspot.crossedlogic.rangesearching;
import java.util.ArrayList;
import java.util.List;
public class Predicates {
/**
* Replicate .NET findAll and <code>Predicate</code>.
*
* @param <T>
* @param array
* @param match implementation of the Predicate interface.
* @return
* @see com.blogspot.crossedlogic.rangesearching.Predicate
*/
public static <T> List<T> findAll(T[] array, Predicate<T> match) {
List<T> lst = new ArrayList<T>();
for (T obj : array) {
if (match.isMatch(obj)) { lst.add(obj); }
}
return lst;
}
}
// Example usage of FindAll for range searching.
lst.addAll(Predicates.findAll(arrayOfObjects,
new Predicate<String>() {
@Override
public boolean isMatch(String s) {
return Ranges.matchRange(s, RANGE_START, RANGE_END);
}
}));
Not one line, but it is just one code statement as the solution is to utilize the range add capabilities of the List interface couples with the List response of the findAll function using a Predicate which again we implement as a simple call to our static matchRange function coupled with constants for start and end of range.
Well, I have kept you waiting long enough, here is how the results stack up:
--------------------------------------------------
Start processing by going through each item
End processing: 37ms - 9311
--------------------------------------------------
Start processing by using FindAll.
End processing: 36ms - 9311
--------------------------------------------------
Start processing by binary range search.
End processing: 14ms - 9311
--------------------------------------------------
Start processing by going through each item
End processing: 1ms - 9311
--------------------------------------------------
Start processing by using FindAll.
End processing: 3ms - 9311
--------------------------------------------------
Start processing by binary range search.
End processing: 2ms - 9311
--------------------------------------------------
Start processing by going through each item
End processing: 1ms - 9312
--------------------------------------------------
Start processing by using FindAll.
End processing: 3ms - 9312
--------------------------------------------------
Start processing by binary range search.
End processing: 2ms - 9312
--------------------------------------------------
Start processing by going through each item
End processing: 1ms - 9313
--------------------------------------------------
Start processing by using FindAll.
End processing: 3ms - 9313
--------------------------------------------------
Start processing by binary range search.
End processing: 3ms - 9313
--------------------------------------------------
Start processing by going through each item
End processing: 2ms - 9314
--------------------------------------------------
Start processing by using FindAll.
End processing: 2ms - 9314
--------------------------------------------------
Start processing by binary range search.
End processing: 3ms - 9314
--------------------------------------------------
Aside from the first iteration where the static objects are created for the first time, you will see 1-3 millisecond (ms) response time from each of the three methodologies giving us two other tools in the toolkit for searching arrays/lists when dealing with large number of elements and ranges.
I am glad I took this journey on the VB.NET side as it inspired me to want to explore this in Java and I am very pleased with the results. Hopefully, it will be useful for you in your travels as well and thanks for reading. We covered a lot of different ground in Generics, Predicates, sorting Arrays, binary search algorithm, and so on so please use links for more information on things I didn't go into details on including previous post as I may have glossed over something I discussed further there.
Thanks and remember to keep learning and/or keep smiling; you never want to end up a sad fool!
Source code: Download com.blogspot.crossedlogic.rangesearching.zip
References:
- Range Searching Using Visual Basic .NET
- Generics in Java: http://java.sun.com/j2se/1.5.0/docs/guide/language/generics.html
- Predicate in Java: http://www.faqs.org/docs/javap/c12/ex-12-4-answer.html
- Java does have a native Predicate interface under the javax.sql.rowset package, but, since not same as .NET predicate in our context, custom Predicate was used above.
Friday, September 14, 2007
Free ColdFusion Server
Anyway, I just saw that New Atlanta released a new version of their ColdFusion server which is free. There are some restrictions on tag support that can be overcome by buying their full blown version with J2EE capabilities or Adobe's ColdFusion server.
Either choice, one good point is that ColdFusion and Java integrate real well to allow designers use to the web interface niceties of ColdFusion Markup Language (CFML) while leveraging business logic code written in Java.
For example you can create a normal Java class as follows:
package com.mydomain;
public class MyClass {
public int getInt(){ return 1; }
}
Then you can create and use the object in CF through a few tags:
<cfset mc = createObject("java","com.mydomain.MyClass") />
<cfset myint = mc.getInt() />
CFScript can be pretty useful in making this flow similar to syntax from Java itself, but using the CF tags aren't a bad way to go either especially if the point is separating duties between developers use to Java code and database design from web designers use to HTML style.
Happy coding!
Saturday, September 01, 2007
Migrating to J# From Java
Microsoft's J# is like Cappuccino, it is pretty much Java with some extra frosting on top.
Microsoft's J#, syntactically, is very much the same as Java; therefore, when this adventure to migrate the availability code to .NET presented itself, my first thought was "convert to J#." Unfortunately, with the features we explored previously being functionality of JDK level 5.0, J# could not actual accomplish this task.
According to Microsoft, J#'s run-time libraries will remain frozen at the JDK 1.1.4 level; there are no plans to emulate later versions of the JDK. If J# catches on, it's likely that users will fill in the gaps over time. Microsoft considers JDK compatibility largely irrelevant. The current level supports Visual J++ projects, and whatever is missing from the JDK compatibility libraries is covered by the .Net framework.
J#'s interface to the .Net framework is solid, but not as seamless as C#. In particular, J# code cannot define new .Net properties, events, value types, or delegates. J# can make use of these language constructs if they are defined in an assembly written in another language, but its inability to define new ones limits J#'s reach and interoperability compared to other .Net languages.
-Tom Yager, Infoworld
Presumably due to legality issues restricting Microsoft from copying Java bytecode for bytecode, coupled with strategic desire to push developers towards using core .NET functionality to accomplish the tasks done in later Java versions, the J# support for newer JDKs will probably not be updated to cover the holes in it current capabilities.
With that said, for my case study, I have come to the realization that code must be re-written in .NET and it will be in Visual Basic. C# is probably the more powerful than Visual Basic .NET; however, the target business system being written in Visual Basic makes it the more desirable for this project.
Don't get me wrong, though, there is a good deal of power in being able to compile java directly into .NET using J#, but just be aware of the limitations. It took me a while to stop seeing 1.1.4 and thinking 1.4, so for the application I was converting there was too much of a gap.
For you developers who have Java applications that would be hard to re-write, J# .NET may fit the need, especially when using tools such as IKVM or JBImp that allow conversion of Java bytecode to Microsoft intermediate language (MSIL). However, this may also be a good argument for using C#, which syntactically is close to that of Java as C/C++ is. After converting Java classes or archive (JAR) files to .NET libraries, migration of application to C# becomes easier.
Thursday, August 30, 2007
Jump In The Pool, The Threads Are Fine
10,000 soldiers may be more powerful than one, but, if they march in a straight line towards enemy fire, they are no more effective.
Threads are the same. Marching them in regiments versus a line adds power and efficiency at the same time.
Thread Pooling
In Java 5.0 (JRE 1.5), the java.util.concurrent package was made available which provided API for concurrent processing/efficient thread pooling. For some good details on the API, please refer to Java/Sun publications on JSR 166. The Distributed Computing Laboratory at Emory University has further documentation as well as a back-ported version of the package for use with older versions of JVM.
To implement the concurrent/thread pool model to the ArrayOfThreads object we created in previous post, the ExecutorService is utilized to invoke a Callable object (introduced in the concurrent package). AThread happens to be a Callable object versus a simple sub-class of Thread object which just has a run() method without a return. Utilized the Callable even in the earlier examples as the end goal is to be able to gather data back from the execution of this process.
Consequently, after submission of threads for processing, the framework for concurrency provides a mechanism to retrieve returned data per thread using the Future object. The major difference this makes in the implementation is that the Future handles the waiting and gathering of thread data, so the main application thread spawning the new processes does not have to wait until each thread completes before moving on.
...
/* code snippet from ArrayOfThreadsPooled.java
* @see java.util.concurrent.Executors
* @see java.util.concurrent.ExecutorService
* @see java.util.concurrent.Future
*/
int NUM_THRDS = 100, NUM_THRDS_POOLED = 100;
ExecutorService tpes = Executors.newFixedThreadPool(NUM_THRDS_POOLED);
Future futures[] = new Future[NUM_THRDS];
AThread calculators[] = new AThread[NUM_THRDS];
Object status[] = new Object[NUM_THRDS];
...
Later on using a for loop to instantiate the threads, we submit each to pool (where i represents the integer index used in loop).
calculators[i] = new AThread(i);
futures[i] = tpes.submit(calculators[i]);
On the first implementation of ArrayOfThreadsPooled we see that the process impracticality of running multiple threads has been removed as the process time is about a hundredth of the time since all 100 threads execute simultaneously (see output example 1).
Don't Be A Resource Hog
However, the problem of scaling is still an issue as your business systems involved in processing and application server itself will probably not appreciate the execution of 10,000 threads. To remedy this per user, the ArrayOfThreadsPooledv2 is enhanced by illustrating the fact that the fixed thread pool does not need to have the same size as the required amount of Callables, Futures, et al. Therefore, the NUM_THRDS_POOLED value can be changed to a lower or higher number than 100.
For example I found that my application worked with a total number of 15 processors running at a time (see output example 2). To better understand the benefit, the AThread class provided includes the ability to provide variable processing times. With variance in execution times, the thread pool with stop and start threads appropriately thus adding even more efficiencies over executing x number of threads at a time with identical run times (see output example 3).
Back 2 The Future
Through the Future get() method wrapped with exception handling, we can retrieve the returned object/data from the Callable later on.
Since the Futures involved take care of getting data, as a developer you get the benefit of all the threads starting at once, but having a method of identifying which response goes to which request. With this information, you can then sort, analyze, manipulate or otherwise report the data however you see fit.
Some further enhancements to this would be o find the optimum amount of threads needed to support all requests from your client base and instantiate the ExecutorService as one static version defined globally for the application.
public static int NUM_THRDS_CONCURRENT = 60;
public static ExecutorService tpes =
Executors.newFixedThreadPool(NUM_THRDS_CONCURRENT);
Each instance of ArrayOfThreadsPooledv2 that were executed would, therefore, submit its number of Callable objects necessary into the shared pool.
futures[i] = ArrayOfThreadsPooledv2.tpes.submit(calculators[i]);
If no other users were on, the response may be as quick as first iteration of ArrayOfThreadsPooled while providing the stability and respect of resources provided by second implementation.
With the source code and detail to this point, I hope those of you on the journey towards creating a multi-threaded application in Java have found enough useful information to utilize the full power of Java's great addition. Keep in mind that further enhancements exist in JRE 1.6.
Keep evolving development!
10,000 Threads Are Better Than One
[basic availability service] an application that communicated with business systems (enterprise resource planning - inventory management in particular) to read current stock information, including current demand allocations; compare to new single demand; ultimately, displaying lead time to fulfill customer requirement whether from stock, assembling from stocked components on a bill of material, or purchasing/fabricating item through the portions if not all of its cumulative lead time...
Since the business world does not exist in a parallel universe where customer demand and actual supply are always in constant harmony, the basic implementation for availability above is insufficient as a final answer to a demand requirement that has differing priorities (e.i. fulfill two or more needs at once). To clarify, please re-read the following excerpt:
[extended availability service] As a customer, you may have an immediate need for five light bulbs or else you will be in darkness for the entire day, but you get cost efficiencies purchasing light bulbs in quantities of 1,000. The purpose of this extension to the availability service is to allow you as the customer understand that you can get 50 from stock today...
The first need of concern is to restore service: get the lights on! The second priority is to ensure that the effort to restore service is done in a fashion that positively impacts the bottom-line long term as well as lessen the likelihood of future outage. At its most basic sense, the response to the above variation in need could involve two calls to availability code: one returning lead time of quantity acquirable through stock (e.g. same day); the other, time through procurement/manufacturing processes (e.g. 60 days).
However, if you add usage and more complex items an scenarios to your thought process over my simple light bulb shortage, the application requirement slowly becomes (or at least it did in my case) one that asks the question "what is the individual lead time of each quantity of the total 1,000 light bulbs requested?"
An Array of Threads
Following the thought above, to answer the question, a user of the availability tool would run sequential requests with required quantities from 1 - 1,000. The first step is to change the process of making the requests an automated one versus having to make 1,000 separate requests. The user would like to query the system once and get 1,000 responses. With the potential for thousands or tens of thousands, the first logical conclusion I got was "10,000 threads are better than one."
The example thread AThread does a simple wait(x milliseconds) to simulate some processing time, but can easily be modified to be a synchronized (depending on locking requirements) block of code executing logic necessary to determine a specific item and required quantity availability. For example:
...
/* call() method for AThread.java
* @see java.util.concurrent.Callable#call()
*/
public Object call() throws Exception {
// Define your own result object and availability processor
LeadTimeResultObject ltro = new LeadTimeResultObject();
AvailabilityProcessor process = new AvailabilityProcessor();
// Use synchronized blocks for thread safety
synchronized(this){
// Insert application logic to get lead time
ltro = process.getLeadTimeResult("some item id", "some quantity");
}
return ltro;
}
...
The ArrayOfThreads class illustrates how to implement calling the lead time availability code through use of an AThread array.
AThread calculators[] = new AThread[100];
Using a for loop to iterate from 0 to 99 index of array, you can use the Constructor of the AThread object to pass in data needed to make each thread calculation unique (e.g. item id and quantity equal to 1 to 100 - array index + 1). Although this accomplishes the first task of automating the requests, each thread must complete serially making the net result of the application performance exactly that of having the main thread executing one availability process 100 times (see output example 1: processing times go in sequence and so total processing time is long).
In addition, this will most likely crash your application and/or back-end systems as user quantity requirements scale upward.
I know this is not very practical for the use case we are exploring, but another component of the application I wrote was used to keep statistics on requests for availability. These statistics were needed for later analysis and did not need to be sent back to user. Applying the methodology of thread arrays for background processing that needs to be done at some point is a perfect fit. User leaves the site or at least is satisfied by a response, while additional business logic is applied as more data is gathered and stored in a business intelligence/reporting back-end.
Thread pooling or utilizing all the threads simultaneously would answer both the automation of user process and most efficient run time concerns. See next post in the concurrent threading series for a detail on the ArrayOfThreadsPooled object included in the source code available for download above.
The Adventure Begins (Concurrent Threading Series)
Background
A practical or real life application for this would be an application that communicated with business systems (enterprise resource planning - inventory management in particular) to read current stock information, including current demand allocations; compare to new single demand; ultimately, displaying lead time to fulfill customer requirement whether from stock, assembling from stocked components on a bill of material, or purchasing/fabricating item through the portions if not all of its cumulative lead time.
Furthermore, the delivery method on this application to client was via the web, so a few seconds processing time is a long wait to many, so response time must be considered in any alterations.
Now consider a secondary request interface for this application that needs to determine separate responses for the same demand above. This was used by Customer Service/Sales Representatives who need to know the breakdown of lead time(s) from satisfying quantity of one to the total requested. As a customer, you may have an immediate need for five light bulbs or else you will be in darkness for the entire day, but you get cost efficiencies purchasing light bulbs in quantities of 1000. The purpose of this extension to the availability service is to allow you as the customer understand that you can get 50 from stock today, 250 built within a week, and 700 available two months from now. This is much more useful to satisfying both business needs than getting one response that indicates to get a complete order 1000 would be two months.
The Problem
Several months ago, during a migration from one business enabling system to another, I was faced with a need to architect this concurrent programming accomplished in Java as a .NET application. My initial thought was to leverage and reuse as much of the Java code I had already written to lessen the development cycle for this change and to ensure that the logic was implemented as close to original as possible then improve from there once integration was established. What did this mean? Microsoft J# to the rescue! Or at least so I hoped.
Microsoft’s J# does not support Java 5.0 as it is based on previous JRE version. This road blocked the effort immensely as it was not clear to me if Microsoft .NET even had the capability to perform the same concurrency programming and/or how, and so began the adventure to acquire knowledge.
The Adventure
The following set of posts try to go through thought process behind the multi-threading being done in the original Java implementation and hopefully arrive at the most efficient means of transforming to a .NET implementation. Whether or not my journey to converting from Java to .NET is successful, I figured this information may be helpful for new (or at least new to threading) developers that are looking for methodologies to accomplish what I have done in either language.
So buckle up and let’s explore together the migration of concurrent processing code from Java technology to J# and .NET in general.