Passing Parameters to Threads while running [duplicate] - java

This question already has answers here:
How to pass parameter to an already running thread in java?
(4 answers)
Closed 3 years ago.
I want to implement Circular Buffer using 2 threads. One Thread that reads from the buffer and the other one writes to the buffer as follow:
Assuming that process P is the writer process and process Q is the reader process.
After starting both processes from the main class, how can I pass to process P (the writer) the value I want it to write to the buffer each time I want to write to the buffer? Similarly, how can I ask process Q (the reader) to read from the buffer (i.e. how can I call it to return the value that it has read from the buffer)?
I am confused because the implementation of both processes is done in the run() method and this method is executed when we issue the .start() command. However, once started, how can we keep passing and reading parameters while it is in the run mode.
I am following the implementation in the following example mentioned in the following question :
Circular Buffer with Threads Consumer and Producer: it get stucks some executions
Shared Variables:
public class BufferCircular {
volatile int[] array;
volatile int p;
volatile int c;
volatile int nElem;
public BufferCircular(int[] array) {
this.array = array;
this.p = 0;
this.c = 0;
this.nElem = 0;
}
public void writeData (int data) {
this.array[p] = data;
this.p = (p + 1) % array.length;
this.nElem++;
}
public int readData() {
int data = array[c];
this.c = (c + 1) % array.length;
this.nElem--;
return data;
}
}
Writer Process:
public class Producer extends Thread {
BufferCircular buffer;
int bufferTam;
int contData;
public Productor(BufferCircular buff) {
this.buffer = buff;
this.bufferTam = buffer.array.length;
this.contData = 0;
}
public void produceData() {
this.contData++;
this.buffer.writeData(contData);
}
public void run() {
for (int i = 0; i < 500; i++) {
while (this.buffer.nElem == this.bufferTam) {
Thread.yield();
}
this.produceData();
}
}
}
Reader Process:
public class Consumer extends Thread {
BufferCircular buffer;
int cont;
public Consumer(BufferCircular buff) {
this.buffer = buff;
this.cont = 0;
}
public void consumeData() {
int data = buffer.readData();
cont++;
System.out.println("data " + cont + ": " + data);
}
public void run() {
for (int i = 0; i < 500; i++) {
while (this.buffer.nElem == 0) {
Thread.yield();
}
this.consumeData();
}
}
}
Main:
public class Main {
public static void main(String[] args) {
Random ran = new Random();
int tamArray = ran.nextInt(21) + 1;
int[] array = new int[tamArray];
BufferCircular buffer = new BufferCircular(array);
Producer producer = new Producer (buffer);
Consumer consumer = new Consumer (buffer);
producer.start();
consumer.start();
try {
producer.join();
consumer.join();
} catch (InterruptedException e) {
System.err.println("Error with Threads");
e.printStackTrace();
}
}
}

You main thread should not be responsible for passing value to producer thread and in same way it should not be responsible to print the data from consumer.
Producer: this should be responsible to getting the data and inserting into your queue, in your case its currently increment int value and passing it, but you can also read data from file or db or use std input to take user input and pass that data in the queue.
Consumer: this should be responsible to process the data from the queue in ur case printing the numbers.
Check online

Related

How can I assign a value to the threadArray variable in Task1 , Task2, Task3 class run() method?

I have to insert the elements using three threads by creating three classes, namely Task1,Task2 and Task3. The values to be inserted into the array are 0,1,2,....299.
Override the run method in the threads. Three integer i,j, and k representing the number of elements each thread should append inside the given array.
Thread one should append 0 to i-1 inside the array,thread two should append i to i+j-1 inside the array,and the third thread should append i+j to 299 inide the array.
Threads one and two must run simultaneously, and the values of the threads one and two must be inserted inside the indices of the array from 0 to i+j-1 randomly.The third thread should start only after the first two threads have been executed completely.
In these code three task are given.
first task and second task start executing the thread at the same time and after completion of first two task then only third task start. If these situation getting correct then test() method return true.
public static final int[] threadArray = new int[300]; how I add random number into these array using Task1 Task2 and Task3 class.
Input :
80
130
90
Output :
True
import java.util.Scanner;
class Task1 extends Thread
{
static int a = 0;
static int beg = 0;
public void run()
{
for(int i=a;i<=beg;i++)
{
Solution.threadArray[i] = i;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Task2 extends Thread
{
static int a = 0;
static int beg = 0;
#Override
public void run()
{
// TODO Auto-generated method stub
for(int i=a;i<=beg;i++)
{
Solution.threadArray[i] = i;
}
}
}
class Task3 extends Thread
{
static int a = 0;
static int beg = 0;
public void run()
{
// TODO Auto-generated method stub
for(int i=a;i<=beg;i++)
{
Solution.threadArray[i] = i;
}
}
}
public class Solution
{
public static final int[] threadArray = new int[300];
public static volatile String i = 0+"";
public boolean test() throws InterruptedException
{
Task1 task1 = new Task1();
Task2 task2 = new Task2();
Task3 task3 = new Task3();
Thread task2Thread = new Thread(task2);
Thread task3Thread = new Thread(task3);
task1.start();
task2Thread.start();
task1.join();
task2Thread.join();
task3Thread.start();
int first = Task1.a+Task2.a;
int containsSecondThread = Task1.a;
String oneAndTwo = "";
String sizeOfTask1 = "";
for(int i=0;i<first;i++)
{
oneAndTwo += threadArray[i]+" ";
}
for(int i=0;i<containsSecondThread;i++)
{
sizeOfTask1 += threadArray[i]+" ";
}
int begOfTask3 = Task3.beg;
String checkingString = "";
for(int i=begOfTask3;i<threadArray.length;i++)
{
checkingString += i + " ";
}
String task3String = "";
for(int j = begOfTask3;j<threadArray.length;j++)
{
task3String += threadArray[j]+" ";
}
if((!oneAndTwo.contains(begOfTask3+"") && sizeOfTask1.contains(Task2.beg+"")) || task3String.equals(checkingString))
{
return true;
}
return false;
}
public static void main(String[] args) throws InterruptedException
{
Scanner sc= new Scanner(System.in);
Solution solution = new Solution();
int one = sc.nextInt();
Task1.a = one;
Task1.beg = 0;
int two = sc.nextInt();
Task2.a = two;
Task2.beg = one;
int three = sc.nextInt();
Task3.a = three;
Task3.beg = one+two;
System.out.print(solution.test());
}
}
First, some observations regarding your code: Instead of using static variables in the classes (i.e., Task1, Task2, and Task3) that extend the class Thread (to understand why have a look at Why are static variables considered evil?):
static int a = 0;
static int beg = 0;
use non-static final fields, and initialize them via the constructor:
class Task1 extends Thread
{
private final int begin;
private final int end;
Task1(int begin, int end){
this.begin = begin;
this.end = end;
}
public void run(){
for(int i=begin; i<= end; i++)
....
}
}
adapt the main method accordingly:
public static void main(String[] args){
...
Task1 task1 = new Task1(begin, end);
}
and then pass the tasks-related objects as parameters of to the test method:
public boolean test(Task1 task1, Task2 task2, Task3 task3){
...
}
For the concatenation of the strings use StringBuilder:
StringBuilder oneAndTwo = new StringBuilder();
for(int i=0;i<first;i++)
{
oneAndTwo.append(threadArray[i]).append(" ");
}
This looks wrong:
Task1.a = one;
Task1.beg = 0;
by looking at the loop of the run method from Task1, this means that, if Task1.a is not a negative number, then Task1 will not do any work.
To use the threads to generate the random values of the array:
int[] threadArray = new int[300];
you can start by extracting a method to generate those random values, based on formula:
r.nextInt(high-low) + low;
this formula generates a random value between low and high.
Adapt the tasks, accordingly:
class Task1 extends Thread
{
private final Random random_values = new Random();
private final int low;
private final int high;
...
public int generate_random(){
return r.nextInt(high-low) + low;
}
public void run()
{
for(....)
{
Solution.threadArray[i] = generate_random();
...
}
}
}
Make sure to pass to the threads the information about the range of the random values to be generated (i.e., the low and high parameters), and the reference to the array that will be filled up with those random values (i.e., array int[] threadArray) . Also make sure that you split the iterations int[] threadArray among the threads. Therefore, each thread should generate a chunk of the random values. An example of such distribution would be:
Thread 1 : 0 to 100;
Thread 2 : 100 to 200;
Thread 3 : 200 to 300;
You can make this more robust and divide the array length by the number to threads and assign the work among threads, accordingly.
I could have provided you with the entire solution, but I feel that is better instead if I give you the pointers so that you can do it in your own.
EDIT: Based on the new edit of your question:
You just need to adapt the Task classes as follows:
class Task1 extends Thread {
static int a = 0;
static int beg = 0;
public void run(){
for(int i=beg;i < a;i++)
Solution.threadArray[i] = i;
}
}
class Task2 extends Thread {
static int a = 0;
static int beg = 0;
public void run(){
for(int i=beg; i< beg + a;i++)
Solution.threadArray[i] = i;
}
}
class Task3 extends Thread{
static int a = 0;
static int beg = 0;
public void run(){
for(int i=beg;i< a + beg;i++)
Solution.threadArray[i] = i;
}
}
Thread1 and Thread2 are supposed to access Common Resource in threadArray[0... Task1.a+Task2+a]. So we have to make use of static volatile variable i declared in Solution Class.
class Task1 extends Thread
{
static int a=0,beg=0;
public void run()
{
int k=Task1.beg;
int i1=0;
while(i1<Task1.a)
{
Solution.threadArray[Integer.parseInt(Solution.i)]=k++;
int a1=Integer.parseInt(Solution.i);
a1++;i1++;
Solution.i=a1+"";
try{
Thread.sleep(1);
}
catch(InterruptedException e){}
}
}
}
class Task2 extends Thread
{
static int a=0,beg=0;
public void run()
{
int y=0;
int k=Task2.beg;
while(y<Task2.a)
{
Solution.threadArray[Integer.parseInt(Solution.i)]=k++;
int a1=Integer.parseInt(Solution.i);
a1++;y++;
Solution.i=a1+"";
try{
Thread.sleep(1);
}
catch(InterruptedException e){}
}
}
}
Thread3 work independently after First 2 threads complete.
class Task3 extends Thread
{
static int beg=0,a=0;
public void run()
{
for(int i=Task3.beg;i<Task3.beg+Task3.a;i++)
{
Solution.threadArray[i]=i;
}
}
}

Erastotenes Sieve in paraller programm Java

I would like to make a programm which count prime numbers using Erastotenes Sieve. In this issue I want to use semaphore to communicate between thread to make calculations on table with numbers.
So far I have written code like that.
public static void main( String[] args ) throws InterruptedException {
System.out.println("Podaj gorny zakres\n");
Scanner scanner = new Scanner(System.in);
Erastotenes erastotenes = new Erastotenes(Integer.parseInt(scanner.nextLine()));
erastotenes.initializeTable();
long start = System.nanoTime();
List<SingleProcess.MyThread> list = new ArrayList<>();
List<Integer> numbers = Dollar.$(2,erastotenes.getMaximumNumber()+1).toList();
for(int i=0;i<2;i++)
{
list.add(new SingleProcess.MyThread(erastotenes,numbers.subList((numbers.size()/2)*i,(numbers.size()/2)*i+numbers.size()/2)));
list.get(list.size()-1).start();
list.get(list.size()-1).join();
}
System.out.println(System.nanoTime() - start);
//System.out.println("Liczba elementów: "+erastotenes.countPrimeElements());
}
Erastotenes class.
public class Erastotenes {
private int upperRange;
private int maximumNumber;
private int table[];
public Erastotenes(int upperRange) {
this.upperRange = upperRange;
this.maximumNumber = (int)(Math.floor(Math.sqrt(upperRange)));
this.table = new int[upperRange+1];
}
public int getMaximumNumber() {
return maximumNumber;
}
public int getUpperRange() {
return upperRange;
}
public void initializeTable()
{
for(int i=1;i<=upperRange;i++) {
table[i] = i;
}
}
public void makeSelectionOfGivenNumber(int number)
{
if (table[number] != 0) {
int multiple;
multiple = number+number;
while (multiple<=upperRange) {
table[multiple] = 0;
multiple += number;
}
}
}
public List<Integer> getList()
{
List<Integer> list = Ints.asList(table);
return list.stream().filter(item->item.intValue()!=0 && item.intValue()!=1).collect(Collectors.toList());
}
}
The class describing single Thread to make calculations with static Semaphore looks like this.
public class SingleProcess {
static Semaphore semaphore = new Semaphore(1);
static class MyThread extends Thread {
Erastotenes erastotenes;
List<Integer> numbers;
MyThread(Erastotenes erastotenes,List<Integer> numbers) {
this.erastotenes = erastotenes;
this.numbers=numbers;
}
public void run() {
for(int number:numbers) {
try {
semaphore.acquire();
//1System.out.println(number + " : got the permit!");
erastotenes.makeSelectionOfGivenNumber(number);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
}
}
}
I thought that splitting on half table with numbers from 2 to maximum numbers as square root like in erastotrenes algorithm for these two Threads will boost calculations, but with upperRange to 100000000 the difference between paraller and sequence is not so big. How can I in another realize this problem of paraller programming Erastotenes Sieve?
I think your main problem is this:
for(int i=0;i<2;i++)
{
list.add(new SingleProcess.MyThread(erastotenes,numbers.subList((numbers.size()/2)*i,(numbers.size()/2)*i+numbers.size()/2)));
list.get(list.size()-1).start();
list.get(list.size()-1).join();
}
You start a thread and then immediately wait for it to finish; that kills the parallelism entirely. You can start and wait in the end:
for(int i=0;i<2;i++)
{
list.add(new SingleProcess.MyThread(erastotenes,numbers.subList((numbers.size()/2)*i,(numbers.size()/2)*i+numbers.size()/2)));
list.get(list.size()-1).start();
}
for (Thread t : list) {
t.join();
}
But, there's also a problem with your semaphore tbh. Each thread blocks all other threads from doing anything as long as it's working on a number; that means that again, all parallelism is gone.
You can do away with the semaphore altogether IMO; there's not really a lot of danger in setting the same index to 0 several times, which is all that happens in this "critical section" - but it's not critical at all because no one ever reads the array value in question before all threads are finished.

Circular Buffer with Threads Consumer and Producer: it get stucks some executions

I'm developing a circular buffer with two Threads: Consumer and Producer.
I'm using active waiting with Thread.yield.
I know that it is possible to do that with semaphores, but I wanted the buffer without semaphores.
Both have a shared variable: bufferCircular.
While the buffer is not full of useful information, producer write data in the position pof array, and while there are some useful information consumer read data in the position c of array. The variable nElem from BufferCircular is the number of value datas that haven't been read yet.
The program works quite good 9/10 times that runs. Then, sometimes, it get stucks in a infinite loop before show the last element on screen (number 500 of loop for), or just dont' show any element.
I think is probably a liveLock, but I can't find the mistake.
Shared Variable:
public class BufferCircular {
volatile int[] array;
volatile int p;
volatile int c;
volatile int nElem;
public BufferCircular(int[] array) {
this.array = array;
this.p = 0;
this.c = 0;
this.nElem = 0;
}
public void writeData (int data) {
this.array[p] = data;
this.p = (p + 1) % array.length;
this.nElem++;
}
public int readData() {
int data = array[c];
this.c = (c + 1) % array.length;
this.nElem--;
return data;
}
}
Producer Thread:
public class Producer extends Thread {
BufferCircular buffer;
int bufferTam;
int contData;
public Productor(BufferCircular buff) {
this.buffer = buff;
this.bufferTam = buffer.array.length;
this.contData = 0;
}
public void produceData() {
this.contData++;
this.buffer.writeData(contData);
}
public void run() {
for (int i = 0; i < 500; i++) {
while (this.buffer.nElem == this.bufferTam) {
Thread.yield();
}
this.produceData();
}
}
}
Consumer Thread:
public class Consumer extends Thread {
BufferCircular buffer;
int cont;
public Consumer(BufferCircular buff) {
this.buffer = buff;
this.cont = 0;
}
public void consumeData() {
int data = buffer.readData();
cont++;
System.out.println("data " + cont + ": " + data);
}
public void run() {
for (int i = 0; i < 500; i++) {
while (this.buffer.nElem == 0) {
Thread.yield();
}
this.consumeData();
}
}
}
Main:
public class Main {
public static void main(String[] args) {
Random ran = new Random();
int tamArray = ran.nextInt(21) + 1;
int[] array = new int[tamArray];
BufferCircular buffer = new BufferCircular(array);
Producer producer = new Producer (buffer);
Consumer consumer = new Consumer (buffer);
producer.start();
consumer.start();
try {
producer.join();
consumer.join();
} catch (InterruptedException e) {
System.err.println("Error with Threads");
e.printStackTrace();
}
}
}
Any help will be welcome.
Your problem here is that your BufferCircular methods are sensitive to race conditions. Take for example writeData(). It executes in 3 steps, some of which are also not atomic:
this.array[p] = data; // 1
this.p = (p + 1) % array.length; // 2 not atomic
this.nElem++; // 3 not atomic
Suppose that 2 threads entered writeData() at the same time. At step 1, they both have the same p value, and both rewrite array[p] value. Now, array[p] is rewritten twice and data that first thread had to write, is lost, because second thread wrote to the same index after. Then they execute step 2--and result is unpredictable since p can be incremented by 1 or 2 (p = (p + 1) % array.length consists of 3 operations, where threads can interact). Then, step 3. ++ operator is also not atomic: it uses 2 operations behind the scenes. So nElem becomes also incremented by 1 or 2.
So we have fully unpredictable result. Which leads to poor execution of your program.
The simplest solution is to make readData() and writeData() methods serialized. For this, declare them synchronized:
public synchronized void writeData (int data) { //...
public synchronized void readData () { //...
If you have only one producer and one consumer threads, race conditions may occur on operations involving nElem. Solution is to use AtomicInteger instead of int:
final AtomicInteger nElem = new AtomicInteger();
and use its incrementAndGet() and decrementAndGet() methods.

Byte[] input to Java Scanner

I'm writing a plugin for a Java application. External software makes a TCP connection to this application, and sends messages as to my plugin as UTF-8 encoded JSON objects. Each message is separated by a delimiter. I'm currently using "\u00A1" (¡) for a delimiter.
{ "message": "value" }¡{ "message": "value" }¡{ "message": "value" }...
Since TCP doesn't provide any guarantees about how much data will arrive at a time, the plugin has to be receiving this stream of data and pull out each individual { "message": "value" } token. Sounds like a great use of java.util.Scanner..
The problem is, the application doesn't provide my plugin direct access to the TCP socket. The plugin receives data as repeated calls to its receiveData(byte[] bytes) method. I need some sort of input stream or channel that Scanner can read from, but that I can also deposit bytes to (from receiveData). Does such a thing exist? If not, any suggestions for implementing one? Or am I way off and is there a better way to approach this?
Note: I originally tried to implement this logic manually. I would take each chunk of received bytes, decode to a string, search for the delimiter, and append to a StringBuilder. Then I realized that this approach isn't valid because the incoming byte[] probably won't end on an even UTF-8 character boundary and would not decode properly. I feel like Scanner is exactly what I want, I just can't figure out how to provide it input.
Edit: The data is streamed continuously to update the display as the application is running. It is not possible to wait until there is no more data to begin parsing.
I ended up creating a circular buffer with a condition variable that implements ReadableByteChannel. Seems to work well enough. Here's a full example:
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Scanner;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Buffer implements ReadableByteChannel {
private final Lock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private int readIndex = 0;
private int writeIndex = 0;
private int size = 0;
private final int capacity;
private final byte[] buff;
public Buffer(int capacity) {
this.capacity = capacity;
this.buff = new byte[capacity];
}
/**
* Deposit bytes to the buffer. Will only write until
* buffer is full.
* #param bytes the bytes to add
* #return the number of bytes actually added
*/
public int addBytes(byte[] bytes) {
lock.lock();
int writeCount = 0;
try {
int available = capacity - size;
writeCount = available <= bytes.length ? available : bytes.length;
for (int i = 0; i < writeCount; ++i) {
buff[writeIndex] = bytes[i];
incrementWrite();
++size;
}
// notify callers waiting on read()
notEmpty.signal();
} finally {
lock.unlock();
}
return writeCount;
}
public int addBytes(byte[] bytes, int offset, int length) {
lock.lock();
int writeCount = 0;
try {
int available = capacity - size;
writeCount = available <= length ? available : length;
for (int i = 0; i <writeCount; ++i) {
buff[writeIndex] = bytes[offset + i];
incrementWrite();
++size;
}
notEmpty.signal();
} finally {
lock.unlock();
}
return writeCount;
}
#Override
public int read(ByteBuffer byteBuffer) throws IOException {
lock.lock();
try {
// if the current size is 0, wait until data is added
while (size == 0) {
notEmpty.wait();
}
int attempt = byteBuffer.remaining();
int readCount = attempt <= size ? attempt : size;
for (int i = 0; i < readCount; ++i) {
byteBuffer.put(buff[readIndex]);
incrementRead();
--size;
}
return readCount;
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return 0;
} finally {
lock.unlock();
}
}
#Override
public boolean isOpen() {
return true;
}
#Override
public void close() throws IOException {
// do nothing
}
private final void incrementRead() {
// increment and wrap around if necessary
if (++readIndex == capacity) {
readIndex = 0;
}
}
private final void incrementWrite() {
// increment and wrap around if necessary
if (++writeIndex == capacity) {
writeIndex = 0;
}
}
public static void main(String[] args) {
final Buffer buff = new Buffer(1024);
final Scanner scanner = new Scanner(buff).useDelimiter("!");
Thread readThread = new Thread(new Runnable() {
#Override
public void run() {
while (scanner.hasNext()) {
String message = scanner.next();
System.out.println(message);
if (message.equals("goodbye")) {
return;
}
}
}
});
readThread.start();
buff.addBytes("hello,".getBytes());
buff.addBytes(" world!".getBytes());
buff.addBytes("good".getBytes());
buff.addBytes("bye!".getBytes());
}
}

How to asynchronously modify an array of Strings

I thought this was an interesting programming problem so I posted it even though I think I have a solution idea that is good enough, see (*). If anyone has an elegant solution I would love to see it!
I am working with a method that calls external library that does http requests to a server. I need to have K strings as input to be effective i.e. each invocation of the external resource is a HTTP request and I need to buffer up some data for effectiveness. (As an example let K be 200 and occurs as a token in a text with say 1% probability so I would need to process 20,000 tokens before finding the 200 input arguments).
Effectively what this does is: externalHTTP(commaDelimitedString) -> get info about each string. Example externalHTTP("foo,bar") -> ["information snippet 1","information snippet 2"]. Where "information snippet 1" is about "foo".
I want to replace the "foo" and "bar" in a long text (string) with the information snippets but only after my buffer for the HTTP request is full. I still want to continue reading the original string while waiting for this to happen.
The text is tokenized by splitting (so I am working with an array of strings).
I.e. I would not like to stop execution of my text processing just because I am waiting for K strings to buffer up.
At first I thought that I could store words as individual string objects that I later update but then I realized that strings are immutable so it is call by value.
(*) My second idea was to store indices of the words (foo and bar) and then in order insert the snippets back into the original string array when the http request is finished. Like
class doStuff {
String text[];
LinkedList<Integer> idxList = new LinkedList<Integer>();
public doStuff(String[] t) {
text = t;
int i = 0;
for (String token : text) {
if (shouldReplaceToken(token)) {
replaceToken(i);
}
i++;
//do other work with the tokens
}
}
void replaceToken(int i) {
idxList.add(i);
if (++count > buffSize) {
count = 0;
String commaDelim = "";
ListIterator<Integer> it = idxList.getListIterator(0);
while (it.hasNext()) {
commaDelim += text[it.next()]+",";
}
String[] http_response = http_req(commaDelim);
for (String snippet : http_response) {
idx = idxList.poll(); //this is not elegant, dependent on FIFO order
text[Idx] = snippet;
}
}
}
}
To complicate things further is that I want to process several longer texts so i would have need to have a matrix of String arrays, one for each text.
I don't like the class known reference
String[] text
or they way I deal with indices in this code...
Hoping to see some suggestions :)
Edit: changed a bit to be more clear. I cant really say what I am looking up, non-disclosure etc, sorry. Some names might be different from java (bit only small difference).
Ok... Here's an attempt to fully answer your question with example code.
I've never played with threads much, so I figured I'd try to learn something tonight.
This solution uses threads to allow the http request to take place asynchronously.
The asynchronous request is simulated by using Thread.sleep().
My test case is primitive: the main class just sleeps for 30 sec to wait for everything to finish up.
Like I said, I'm new to Thread programming, so I probably overlooked something.
Hopefully this gets you started in the right direction...
/**
* A class that asynchronously replaces text in an
* array of strings by using helper threads.
*/
public class TextReplacer {
private final int bufferSize;
List<String> wordList = new ArrayList<String>();
List<Integer> indexList = new ArrayList<Integer>();
int bufferPosition = 0;
int lastPosition = 0;
public TextReplacer(String[] a, int n) {
bufferSize = n;
if (a != null) {
wordList = Arrays.asList(a);
}
}
public void replaceText() {
int i = 0;
for (String thisWord : getWordListCopy()) {
if (shouldReplaceToken(thisWord)) {
indexList.add(i);
processTextReplacement();
}
i++;
}
}
private void processTextReplacement() {
if (isBufferReady()) {
int currentPos = lastPosition;
replaceStrings(getCsv(), currentPos);
}
}
/** Uses a thread to replace strings in wordList. */
private void replaceStrings(String csv, int pos) {
new ReplacerThread(wordList, indexList, csv, pos, bufferSize).start();
}
private String getCsv() {
StringBuilder csv = new StringBuilder();
for (int i = 0; i < bufferSize; i ++) {
int idx = indexList.get(lastPosition++);
csv.append(wordList.get(idx)).append(",");
}
return csv.toString();
}
private boolean isBufferReady() {
bufferPosition++;
return ( bufferPosition % bufferSize == 0 );
}
private List<String> getWordListCopy() {
List<String> listCopy = new ArrayList<String>();
listCopy.addAll(wordList);
return listCopy;
}
/**
* Simulates a 10% replacement rate by only
* returning true for input that ends with a 3.
*/
private boolean shouldReplaceToken(String s) {
return s.endsWith("3");
}
public List<String> getWordList() {
return wordList;
}
public String[] getWordArray() {
return wordList.toArray(new String[0]);
}
}
/**
* A thread that sleeps for up to 8 seconds, then
* replaces a bunch of words in the list that is
* passed to it in its constructor.
*/
public class ReplacerThread extends Thread {
List<String> originalWords;
List<Integer> indices;
String wordCsv;
String[] replacementWords;
int startPos;
int bufferSize;
int maxSleepMillis = 8000;
int sleepMillis = getSleepMillis();
int threadNum; // for debugging
String prefix = new String(); // for debugging
/** Create a new thread. */
public ReplacerThread(List<String> o, List<Integer> i,
String c, int p, int n) {
originalWords = o;
indices = i;
wordCsv = c;
startPos = p;
bufferSize = n;
threadNum = startPos / bufferSize;
int count = 0;
while (count++ < threadNum) {
prefix += " ";
}
}
#Override
public void run() {
replacementWords = httpReq(wordCsv);
for (int i = 0; i < bufferSize; i ++) {
int pos = startPos + i;
int idx = indices.get(pos);
originalWords.set(idx, replacementWords[i]);
}
print("Thread #" + threadNum + " COMPLETE");
}
/** Simulate an asynchronous http request by using Thread.sleep */
private String[] httpReq(String s) {
try {
printSleepMessage();
sleep(sleepMillis);
}
catch (InterruptedException ex) {}
String[] repText = s.split(",");
for (int i = 0; i < repText.length; i++) {
repText[i] = repText[i].replace("Line", "Funky Line");
}
return repText;
}
private void printSleepMessage() {
int ms = sleepMillis / 1000;
print("Thread #" + threadNum + " SLEEP(" + ms + ")");
}
private int getSleepMillis() {
Double ms = maxSleepMillis * Math.random();
return ms.intValue();
}
public void print(Object o) {
String s = (o == null ? "null" : o.toString());
System.out.println(prefix + s + "\n");
}
}
/** A class that tests my funky solution. */
public class Main {
static String inputFile = "test-input.txt";
static int bufferSize = 50;
public static void main(String[] args) {
String[] theInput = readInput();
TextReplacer testItem = new TextReplacer(theInput, bufferSize);
testItem.replaceText();
try {
// wait 40 seconds for everything to happen
Thread.sleep(40000);
}
catch (InterruptedException ex) { }
dumpOutput(testItem.getWordArray());
}
public static String[] readInput() {
File inFile = new File(inputFile);
List<String> lineList = new ArrayList<String>();
try {
BufferedReader buff = new BufferedReader(new FileReader(inFile));
String currentLine = buff.readLine();
while (currentLine != null) {
lineList.add(currentLine);
currentLine = buff.readLine();
}
}
catch (IOException ignoreMe) {}
print("Lines read: " + lineList.size());
return lineList.toArray(new String[0]);
}
public static void dumpOutput(String[] txt) {
long ms = System.currentTimeMillis();
String fileName = "output-" + ms + ".txt";
File outFile = new File(fileName);
try {
BufferedWriter buff = new BufferedWriter(new FileWriter(outFile));
for (String s : txt) {
buff.write(s);
buff.newLine();
}
}
catch (IOException ignoreMe) {}
print("Lines written: " + txt.length);
print("File: " + fileName);
}
public static void print(Object o) {
System.out.println(o == null ? "null" : o.toString());
}
}

Categories