Course Objectives

Students will demonstrate an understanding multitier software design
Students will understand the use of standard collections and generics in the Java programming language
Students will understand elementary Thread programming in Java
Students will create software that performs standard data operations with Java and a relational database
Students will use streams to communicate with sockets and files
Students will apply elementary XML functionality in Java
Students will understand the importance of using standard Object Oriented Programming (OOP) design tools such as the Unified Modeling Language (UML)
Students will demonstrate a preference for messages and Exceptions for communicating between components
Students will systematize Test Driven Development

Multithreading and Java

Objectives

  • Explain the importance of multithreading in modern software development.

  • Explain how multithreading can improve application performance and resource utilization.

  • Discuss the two primary ways to create threads in Java: extending the Thread class and implementing the Runnable interface. Provide examples of each approach and explain the advantages and disadvantages of each method.

  • Demonstrate how to start a thread using the start() method and explain the different states a thread can go through during its lifecycle (new, runnable, running, blocked/waiting, terminated). Use visual aids or diagrams to help students understand the thread lifecycle.

Objectives

  • Introduce the concept of thread priorities and how the JVM tries to schedule threads according to their priorities. Explain how to set the priority of a thread using the setPriority() method.

  • Discuss the challenges of working with threads, such as thread safety, deadlocks, and resource management. Explain the importance of synchronization in ensuring thread safety and preventing race conditions.

  • Demonstrate the use of synchronized methods and synchronized blocks to protect shared resources from concurrent access. Provide examples of both method-level and block-level synchronization.

Objectives

  • Introduce the volatile keyword and its role in ensuring memory visibility across threads. Explain how the volatile keyword can help prevent certain types of concurrency issues.

  • Discuss advanced concurrency tools in Java, such as ConcurrentHashMap, CopyOnWriteArrayList, and BlockingQueue. Explain how these tools can simplify concurrent programming and improve performance.

  • Introduce the Executor framework, including ExecutorService and ScheduledExecutorService, and demonstrate how they can be used to manage thread pools and execute tasks asynchronously.

Objectives

  • Explain the Future and Callable interfaces, and how they can be used to handle tasks that return results. Provide examples of using these interfaces with the Executor framework.8

  • Finally, introduce the Fork/Join framework and its use in recursively dividing parallelizable tasks into smaller subtasks. Discuss the work-stealing algorithm and how it can improve performance.8

Simple WebCrawler

  • The following code creates a program that will connect to the Internet, read a web page, and print the HTML to the console.

  • Learn about this code on the next slide.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;

public class SimpleWebCrawler {

    public static void main(String[] args) {
        String urlString = "https://www.kirkwood.edu";
        
//         String urlString = String.format("https://graphical.weather.gov/"
//                 + "xml/sample_products/browser_interface/ndfdXMLclient.php?"
//                 + "lat=41.911150&lon=-91.652149&product=time-series&"
//                 + "begin=%s&end=%s&maxt=maxt&mint=mint", LocalDateTime.now().with(LocalTime.MIN), LocalDateTime.now().with(LocalTime.MAX));
        try {
            // NOTE: Once a URL object is created, you cannot change it.
            URL url = new URL(urlString);
            BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
            String line;
            int lineNumber = 1;
            while ((line = reader.readLine()) != null) {
                System.out.printf("%4d %s\n", lineNumber++, line);
            }
        } catch (MalformedURLException ex) {
            System.out.println("ERROR: MalformedURLException " + ex.getMessage());
        } catch (IOException ex) {
            System.out.println("ERROR: IOException " + ex.getMessage());
        }
    }

}

Network Programming

  • To work with URLs in Java, you can pass a string representing a complete URL to the URL class.

    • A MalformedURLException will be thrown if something is wrong with the URL.

  • After creating the URL object we can use its .openStream() method to return an InputStream.

  • InputStream will return binary data. We can pass it to the constructor for an InputStreamReader to give access to methods that translate binary data into character data.

    • Since web pages are made up of HTML text, this is useful.

  • Then we pass the InputStreamReader to the constructor for a BufferedReader to give access to methods that can get the text from the InputStreamReader line by line.

    • The BufferedReader can read any resource from the Internet as text line by line (CSV and XML included)

Intermediate Webcrawler

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class IntermediateWebCrawler {
    public static void main(String[] args) {
        String pageContent = getPageContent();
        List<Shoe> shoes = getShoes(pageContent);
        shoes.forEach(System.out::println);
    }

    public static List<Shoe> getShoes(String pageContent) {
        List<Shoe> shoes = new ArrayList<>();
        int indexStartH2 = pageContent.indexOf("<h2>");
        while(indexStartH2 >= 0) {
            Shoe shoe = new Shoe();
            // Get ranking
            int indexStartRanking = indexStartH2 + 4;
            int indexEndRanking = pageContent.indexOf(".", indexStartRanking);
            String ranking = pageContent.substring(indexStartRanking, indexEndRanking);
//            System.out.println(ranking);
            shoe.setRanking(Integer.parseInt(ranking));

            // Get shoe name
            int indexStartShoeName = indexEndRanking + 2;
            int indexEndShoeName = pageContent.indexOf("</h2>", indexStartShoeName);
            String shoeName = pageContent.substring(indexStartShoeName, indexEndShoeName);
//            System.out.println(shoeName);
            shoe.setShoeName(shoeName);

            // Get image
            int indexStartShoeImage = pageContent.indexOf("https", indexEndShoeName);
            int indexEndShoeImage = pageContent.indexOf(".jpg", indexStartShoeImage) + 4;
            String shoeImage = pageContent.substring(indexStartShoeImage, indexEndShoeImage);
//            System.out.println(shoeImage);
            shoe.setShoeImage(shoeImage);


            shoes.add(shoe);
            indexStartH2 = pageContent.indexOf("<h2>", indexStartH2 + 1);
        }
        return shoes;
    }

    public static String getPageContent() {
        String urlString = "https://www.espn.com/sportsnation/story/_/id/14734617/ranking-every-air-jordan-sneaker-1-xx9";
        //        String urlString = "https://www.espn.com/espn/feature/story/_/id/39771146/sneakerhead-guide-every-nba-wnba-signature-sneaker-history";
        String pageContent = "";
        try {
            // NOTE: Once a URL object is created, you cannot change it.
            URL url = new URL(urlString);
            BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                pageContent += line;
            }
        } catch (MalformedURLException ex) {
            System.out.println("ERROR: MalformedURLException " + ex.getMessage());
        } catch (IOException ex) {
            System.out.println("ERROR: IOException " + ex.getMessage());
        }
        return pageContent;
    }
}

class Shoe {
    private int ranking;
    private String shoeName;
    private String shoeImage;

    public Shoe() {
    }

    public int getRanking() {
        return ranking;
    }

    public void setRanking(int ranking) {
        this.ranking = ranking;
    }

    public String getShoeName() {
        return shoeName;
    }

    public void setShoeName(String shoeName) {
        this.shoeName = shoeName;
    }

    public String getShoeImage() {
        return shoeImage;
    }

    public void setShoeImage(String shoeImage) {
        this.shoeImage = shoeImage;
    }

    @Override
    public String toString() {
        return "ESPNShoe{" +
                "ranking=" + ranking +
                ", shoeName='" + shoeName + '\'' +
                ", shoeImage='" + shoeImage + '\'' +
                '}';
    }
}

Advanced WebCrawler

What is Multithreading?

  • Multithreading provides a way to perform multiple actions at the same time — simultaneously in parallel — rather than in a strict sequence.

  • It is a programming concept not specific to Java.

  • Read Chapter 11 of either the Beginner's Guide or Complete Reference textbook.

  • When dividing a program into independent tasks, each task becomes a thread.

  • Threads are like small robots that run commands to complete specific tasks they were assigned.

What is a Thread?

Multithreading

  • Multithreaded program: A program that can perform two or more operations at the same time. Its a form of multitasking.

  • Multi-threaded programming is common in video games.  Every object moving around (projectiles, vehicles, etc.) is a separate thread doing its own thing.
  • Thread: A subprocess (a smaller piece) of a multithreaded program.

    • Must run at the same time as another thread.

    • Each thread defines a separate path of execution.

  • Takes advantage of idle time that is present in most programs.
    • While one part of the program is performing a task another part can be performing another (sending a file, reading input, buffering data, etc.)
  • Programs can switch between threads multiple times per second, giving the impression that they are running simultaneously.

    • Suppose you have a program with a user interface. When the user clicks 'Continue', some calculations happen, and the user sees the following screen.

    • If these actions were performed sequentially, then the program would just hang after the user clicks the 'Continue' button. The user will see the screen with the 'Continue' button until the program performs all internal calculations and reaches the part where the user interface is refreshed.

    • Or we could rework our program to perform our calculations on one thread and draw the user interface on another. If we take this route, then the program won't freeze up and the user will move smoothly between screens without worrying about what's happening inside.

Why are Threads Useful?

Creating Threads

  • The Runnable interface plays a crucial role in working with threads, as it contains a single abstract method - void run.
  • The run method must be overridden to specify the commands to be executed by a Thread object.
public class Printer implements Runnable {
    @Override
    public void run() {
        System.out.println("Hello I'm a thread!");
    }
}
  • To create a new thread, one must instantiate a new Thread object, passing the Runnable object, and then calling the start method on the created Thread object.
public class PrinterController {
    public static void main(String[] args) {
        Printer printer1 = new Printer();
        Thread thread = new Thread(printer1);
        thread.start();
    }
}
  • Note that the main method is considered the parent thread and the Printer object is considered a child thread.

Thread is Runnable

  • The Thread class inherits the Runnable interface so that the same code can be written by extending the Thread class instead of implementing the Runnable interface.
  • Note there is one fewer line in the main method because we instantiate the Thread object via the Printer object.
public class Printer extends Thread {

    @Override
    public void run() {
        System.out.println("Hello I'm a thread!");
    }
}
public class PrinterController {
    public static void main(String[] args) {
        Printer printer1 = new Printer();
        printer1.start();
    }
}

Multiple Threads

  • Additional threads can be created to handle parallel execution of tasks, with each child thread having its own run method.
  • The getName() method is inherited from the Thread class.
  • Note that the thread names may not appear in order when you run the program because threads don't run sequentially.
public class Printer extends Thread {

    public Printer(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println("Starting '" + this.getName() + "' thread.");
        System.out.println("Ending '" + this.getName() + "' thread.");
    }
}
public class PrinterController {
    public static void main(String[] args) {
        Printer printer1 = new Printer("Epson");
        printer1.start();
        
        Printer printer2 = new Printer("HP");
        printer2.start();
        
        Printer printer3 = new Printer("Brother");
        printer3.start();
        
        Printer printer4 = new Printer("Xerox");
        printer4.start();
    }
}

The join() method

  • By calling the join() method, the main thread stops executing until the specified thread has finished.
  • In other words, the program waits for the specified thread to complete before continuing.
  • This allows for sequential execution of threads.
  • Note that the join() method automatically checks if any thread has interrupted the specified thread. If it does, an InterruptedException is thrown.
public class PrinterController {
    public static void main(String[] args) throws InterruptedException {
    	Printer printer1 = new Printer("Epson");
        printer1.start();
        printer1.join();
		// The second thread will start running only after the first thread is finished
        
        Printer printer2 = new Printer("HP");
        printer2.start();
        printer2.join();
		// The third thread will start running only after the second thread is finished
        
        Printer printer3 = new Printer("Brother");
        printer3.start();
        printer3.join();
		// The main thread will continue running only after the third thread is finished
        
        Printer printer4 = new Printer("Xerox");
        printer4.start();
        printer4.join();
		// The main thread will continue running only after the fourth thread is finishe
        
    }
}

The sleep() method

  • The sleep() method is a static method of the Thread class that takes a time interval (aka delay) in milliseconds as its parameter.
  • It makes a program pause for a specified amount of time.
  • Use sleep() to execute an action N times per second.
  • For example, if an action needs to be performed ten times per second, add a delay of 100 milliseconds to achieve the desired frequency.
  • This code is intended to run a Clock thread forever. You can ignore the warning messages that appear.
public class ClockController {

    public static void main(String[] args) throws InterruptedException {
        Clock clock = new Clock(); // clock is a child thread of the main thread.
        clock.start(); // The main thread starts the child thread.
    }
}
public class Clock extends Thread {
    public void run() {
        while (true) {
        	System.out.println("Tick"); // writes the word Tick to the console once a second
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                
            }
        }
        System.out.println("BOOM!");
    }
}

The interrupt() method

  • Threads can be signaled to stop by calling the interrupt() method from the Thread class.
  • The sleep() method automatically checks if any thread has interrupted the current thread. When it does, an InterruptedException is thrown.
public class Clock extends Thread {
    public void run() {
        while (true) {
        	System.out.println("Tick"); // writes the word Tick to the console once a second
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                break
            }
        }
        System.out.println("BOOM!");
    }
}
public class ClockManager {

    public static void main(String[] args) throws InterruptedException {
        Clock clock = new Clock(); // clock is a child thread of the main thread.
        clock.start(); // The main thread starts the child thread.
        Thread.sleep(5000); // Wait 5 seconds...
        clock.interrupt(); // ... then stop the task.
    }

}

The interrupt() method

  • There are 2 possibilities when the interrupt() method is called:
  1. If the thread is in the waiting state (due to the join or sleep methods) then the wait will be interrupted and the program will throw an InterruptedException.
  2. If the thread is in a functioning state, then the boolean interrupted flag will be set on the object.
  • But we have to check the value of this flag on the thread and correctly complete the work. That's why the Thread class has the boolean isInterrupted() method.
public class Clock extends Thread {
    public void run() {
        // writes the word Tick to the console once a second 
        // as long the thread has not been interrupted.
        while (!isInterrupted()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                break;
            }
            System.out.println("Tick");
        }
    }
}

The stop() method

  • In a previous version of Java there was a stop() method that allowed a thread to be interrupted.

  • This method is deprecated because it just stops the thread without releasing resources or error handling. 

  • To exaggerate slightly, the stop() method is like pulling the power cord from an outlet to turn off a computer. Yeah, you can get the desired result, but after a couple of weeks, your computer won't be thanking you for treating it that way.

Summary

  • A Thread object represents an individual execution environment.
  • The Runnable interface is key in specifying the run() method of a Thread object.

  • Working with threads involves instantiating Thread objects, and then calling the start() method, which executes the run() method.

  • Threads can execute code in parallel rather than sequentially.

  • The main thread can then call methods like join() and interrupt() on the Thread object to control the execution of the child thread.
  • The sleep() method is a static method, meaning it is not called on a specific thread object. Use it to pause or delay any part of the program.

Popular email services

Azure Email Communication

Azure Email Communication

  • Click the "Provision domains" tab.
  • Add a new Azure Domain. 
  • Visit the new Email Communication Services Domain, and click the "MailFrom Addresses" tab.
  • Click the "Add" button.
  • Type your name in the "Display Name" field.
  • Type a username of your choice in the "MailFrom Address" field. Please note that this email address can only send messages. It cannot receive any.
  • Click the elipsis icon and choose "Copy MailFrom Address".
  • Open your .env file and add this entry:
    AZURE_EMAIL_FROM=DoNotReply@XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXazurecomm.net

Azure Communication Service

  • Go to the Azure Portal home page and create a new Azure Communication Service. Select the same resource group as your web app. Name it anything you want. It will take several minutes to deploy.

  • After the service is deployed, click the button to go to the resource. Click the "Email > Domains" tab. Connect your new Azure domain.

  • Click the "Try Email" tab. Send yourself a test message.

  • Your email program may consider it spam. If that happens, mark it as safe to unblock the sender.

Azure Email Dependency

  • Add the Microsoft Azure Communications SDK For Email dependency to your pom.xml file.
  • In the "shared" package, create a new class called "CommunicationService".

  • Go back to Azure, find the Java code. Click the "Insert my connection string" checkbox. Copy the code, and change the class name to CommunicationService.

  • Add a semi-colon at the end of the third line. Delete the first line. Change "Emailmessage" to "EmailMessage".

  • Move the connectionString to the .env file.
    AZURE_EMAIL_CONNECTION=endpoint=https://XXX.
    unitedstates.communication.azure.com/;accesskey=XXX

AzureEmail Class

  • Create an AzureEmail class in the shared package.
  • Create a getEmailClient method.
  • Get the AZURE_EMAIL_CONNECTION environment variable. 
  • Copy and paste the code from the Azure Try Email page.
import com.azure.communication.email.EmailClient;
import com.azure.communication.email.EmailClientBuilder;
import io.github.cdimascio.dotenv.Dotenv;

public class AzureEmail {
    public static EmailClient getEmailClient() {
        String connectionString = Dotenv.load().get("AZURE_EMAIL_CONNECTION");

        EmailClient emailClient = new EmailClientBuilder()
                .connectionString(connectionString)
                .buildClient();

        return emailClient;
    }
}

AzureEmail Class

  • Create a sendEmail method.
  • Add the jsoup dependency to the pom.xml file. We will use this to strip HTML from a string. 
  • Get the AZURE_EMAIL_FROM environment variable. 
  • Copy and paste the code from the Azure Try Email page.
  • Add a try-catch to determine if the request fails to be sent.
// code omitted
import com.azure.communication.email.models.EmailAddress;
import com.azure.communication.email.models.EmailMessage;
import com.azure.communication.email.models.EmailSendResult;
import com.azure.core.util.polling.PollResponse;
import com.azure.core.util.polling.SyncPoller;
import edu.kirkwood.ecommerce.thread.ContactUsEmail;

import org.jsoup.Jsoup;

public class AzureEmail {
    // code omitted

    public static String sendEmail(String toEmailAddress, String subject, String bodyHTML) {
        EmailClient emailClient = getEmailClient();
        EmailAddress toAddress = new EmailAddress(toEmailAddress);

        String body = Jsoup.parse("bodyHTML").text();

        EmailMessage emailMessage = new EmailMessage()
                .setSenderAddress(Dotenv.load().get("AZURE_EMAIL_FROM"))
                .setToRecipients(toAddress)
                .setSubject(subject)
                .setBodyPlainText(body)
                .setBodyHtml(bodyHTML);
        SyncPoller<EmailSendResult, EmailSendResult> poller = null;
        try {
            poller = emailClient.beginSend(emailMessage, null);
        } catch(RuntimeException e) {
            return e.getMessage();
        }
        PollResponse<EmailSendResult> result = poller.waitForCompletion();

        return "";
    }
}

Azure Email Dependency

  • Add a main method to test the method.
public static void main(String[] args) {

    String toEmailAddress = "abc@example.com"; // Get emailAddress parameter
    boolean error =false;
    if(toEmailAddress == null || !Validators.isValidEmail(toEmailAddress)) {
        // set emailAddress-error attribute
        System.out.println("Invalid email address: " + toEmailAddress);
        error = true;
    }
    String subject = "Testing"; // Get subject parameter
    if(subject == null || subject.isEmpty()) {
        // set subject-error attribute
        System.out.println("Subject is required");
        error = true;
    }
    String bodyHTML = "<h2>This is a test email</h2><p>Testing, Testing, Testing</p>"; // Get messageBody parameter
    if(bodyHTML == null || bodyHTML.isEmpty()) {
        // set messageBody-error attribute
        System.out.println("Body is required");
        error = true;
    }
    if(!error) {
        String errorMessage = sendEmail(toEmailAddress, subject, bodyHTML);
        if (errorMessage.isEmpty()) {
        	// set send-success attribute
            System.out.println("Message sent to " + toEmailAddress);
        } else {
        	// Set send-error attribute
            System.out.println("Message not sent to " + toEmailAddress + " - " + errorMessage);
        }
    }
}
  • Add a main method to the Validators class to check for a valid email address.
// Source: Google Gemini
public static boolean isValidEmail(String email) {
    String regex = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(email);
    return matcher.matches();
}

EmailThread

  • Start two or more EmailThread objects.
EmailThread emailThread1 = new EmailThread("marc.hauschildt@gmail.com", "Testing Thread", "<h2>Testing Thread</h2>");
emailThread1.start();
EmailThread emailThread2 = new  EmailThread("mlhauschildt@yahoo.com", "Testing Thread", "<h2>Testing Thread</h2>");
emailThread2.start();
if (emailThread1.getErrorMessage().isEmpty()) {
    System.out.println("Message sent to " + emailThread1.getToEmailAddress());
} else {
    System.out.println("Message not sent to " + emailThread1.getToEmailAddress() + " - " + emailThread1.getErrorMessage());
}
if (emailThread2.getErrorMessage().isEmpty()) {
    System.out.println("Message sent to " + emailThread2.getToEmailAddress());
} else {
    System.out.println("Message not sent to " + emailThread2.getToEmailAddress() + " - " + emailThread2.getErrorMessage());
}
  • Create an EmailThread class in the shared package.
  • The run method will call the sendEmail method.
public class EmailThread extends Thread {
    private String toEmailAddress;
    private String subject;
    private String bodyHTML;
    private String errorMessage;

    public EmailThread(String toEmailAddress, String subject, String bodyHTML) {
        this.toEmailAddress = toEmailAddress;
        this.subject = subject;
        this.bodyHTML = bodyHTML;
    }

    @Override
    public void run() {
        errorMessage = AzureEmail.sendEmail(toEmailAddress, subject, bodyHTML);
        // TODO: If an error occurs, try a backup email service.
    }

    public String getToEmailAddress() {
        return toEmailAddress;
    }

    public String getErrorMessage() {
        return errorMessage;
    }
}

Factory Method

  • A static factory method can be added to accomplish 3 things:
    (a) create a new MyThread instance, (b) call start() on the thread associated with that instance, and (c) then return a reference to the newly created MyThread object.

  • Typically, factory methods are static methods of a class.

  • The createAndStart() factory method enables an object to be constructed and then set to some specified state prior to being returned to the caller.

public class MyThread implements Runnable {
    public Thread thread;
    
    public MyThread(String name) {
        thread = new Thread(this, name);
    }
    
    // a factory method that creates, starts, and returns a thread
    public static void createAndStart(String name) {
        MyThread mt = new MyThread(name);
        mt.thread.start();
    }
    
    @Override
    public void run() {
        System.out.println(thread.getName() + " thread starting.");
        try {
          for (int i = 0; i < 10; i++) {
              Thread.sleep(400);
              System.out.println("In " + thread.getName() + ", count is " + i);
          }  
        } catch(InterruptedException ex) {
            System.out.println(thread.getName() + " interrupted");
        }
        System.out.println(thread.getName() + " thread ending");
    }
}

Call the Factory Method

  • Now, the thread will begin execution in a single call to the .createAndStart() method from the main thread.

public class UseThreads {
    public static void main(String[] args) {
        System.out.println("Main thread starting");
        MyThread.createAndStart("Child #1");
        for(int i = 0; i < 50; i++) {
            System.out.print(".");
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                System.out.println("Main thread interrupted");
            }
        }
        System.out.println("Main thread ending");
    }
}
  • Your programs can have as many threads as it needs. This code create 3 child threads.

  • Once started, all three threads share the CPU. Threads are started in the order created. Java is free to schedule the execution of threads in its own way

public class UseThreads {
    public static void main(String[] args) {
        System.out.println("Main thread starting");
        MyThread.createAndStart("Child #1");
        MyThread.createAndStart("Child #2");
        MyThread.createAndStart("Child #3");
        for(int i = 0; i < 50; i++) {
            System.out.print(".");
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                System.out.println("Main thread interrupted");
            }
        }
        System.out.println("Main thread ending");
    }
}

Extend Thread

  • When Extending Thread, it is also possible to include the ability to create and start a thread in using a static factory method.

  • This method creates a new MyThread instance, calls start(), and returns a reference to the thread.

public class MyThread extends Thread {

    public MyThread(String name) {
        super(name);
    }
    
    public static MyThread createAndStart(String name) {
        MyThread mt = new MyThread(name);
        mt.start();
        return mt;
    }
    
    @Override
    public void run() {
        System.out.println(getName() + " thread starting.");
        try {
          for (int i = 0; i < 10; i++) {
              Thread.sleep(400);
              System.out.println("In " + getName() + ", count is " + i);
          }  
        } catch(InterruptedException ex) {
            System.out.println(getName() + " interrupted");
        }
        System.out.println(getName() + " thread ending");
    }
}
public class UseThreads {
    public static void main(String[] args) {
        System.out.println("Main thread starting");
        MyThread mt = MyThread.createAndStart("Child #1");
        for(int i = 0; i < 50; i++) {
            System.out.print(".");
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                System.out.println("Main thread interrupted");
            }
        }
        System.out.println("Main thread ending");
    }
}

When a Thread Ends

  • In our previous examples, the main thread  was kept alive until the other threads ended by having the main thread sleep longer.

    • This is not the best solution.

  • We can stop the main thread as soon as all other threads have ended.

  • The Thread class provides an isAlive() method to return true if a thread is running, and false otherwise.

  • This code produces the same output as before, but the main thread ends as soon as the other threads finish.

public class UseThreads {
    public static void main(String[] args) {
        System.out.println("Main thread starting");
        MyThread mt1 = MyThread.createAndStart("Child #1");
        MyThread mt2 = MyThread.createAndStart("Child #2");
        MyThread mt3 = MyThread.createAndStart("Child #3");
        do {
            System.out.print(".");
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                System.out.println("Main thread interrupted");
            }
        } while(mt1.thread.isAlive() || mt2.thread.isAlive() || mt3.thread.isAlive());
        System.out.println("Main thread ending");
    }
}

When a Thread Ends

  • Another way to wait for a thread to finish is with the Thread class join() method.

  • This method waits for specified threads to "join" before the calling thread ends.

    • This ensures that the main thread is the last to stop.

  • Similar to the last slide, the main thread ends as soon as the other threads finish.

public class UseThreads {
    public static void main(String[] args) {
        System.out.println("Main thread starting");
        MyThread mt1 = MyThread.createAndStart("Child #1");
        MyThread mt2 = MyThread.createAndStart("Child #2");
        MyThread mt3 = MyThread.createAndStart("Child #3");
        try {
            mt1.thread.join();
            System.out.println("Child #1 joined");
            mt2.thread.join();
            System.out.println("Child #2 joined");
            mt3.thread.join();
            System.out.println("Child #3 joined");
        } catch (InterruptedException ex) {
            System.out.println("Main thread interrupted");
        }
        System.out.println("Main thread ending");
    }
}

Thread Priorities

  • A thread priority is a numeric value that specifies when one thread should be chosen over another.

    • Low-priority threads receive less access to CPU time

    • High-priority threads receive more access to CPU time

  • When started, a child thread's priority setting is equal to its parent thread. This can be changed with the .setPriority() method

    • This method requires an integer argument that specifies the new priority setting, within the range 1 to 10.

    • The normal/default priority is 5.

  • A child thread's priority setting can be obtained with the .getPriority() method.

  • Threads will switch based on their priority.

  • See code on next slide.

Thread Priorities

public class UseThreads {
    public static void main(String[] args) {
        System.out.println("Main thread starting");
        MyThread mt1 = MyThread.createAndStart("Child #1");
        mt1.thread.setPriority(Thread.MIN_PRIORITY);
        MyThread mt2 = MyThread.createAndStart("Child #2");
        mt2.thread.setPriority(Thread.MAX_PRIORITY);
        MyThread mt3 = MyThread.createAndStart("Child #3");
        mt3.thread.setPriority(Thread.NORM_PRIORITY);
        try {
            mt1.thread.join();
            System.out.println("Child #1 joined");
            mt2.thread.join();
            System.out.println("Child #2 joined");
            mt3.thread.join();
            System.out.println("Child #3 joined");
        } catch (InterruptedException ex) {
            System.out.println("Main thread interrupted");
        }
        System.out.println("Main thread ending");
    }
}
public class MyThread implements Runnable {
    public Thread thread;
    
    public MyThread(String name) {
        thread = new Thread(this, name);
    }
    
    public static MyThread createAndStart(String name) {
        MyThread mt = new MyThread(name);
        mt.thread.start();
        return mt;
    }
    
    @Override
    public void run() {
        System.out.println(thread.getName() + " thread starting with priority: " + thread.getPriority() + ".");
        try {
          for (int i = 0; i < 10; i++) {
              Thread.sleep(400);
              System.out.println("In " + thread.getName() + ", count is " + i);
          }  
        } catch(InterruptedException ex) {
            System.out.println(thread.getName() + " interrupted");
        }
        System.out.println(thread.getName() + " thread ending");
    }
}

Synchronization

  • Synchronization: coordinating multiple threads to share a single resource without conflicts.

    • If one thread is writing to a file, the second thread must be prevented from doing the same. We want the second thread to wait, or be held in a suspended state, until the other thread is finished executing.

  • A monitor is a mechanism built into all Java objects by which that object may be locked for use by a single thread at a time.

  • The monitor works by implementing the concept of a lock. A lock will show that an object is being used by a single thread in a way that should prevent other threads from using it.

    • When the thread has completed its use of the object, the lock is released and other threads may then have access to the object.

    • The lock is maintained by the object's monitor.

Synchronization

  • A synchronized method uses the keyword synchronized to show that the method is part of an object that must be locked during the method call.

    • Creating locks happens automatically when using the synchronized keyword.

  • Create a new class called SumArray. It contains a method sumArray(), which sums an array of integers.

  • When sumArray() is called, the calling thread will enter the object's monitor automatically, which then locks the object and no other thread can enter the method or any other synchronized method defined by the object's class.

  • The sleep() method will allow a task switch to occur. But remember, because sumArray is synchronized, only one thread can use it at a time.

public class SumArray {
    private int sum;
    
    public synchronized int sumArray(int[] nums) {
        sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
            System.out.println("Running total for " + Thread.currentThread().getName() + " is " + sum);
            try {
                Thread.sleep(10);
            } catch (InterruptedException ex) {
                System.out.println("Thread interruped");
            }
        }
        return sum;
    }
}

Synchronization

  • In the MyThread class, we are adding a static object of type SumArray to obtain the sum of an integer array. Because it is static, there is only one copy of it that is shared by all instances of MyThread.

public class MyThread implements Runnable {
    public Thread thread;
    public static SumArray sa = new SumArray();
    int[] a;
    int answer;
    
    public MyThread(String name, int[] nums) {
        thread = new Thread(this, name);
        a = nums;
    }
    
    public static MyThread createAndStart(String name, int[] nums) {
        MyThread mt = new MyThread(name, nums);
        mt.thread.start();
        return mt;
    }
    
    @Override
    public void run() {
        int sum;
        System.out.println(thread.getName() + " thread starting with priority: " + thread.getPriority() + ".");
        answer = sa.sumArray(a);
        System.out.println("Sum for " + thread.getName() + " is " + answer);
        System.out.println(thread.getName() + " thread ending");
    }
}
public class UseThreads {
    public static void main(String[] args) {
        int[] a = {1, 2, 3, 4, 5};
        System.out.println("Main thread starting");
        MyThread mt1 = MyThread.createAndStart("Child #1", a);
        mt1.thread.setPriority(Thread.MIN_PRIORITY);
        MyThread mt2 = MyThread.createAndStart("Child #2", a);
        mt2.thread.setPriority(Thread.MAX_PRIORITY);
        MyThread mt3 = MyThread.createAndStart("Child #3", a);
        mt3.thread.setPriority(Thread.NORM_PRIORITY);
        try {
            mt1.thread.join();
            System.out.println("Child #1 joined");
            mt2.thread.join();
            System.out.println("Child #2 joined");
            mt3.thread.join();
            System.out.println("Child #3 joined");
        } catch (InterruptedException ex) {
            System.out.println("Main thread interrupted");
        }
        System.out.println("Main thread ending");
    }
}
  • The main class creates 3 threads and has each compute the sum of an integer array.

Synchronization

  • To understand the importance of synchronization, remove the synchronized keyword and run the code again.

  • When threads are allowed to run concurrently, the running total is stored in sum. Thus, when multiple threads call sumArray() at the same time, incorrect results are produced because sum reflects the summation of all threads mixed together.

  • In summary:

    • A synchronized method is created by preceding its declaration with "synchronized"

    • For any given object, once a synchronized method has been called, the object is locked and no synchronized methods on the same object can be used by another thread.

    • Other threads trying to call an in-use synchronized object will enter a wait state until the object is unlocked.

Synchronization 3rd Party Code

  • You don't always have access to source code made by another party.

  • To achieve synchronization without the ability to modify someone else's source code, you simply put calls to the method defined by a class inside a synchronized block.

  • Assume sumArray() is not available to modify and it has public access. The following code will achieve the same results as before

public class MyThread implements Runnable {
    public Thread thread;
    public static SumArray sa = new SumArray();
    int[] a;
    int answer;
    
    public MyThread(String name, int[] nums) {
        thread = new Thread(this, name);
        a = nums;
    }
    
    public static MyThread createAndStart(String name, int[] nums) {
        MyThread mt = new MyThread(name, nums);
        mt.thread.start();
        return mt;
    }
    
    @Override
    public void run() {
        int sum;
        System.out.println(thread.getName() + " thread starting with priority: " + thread.getPriority() + ".");
        synchronized(sa) {
            answer = sa.sumArray(a);
        }
        System.out.println("Sum for " + thread.getName() + " is " + answer);
        System.out.println(thread.getName() + " thread ending");
    }
}

Monitor

  • A monitor is a thread-safe mechanism that safely allows access to a method or variable by more than one thread.

  • Two threads cannot access a monitored (synchronized) section at the same time.

  • One thread will start. Monitor will prevent the other from accessing the region before the first one finishes.

  • Synchronization mechanisms are placed at the Class Hierarchy root: Object.

    • Monitor does not need to be explicitly implemented.

    • The wait() and notify() methods will use an object's monitor mechanism to communicate among different threads.

  • The second to last section of Chapter 11 in the textbook discusses Thread communication using notify(), wait(), and notifyAll() methods.

Hopper Class

  • There's a Thread example project on Talon for you to download and test.  There are many parts to it. Let's attempt to build it from scratch.

  • Create a new project called "ThreadExample". Create a new class called "Hopper". This class uses a Generic <T> to represent a container for a specific type of material used in manufacturing.

  • Contents added to the top and removed from the bottom - Pics.

import java.util.ArrayList;

public class Hopper<T> {

    private final ArrayList<T> items;

    public Hopper() {
        items = new ArrayList<>();
    }

    public synchronized ArrayList<T> getList(){
        return items;
    }

    public synchronized T getItem(){
        T item = null;
        if(!items.isEmpty()){
            item = items.get(0);
            items.remove(item);
        }
        return item;
    }

    public synchronized void addItem(T item){
        items.add(item);
    }

    public synchronized boolean isEmpty(){
        return items.isEmpty();
    }
}

Hopper Class

  • The ArrayList will hold items of a specific type: Axle or Wheel

  • The constructor initializes the ArrayList.

  • Notice how the getList(), getItem(), addItem(), and isEmpty() methods are all synchronized.

    • This is used to coordinate the activities of multiple threads.

      • Each method is part of a Hopper object that must be locked during the method call.

      • If one thread is adding an item to the ArrayList, the second thread will be prevented from doing the same.

  • .getList() will simply return the ArrayList of items.

  • .getItem() will first check if the Hopper contains at least one item, and if it does remove and return the first element.

  • .addItem() will add a new items of a particular type to the list.

  • .isEmpty() will tell us if the Hopper contains at least one item.

Wheel Class

  • The Wheel class has two instance variables: source and createDate.

    • The source is the name of the assembly line (representing a thread) that created the wheel

    • The createDate is a time stamp for when the wheel was created.

  • A constructor assigns values to both variables.
  • A .toString() method provides the String representation of a Wheel.
  • Getter and setter methods are provided for both variables.
import java.time.LocalDateTime;

public class Wheel {

    private String source;
    private LocalDateTime createDate;

    public Wheel(String source, LocalDateTime createDate) {
        this.source = source;
        this.createDate = createDate;
    }

    @Override
    public String toString() {
        return "Wheel[" + "source=" + source + ", created on: " + createDate + ']';
    }

    public String getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public LocalDateTime getCreateDate() {
        return createDate;
    }

    public void setCreateDate(LocalDateTime createDate) {
        this.createDate = createDate;
    }

}

Axle Class

  • The Axle class is basically the same as the Wheel class, but represents a different part in the manufacturing process.

import java.time.LocalDateTime;

public class Axle {
    private String source;
    private LocalDateTime createDate;

    public Axle(String source, LocalDateTime createDate) {
        this.source = source;
        this.createDate = createDate;
    }

    @Override
    public String toString() {
        return "Axle[" + "source=" + source + ", created on: " + createDate + ']';
    }

    public String getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public LocalDateTime getCreateDate() {
        return createDate;
    }

    public void setCreateDate(LocalDateTime createDate) {
        this.createDate = createDate;
    }

}

AssemblyLine Class

  • We can begin building the main logic of the program. Starting with two Hoppers: one to hold Wheels and one to hold Axles.

public class AssemblyLine {

    public static void main(String[] args) {
        Hopper<Wheel> wheels = new Hopper<>();
        Hopper<Axle> axles = new Hopper<>();
    }

}

WheelMaker Class

  • The WheelMaker class represents a thread. It will simulate an assembly line in a manufacturing setting.

  • The class has 3 instance variables:

    • The hopper represents the storage unit for completed wheels

    • The wheelCount is the total number of wheels the line will make

    • The lineName is the name of the assembly line

  • A constructor assigns values to all variables.
  • The class implements Runnable, therefore must include a run() method
    • It will create one wheel every 2 seconds (2000 milliseconds) and add each Wheel to a Hopper.
import java.time.LocalDateTime;

public class WheelMaker implements Runnable {

    private final Hopper hopper;
    private int wheelCount;
    private final String lineName;
    
    public WheelMaker(Hopper hopper, int wheelCount, String lineName) {
        this.hopper = hopper;
        this.wheelCount = wheelCount;
        this.lineName = lineName;
    }
    
    @Override
    public void run() {
        Wheel wheel;
        while (wheelCount > 0) {
            wheelCount--;
            wheel = new Wheel(lineName, LocalDateTime.now());
            System.out.println("Adding wheel: " + wheel);
            hopper.addItem(wheel);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException ie) {
                // ignore for the moment
            }
        }
    }

}

AxleMaker Class

  • The AxleMaker class is basically the same as the WheelMaker class, but represents a different assembly line.

  • The main difference is that it will create a new axle every half second (500 milliseconds) and add each Axle to a Hopper.

import java.time.LocalDateTime;

public class AxleMaker implements Runnable {

    private final Hopper hopper;
    private int axleCount;
    private final String lineName;
    
    public AxleMaker(Hopper hopper, int axleCount, String lineName) {
        this.hopper = hopper;
        this.axleCount = axleCount;
        this.lineName = lineName;
    }

    @Override
    public void run() {
        Axle axle;
        while (axleCount > 0) {
            axleCount--;
            axle = new Axle(lineName, LocalDateTime.now());
            System.out.println("Adding axle: " + axle);
            hopper.addItem(axle);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
                // ignore for the moment
            }
        }
    }
}

Main Class

  • Continue to build the main logic of the program by creating three assembly lines: two for making wheels and one for making axles.

    • Two wheels go one each axle.

public class AssemblyLine {

    public static void main(String[] args) {
        Hopper<Wheel> wheels = new Hopper<>();
        WheelMaker wheelMaker1 = new WheelMaker(wheels, 20, "Wheel Assembly Line 1");
        WheelMaker wheelMaker2 = new WheelMaker(wheels, 20, "Wheel Assembly Line 2");
        
        Hopper<Axle> axles = new Hopper<>();
        AxleMaker axleMaker = new AxleMaker(axles, 20, "Axle Assembly Line 1");
    }

}

WheelAssembly Class

  • A wheel assembly is one axle and two wheels put together.

  • The WheelAssembly class has five instance variables:

    • The first two are Wheel objects - left and right

    • The third is an Axle object

    • The source is the name of the assembly line (representing a thread) that created the wheel assembly

    • The createDate is a time stamp for when the wheel assembly was created.

  • A constructor assigns values to all variables.
  • A .toString() method provides the String representation of the WheelAssembly.
  • Getter and setter methods are provided for both variables.
import java.time.LocalDateTime;

public class WheelAssembly {

    private Wheel leftWheel;
    private Wheel rightWheel;
    private Axle axle;
    private String source;
    private LocalDateTime createdDate;
    
    public WheelAssembly(Wheel leftWheel, Wheel rightWheel, Axle axle,
             String source, LocalDateTime createdDate) {
        this.leftWheel = leftWheel;
        this.rightWheel = rightWheel;
        this.axle = axle;
        this.source = source;
        this.createdDate = createdDate;
    }

    @Override
    public String toString() {
        return "WheelAssembly[" + "source=" + source
                + ", created on " + createdDate + ']';
    }

    public Wheel getLeftWheel() {
        return leftWheel;
    }

    public void setLeftWheel(Wheel leftWheel) {
        this.leftWheel = leftWheel;
    }

    public Wheel getRightWheel() {
        return rightWheel;
    }

    public void setRightWheel(Wheel rightWheel) {
        this.rightWheel = rightWheel;
    }

    public Axle getAxle() {
        return axle;
    }

    public void setAxle(Axle axle) {
        this.axle = axle;
    }

    public String getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public LocalDateTime getCreatedDate() {
        return createdDate;
    }

    public void setCreatedDate(LocalDateTime createdDate) {
        this.createdDate = createdDate;
    }

}

WheelAssemblyMaker Class

  • A description of this code starts on the next slide.

import java.time.LocalDateTime;

public class WheelAssemblyMaker implements Runnable {

    private final Hopper<Wheel> wheelHopper;
    private final Hopper<Axle> axleHopper;
    private final Hopper<WheelAssembly> assemblyHopper;
    private int assemblyCount;
    private final String lineName;
    
    public WheelAssemblyMaker(Hopper<Wheel> wheelHopper, Hopper<Axle> axleHopper,
            Hopper<WheelAssembly> assemblyHopper, int assemblyCount, String lineName) {
        this.wheelHopper = wheelHopper;
        this.axleHopper = axleHopper;
        this.assemblyHopper = assemblyHopper;
        this.assemblyCount = assemblyCount;
        this.lineName = lineName;
    }
    
    @Override
    public void run() {
        Axle axle;
        Wheel leftWheel;
        Wheel rightWheel;
        WheelAssembly wheelAssembly;
        while (assemblyCount > 0) {
            assemblyCount--;

            axle = axleHopper.getItem();
            leftWheel = wheelHopper.getItem();
            rightWheel = wheelHopper.getItem();
            wheelAssembly = new WheelAssembly(leftWheel, rightWheel, axle, lineName, LocalDateTime.now());
            System.out.println("Adding wheel assembly: " + wheelAssembly);
            assemblyHopper.addItem(wheelAssembly);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
                // ignore for the moment
            }
        }
    }

}

WheelAssemblyMaker Class

  • The WheelAssemblyMaker class represents a thread. It will simulate an assembly line that puts two wheels on each axle.

  • The class has 5 instance variables:

    • The wheelHopper and axleHopper represent the storage units that contain completed wheels and axles

    • The assemblyHopper represents the storage unit for completed WheelAssembly objects.

    • The assembly Count is the total number of assemblies to be made

    • The lineName is the name of the assembly line

  • A constructor assigns values to all variables.
  • The class implements Runnable, therefore must include a run() method.
    • It will create one WheelAssembly every half second (500 milliseconds) and add each assembly to a Hopper.
    • It will attempt to get axles and wheels every half second -- remember it takes 2 seconds to make a wheel and a half second to make an axle
    • What do you predict will happen?

AssemblyLine Class

  • Complete the main logic of the program by creating a Hopper for WheelAssembly objects and an assembly line for making wheel assemblies.

  • Create a multi-threaded program by instantiating, starting, and joining a Thread for each of our 4 assembly lines

    • The start() method starts a thread by calling its run() method.

    • The join() method waits for specified threads to "join" before the main thread ends.

  • The for loop at the end prints the details of all 20 wheel assemblies

  • When you run the code, notice how some wheel assemblies don't have wheels or axles -- they are null.

import java.util.List;

public class AssemblyLine {

    public static void main(String[] args) {
        Hopper<Wheel> wheels = new Hopper<>();
        WheelMaker wheelMaker1 = new WheelMaker(wheels, 20, "Wheel Production Line 1");
        WheelMaker wheelMaker2 = new WheelMaker(wheels, 20, "Wheel Production Line 2");
        
        Hopper<Axle> axles = new Hopper<>();
        AxleMaker axleMaker = new AxleMaker(axles, 20, "Axle Production Line");
        
        Hopper<WheelAssembly> wheelAssemblies = new Hopper<>();
        WheelAssemblyMaker wheelAssemblyMaker = new WheelAssemblyMaker(wheels, axles, wheelAssemblies, 20, "Assembly Line");

        System.out.println("\nStarting main thread... ");
        try {
            Thread wheel1 = new Thread(wheelMaker1);
            Thread wheel2 = new Thread(wheelMaker2);
            Thread axle1 = new Thread(axleMaker);
            Thread wheelAssemblyThread = new Thread(wheelAssemblyMaker);
            wheel1.start();
            wheel2.start();
            axle1.start();
            wheelAssemblyThread.start();
            wheel1.join();
            wheel2.join();
            axle1.join();
            wheelAssemblyThread.join();
        } catch (InterruptedException ex) {
            System.out.println("Exception: " + ex.getMessage());
        }
        System.out.println("Main thread complete");

        System.out.println("\nPrinting the completed Wheel Assemblies...");
        List<WheelAssembly> assemblies = wheelAssemblies.getList();
        for (WheelAssembly assembly : assemblies) {
            System.out.println("ASSEMBLY:");
            System.out.println("\tSOURCE: " + assembly.getSource());
            System.out.println("\tCREATED ON: " + assembly.getCreatedDate());
            System.out.println("\tAXLE: " + assembly.getAxle());
            System.out.println("\tLEFT WHEEL: " + assembly.getLeftWheel());
            System.out.println("\tRIGHT WHEEL: " + assembly.getRightWheel());
            System.out.println("\t---\n");
        }
        System.out.println("Printing complete.");
    }

}

The run() method

  • Delete all of the code from inside the assemblyCount while loop.

  • The creation of the Axle and Wheel objects cannot be pre-determined, therefore we will need boolean variables to keep track of each part that is needed to make a WheelAssembly.

    • Add this code before the assemblyCount while loop.

  • Inside the loop consider each part to be missing by setting their initial values to null. Assign true to each of the boolean variables keeping track of what parts are needed.

@Override
public void run() {

    Axle axle;
    Wheel leftWheel;
    Wheel rightWheel;
    WheelAssembly wheelAssembly;
    
    boolean needAxle;
    boolean needLeftWheel;
    boolean needRightWheel;
    while (assemblyCount > 0) {
        // clear the variables
        axle = null;
        leftWheel = null;
        rightWheel = null;
        
        // Get the axle
        needAxle = true;
        
        // Get the leftWheel
        needLeftWheel = true;
        
        // Get the rightWheel
        needRightWheel = true;
    
    }
}

The run() method

  • For each part (axle, right wheel, left wheel) that is needed, run a while loop as until that part becomes available.

    • Use the Hopper's .getItem() method to check if a part is available.

      • If it's not available, continue waiting. Delay 1 second (1000 milliseconds) each time you check if one is available.

      • If it is available, state that it was received an change the boolean need variable from true to false.

      • The loop for that part will stop. The remaining part loops will continue.

@Override
public void run() {

    Axle axle;
    Wheel leftWheel;
    Wheel rightWheel;
    WheelAssembly wheelAssembly;
    
    boolean needAxle;
    boolean needLeftWheel;
    boolean needRightWheel;
    while (assemblyCount > 0) {
        // clear the variables
        axle = null;
        leftWheel = null;
        rightWheel = null;
        
        // Get the axle
        needAxle = true;
        while (needAxle) {
            axle = axleHopper.getItem();
            if (null == axle) {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " waiting on an axle."
                );
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {}
            } else {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " got axle."
                );
                needAxle = false;
            }
        }
        
        // Get the leftWheel
        needLeftWheel = true;
        while (needLeftWheel) {
            leftWheel = wheelHopper.getItem();
            if (null == leftWheel) {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " waiting on an left wheel."
                );
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {}
            } else {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " got left wheel."
                );
                needLeftWheel = false;
            }
        }
        
        // Get the rightWheel
        needRightWheel = true;
        while (needRightWheel) {
            rightWheel = wheelHopper.getItem();
            if (null == rightWheel) {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " waiting on an right wheel."
                );
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {}
            } else {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " got right wheel."
                );
                needRightWheel = false;
            }
        }
    
    }
}

The run() method

  • Instantiate the new WheelAssembly after all 3 parts have been received.

  • Add the WheelAssembly to the respective Hopper and reduce the number of assemblies needed.

  • This time when you run the code, notice the messages that say Assembly Line 4 is waiting for parts and that all WheelAssembly objects are complete at the end.

@Override
public void run() {

    Axle axle;
    Wheel leftWheel;
    Wheel rightWheel;
    WheelAssembly wheelAssembly;
    
    boolean needAxle;
    boolean needLeftWheel;
    boolean needRightWheel;
    while (assemblyCount > 0) {
        // clear the variables
        axle = null;
        leftWheel = null;
        rightWheel = null;
        
        // Get the axle
        needAxle = true;
        while (needAxle) {
            axle = axleHopper.getItem();
            if (null == axle) {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " waiting on an axle."
                );
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {}
            } else {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " got axle."
                );
                needAxle = false;
            }
        }
        
        // Get the leftWheel
        needLeftWheel = true;
        while (needLeftWheel) {
            leftWheel = wheelHopper.getItem();
            if (null == leftWheel) {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " waiting on an left wheel."
                );
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {}
            } else {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " got left wheel."
                );
                needLeftWheel = false;
            }
        }
        
        // Get the rightWheel
        needRightWheel = true;
        while (needRightWheel) {
            rightWheel = wheelHopper.getItem();
            if (null == rightWheel) {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " waiting on an right wheel."
                );
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {}
            } else {
                System.out.println(
                        "WheelAssemblyMaker "
                        + lineName
                        + " got right wheel."
                );
                needRightWheel = false;
            }
        }
        wheelAssembly = new WheelAssembly(leftWheel, rightWheel, axle, lineName, LocalDateTime.now());
        System.out.println("Adding wheel assembly: " + wheelAssembly);
        assemblyHopper.addItem(wheelAssembly);
        assemblyCount--;
        try {
            Thread.sleep(500);
        } catch (InterruptedException ie) {
            // ignore for the moment
        }
    
    }
}

The Request Handler

  • The server must be created first. It must be running before the client can request anything from it. Create a new Netbeans project called DemoServer.

  • Create a new class called RequestHandler. The purpose of this class is to take in requests, handle them appropriately, and wait for more requests.

  • More description on the next slide.
public class RequestHandler implements Runnable {

    private Socket socket;

    public RequestHandler(Socket socket) {
        if (null == socket) {
            throw new IllegalArgumentException("Socket cannot be null.");
        }
        this.socket = socket;
    }

    @Override
    public void run() {
        try(
            DataInputStream inputStream = new DataInputStream(socket.getInputStream());
            DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
        ) {

            
        } catch (SocketTimeoutException ste) {
            System.out.println("\tSocket connection timed out: "
                    + ste.getMessage());
        } catch (IOException ioe) {
            System.out.println("\tIO Error: " + ioe.getMessage());
        } catch (Exception ex) {
            System.out.println("\tERROR: " + ex.getMessage());
        }
    }

}

The Request Handler

  • The RequestHandler class represents a Thread.

    • It implements Runnable and includes a run() method.

    • As thread, it can be used with a server to handle multiple concurrent requests

  • This class creates a Socket object, which allows for communication between the server and the client using .getInputStream() and .getOutputStream() methods.

    • The Socket is how the RequestHandler gets the request

    • The constructor will enforce that it receives a Socket by throwing an IllegalArgumentException if the Socket is null.

The Request Handler

  • A Socket object has both an InputStream and OutputStream, both of which assist with the transfer of binary data.

  • The run() method will get these streams from the Socket and wrap them in helper objects to assist in working with the data

  • Both DataInputStream and DataOutputStream are AutoCloseable, so they can be used in a try-with-resources statement.

  • We can respond to bad data by throwing an Exception.

    • A SocketTimeoutException can be thrown to signal that the result cannot be processed within a certain amount of time

    • An IOException can be thrown by failed input/output operations.

The Request Handler

  • For demonstration purposes, we can display the IP Address of the client making the request using Socket's .getInetAddress() to return a InetAddress object.

  • Then we can use the InetAddress .getHostAddress() method to get the IP address string.

  • This code gets the address of the connection and prints it to the server-side console.

    • System.out.print statements will only display for illustrative or debugging purposes. It is not necessary for the functionality.

public class RequestHandler implements Runnable {

    private Socket socket;

    public RequestHandler(Socket socket) {
        if (null == socket) {
            throw new IllegalArgumentException("Socket cannot be null.");
        }
        this.socket = socket;
    }

    @Override
    public void run() {
        try(
            DataInputStream inputStream = new DataInputStream(socket.getInputStream());
            DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
        ) {

            InetAddress inetAddress = socket.getInetAddress();
            String clientAddress = inetAddress.getHostAddress();
            System.out.println("Connection from " + clientAddress);
            
        } catch (SocketTimeoutException ste) {
            System.out.println("\tSocket connection timed out: "
                    + ste.getMessage());
        } catch (IOException ioe) {
            System.out.println("\tIO Error: " + ioe.getMessage());
        } catch (Exception ex) {
            System.out.println("\tERROR: " + ex.getMessage());
        }
    }

}

The Request Handler

  • Next, we read the incoming values using an appropriate DataInputStream read method.

    • The .readDouble() method reads the binary data and converts it into a Java double value. An exception will be thrown if it cannot read it.

  • Then we perform operations to interpret and modify the input.

    • The client will supply the radius of the circle.

    • Any System.out.print statement will be visible only on the server side. Typically used for debugging.

    • In a production environment, you will not print to the console. You may print to a log file.

  • Then we write the respond to the client with values using an appropriate DataOutputStream write method, such as writeDouble().

public class RequestHandler implements Runnable {

    private Socket socket;

    public RequestHandler(Socket socket) {
        if (null == socket) {
            throw new IllegalArgumentException("Socket cannot be null.");
        }
        this.socket = socket;
    }

    @Override
    public void run() {
        try(
            DataInputStream inputStream = new DataInputStream(socket.getInputStream());
            DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
        ) {

            InetAddress inetAddress = socket.getInetAddress();
            String clientAddress = inetAddress.getHostAddress();
            System.out.println("Connection from " + clientAddress);
            
            double radius = inputStream.readDouble();
            double area = radius * radius * Math.PI;
            System.out.printf("\tRadius: %10.5f Area: %10.5f\n",
                     radius, area);
                     
            outputStream.writeDouble(area);
            
        } catch (SocketTimeoutException ste) {
            System.out.println("\tSocket connection timed out: "
                    + ste.getMessage());
        } catch (IOException ioe) {
            System.out.println("\tIO Error: " + ioe.getMessage());
        } catch (Exception ex) {
            System.out.println("\tERROR: " + ex.getMessage());
        }
    }

}

The Request Handler

  • The writeDouble() will first write data to a buffer. Java will only send the data when it has enough buffer contents or it has waited long enough.
    • If the program ends while there is still data in the buffer, the data will be lost
  • To force Java to send any data in the buffer immediately we use the DataOutputStream .flush() method.
    • Include this code after you write to any OutputStream.
public class RequestHandler implements Runnable {

    private Socket socket;

    public RequestHandler(Socket socket) {
        if (null == socket) {
            throw new IllegalArgumentException("Socket cannot be null.");
        }
        this.socket = socket;
    }

    @Override
    public void run() {
        try(
            DataInputStream inputStream = new DataInputStream(socket.getInputStream());
            DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
        ) {

            InetAddress inetAddress = socket.getInetAddress();
            String clientAddress = inetAddress.getHostAddress();
            System.out.println("Connection from " + clientAddress);
            
            double radius = inputStream.readDouble();
            double area = radius * radius * Math.PI;
            System.out.printf("\tRadius: %10.5f Area: %10.5f\n",
                     radius, area);
                     
            outputStream.writeDouble(area);
            outputStream.flush();
            
        } catch (SocketTimeoutException ste) {
            System.out.println("\tSocket connection timed out: "
                    + ste.getMessage());
        } catch (IOException ioe) {
            System.out.println("\tIO Error: " + ioe.getMessage());
        } catch (Exception ex) {
            System.out.println("\tERROR: " + ex.getMessage());
        }
    }

}

The Server

  • Create a main class called "Server". The code that goes in this class will act as the main thread. The RequestHandler class will be used for multiple child threads.

  • A production server will read in startup values from a configuration file. Our demo server will read in startup value from hard-coded variables.

    • Port number: Each software is identified with a port number.

    • Thread count: The maximum number of RequestHandlers (Threads) that can run at the same time)

    • Timeout length: The length of time, in milliseconds, before a connection times out if an error occurs.

public class CircleAreaServer {

    public static void main(String[] args) {

        int port = 5555;
        int threadCount = 100;
        int timeoutLength = 3000;

    }

}

The Server

  • Java's Executors class has a method called newFixedThreadPool() that takes a number as input and creates a fixed number of threads. The result is an ExecutorService object.

    • This class has many factory methods that return implementations of ExecutorService, among others.

    • In this case, the server requests a thread pool with a fixed number of threads.

  • Java's ExecutorService interface can be used to hold and manage a large number of threads concurrently.

    • This interface will eventually be used to check the pool of threads to see if one is available.

public class CircleAreaServer {

    public static void main(String[] args) {

        int port = 5555;
        int threadCount = 100;
        int timeoutLength = 3000;

        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

    }

}

The Server

  • Java's ServerSocket class is used to wait and listen for requests over the network.

    • We instantiate a ServerSocket inside a try block.

    • The constructor requires a port number.

    • If the system refuses to allow our server program to use the requested port, the server will catch an IOException and will end.

public class CircleAreaServer {

    public static void main(String[] args) {

        int port = 5555;
        int threadCount = 100;
        int timeoutLength = 3000;

        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

        try{
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.printf("ServerSocket listening on port %d\n", port);

            

        } catch (IOException ex) {
            System.out.println("CATASTROPHIC FAILURE:  Server Stopping!");
            System.out.println(ex.getMessage());
        }
    }

}

The Server

  • The ServerSocket's .accept() method returns a Socket object, which will be used to communicate with the client.

    • The infinite loop is used so the server's main thread is always listening and and waiting for a connection.

    • accept() is a blocking method, meaning that the server cannot continue until it gets a connection.

    • Once received, we set the timeout to 3 seconds (3000 milliseconds).

public class CircleAreaServer {

    public static void main(String[] args) {

        int port = 5555;
        int threadCount = 100;
        int timeoutLength = 3000;

        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

        try{
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.printf("ServerSocket listening on port %d\n", port);

            Socket socket;
            while(true){
                socket = serverSocket.accept();
                socket.setSoTimeout(timeoutLength);
                
            }

        } catch (IOException ex) {
            System.out.println("CATASTROPHIC FAILURE:  Server Stopping!");
            System.out.println(ex.getMessage());
        }
    }

}

The Server

  • The Socket is then used to instantiate a Runnable object (a RequestHandler)

  • The ExecutorService has an .execute() method which takes a Runnable object as an argument.

    • execute() will check the pool of threads to see if one is available.

      • If there is, the Runnable is started. The thread will run.

      • If not, the Runnable will wait in a Queue until a Thread is available.

    • When finished, the thread will rejoin the thread pool.

    • Threads can then be reused

public class CircleAreaServer {

    public static void main(String[] args) {

        int port = 5555;
        int threadCount = 100;
        int timeoutLength = 3000;

        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

        try{
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.printf("ServerSocket listening on port %d\n", port);

            Socket socket;
            while(true){
                socket = serverSocket.accept();
                socket.setSoTimeout(timeoutLength);
                executorService.execute(new RequestHandler(socket));
            }

        } catch (IOException ex) {
            System.out.println("CATASTROPHIC FAILURE:  Server Stopping!");
            System.out.println(ex.getMessage());
        }
    }

}

The Infinite Loop

  • Once the request is handled, the loop returns to the top to listen for the next incoming request.

  • On a production server you wouldn't want to use an infinite loop without a condition that can cause the loop to exit.

    • We are using an infinite loop to keep the example program simple.

    • A production server will have more than one port open:

      • A primary port is associated with the server

      • A secondary port is used for control signals

        • A management client can send control signals to the server through the secondary port to alter the server's behavior, such as "stop" or "shut down".

The Client

  • Create a new Netbeans project called "DemoClient". Create a main class called "CircleAreaClient".

    • This client program will interact with the user.

  • This code in the main method simply asks the user to enter a decimal number for the radius of a circle or type the letter "Q" to quit.

public class CircleAreaClient {
    
    private static String getUserInput(String prompt){
        System.out.print(prompt + " ");
        return new Scanner(System.in).nextLine();
    }
    
    public static void main(String[] args) {

        String response;
        String prompt = "Enter the radius of a circle or Q to quit:";
        boolean keepGoing = true;
        while(keepGoing){
            response = getUserInput(prompt);
            if(response.equalsIgnoreCase("Q")){
                keepGoing = false;
            } else {
                try{
                    double radius = Double.parseDouble(response);
                    
                } catch(NumberFormatException nfe){
                    System.out.printf("ERROR: [%s] is not a number!\n", response);
                    System.out.println("Please try again.\n");
                }
            }
        }
        System.out.println("\nProgram complete.");
    }

}

The Client

  • To get the area from the circle, we create a helper method that instantiates a Socket using the server's known address and port.
  • Next, the program gets the InputStream and OutputStream in the same manner as the RequestHandler on the server.
    • The OutputStream takes a radius and sends it to the server. The .flush() method forces the buffer to send the data.
    • The InputStream receives the area, converting it to a double.
  • Since this code isn't using a try-with-resources statement, we need to manually close the streams.
  • Upon accepting a valid double input, the client will contact the server requesting the area of a circle.

    • The print statement will print the area for the user to see.

public class CircleAreaClient {

    private static final int PORT = 5555;
    private static final String HOST_NAME = "localhost";
    
    private static double getAreaFromServer(double radius) throws UnknownHostException, IOException {
        Socket socket = new Socket(HOST_NAME, PORT);
        DataInputStream inputStream = new DataInputStream(socket.getInputStream());
        DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
        outputStream.writeDouble(radius);
        outputStream.flush();
        double area = inputStream.readDouble();
        inputStream.close();
        outputStream.close();
        return area;
    }
    
    private static String getUserInput(String prompt){
        System.out.print(prompt + " ");
        return new Scanner(System.in).nextLine();
    }
    
    public static void main(String[] args) {

        String response;
        String prompt = "Enter the radius of a circle or Q to quit:";
        boolean keepGoing = true;
        while(keepGoing){
            response = getUserInput(prompt);
            if(response.equalsIgnoreCase("Q")){
                keepGoing = false;
            } else {
                try{
                    double radius = Double.parseDouble(response);
                    try{
                        double area = getAreaFromServer(radius);
                        System.out.printf("The area from the server is %f\n", area);
                    } catch(UnknownHostException uhe){
                        System.out.println("ERROR: " + uhe.getMessage());
                        System.out.println("Program terminating!");
                        System.exit(-1);
                    } catch(IOException ioe) {
                        System.out.println("ERROR: " + ioe.getMessage());
                    }
                } catch(NumberFormatException nfe){
                    System.out.printf("ERROR: [%s] is not a number!\n", response);
                    System.out.println("Please try again.\n");
                }
            }
        }
        System.out.println("\nProgram complete.");
    }

}

Conclusion

  • Start the server first, then run the client. Enter a radius on the client-side. Read the output on both the client and server-side.
  • A server can handle requests from multiple clients: a web page, a mobile app, an internet-of-things (IoT) device.
    • Client programs will communicate with the same server.
    • If I'm playing a video game on my phone you could be playing the same game on a PC, XBox or Playstation device at the same time.
    • Understanding client-server relationships is important in understanding cross-platform play.
  • The client will focus on user interaction
  • The server will focus on the most critical and secure parts of the application, such as handling data input and output.

Network Vocab

Term Definition
Application Data This is data used by an application.
Application Layer (OSI Layer 7) In the OSI model, this layer is responsible for providing a consistent means for applications running on a host to utilize the networking layer implementations on that host.
Client AS client is a system, either hardware or software, that makes use of a service made available by a server program.
Data Link Layer (OSI Layer 2) In the OSI model, this layer is responsible for establishing and maintaining the connections between two nodes on the network.
Datagram A datagram is a grouping of data including a header and payload and ids used for transmitting the data across a network. The header contains data such as who it is from, who it is to, and the size of the payload. When data is broken into multiple pieces for transmission, the header will also contain the data needed to put the pieces back together after they have been received.
Domain Name A human-readable representation of an IP address. It is a unique identifier and is organized into sub-domains, starting with a root domain, such as "com".
Domain Name System (DNS) This is a decentralized, hierarchical system of computers that connects domain names to related resources, such as the IP address of the target system and the name of the owner.
Host The host is the computer on which server software, and other software, runs.
Hypertext Transfer Protocol (HTTP) HTTP is an application protocol for connecting hypermedia (sites and pages connected by hyperlinks).
Internet Access Point An Internet access point is a connection that ISPs connect to to make the Internet completely connected. Without these, each ISP would have to maintains a separate network and users could only reach servers also connected to the ISP.
Internet Protocol (IP) A series of functions that manage putting data into and taking it out of datagrams.

Network Vocab

Term Definition
IP Address The IP address is a numerical address representing a machine on a network. If you know the IP address, you should be able to contact that machine, though it may choose to refuse your contact. Most are in IPv4 format, made up of four (4) octets with periods between them (i.e.: 209.56.159.13). The new version is IPv6 made up of eight (8) groups of four (4) hexadecimal numbers (i.e.: FE80::0202:B3FF:FE1E:8329). Note that in IPv6, two colons together (::) indicate that the value between them is zero.
Internet Message Access Protocol (IMAP) The Internet Message Access Protocol in an Application Level Protocol that allows email clients to work with email records on an email server.
Internet Service Provider (ISP) An Internet Service Provider is a company that provides Internet access to individuals and organizations that would not have their own Internet access point and related technology. Many ISPs offer other services such as email.
Network Layer (OSI Layer 3) In the OSI model, this layer is responsible for packet forwarding (sending packets) and routing.
Open Systems Interconnection Model (OSI Model) An abstract model of the functions needed for communication in computing systems, consisting of seven (7) layers, each with its own function. This model is independent of underlying hardware on which it is implemented.
Packet A packet is a collection of data in some protocols that includes control information and other data.
Physical Layer (OSI Layer 1) In the OSI model, this layer is responsible for the physical transmission and reception of data, often through wires or radio transmissions.
Port In networking, ports are the endpoints for communication. There are physical ports which usually exchange data as a series of electrical impulses. The computer systems will also have software representations of ports that are used as part of the communication protocols. One physical port can be affiliated with multiple software ports with each software port identified with a port number.

Network Vocab

Term Definition
Port Number The port number is a number assigned to the software representation of a port so that each such port can be uniquely identified. The port number is often appended to the end of an IP address so that responses can be directed to the correct port. This is done by placing a colon and then the port number after the IP address but before any other information: 127.0.0.1:88
Post Office Protocol (POP) The post office protocol is an application layer protocol used to retrieve email from an email server.
Presentation Layer (OSI Layer 6) An abstract model of the functions needed for translating between the different formats used by the networking processes and the application. This has nothing to do with presenting to the end user!
Server A server is software (not hardware) that listens for and responds to requests from other software. Many people, in and out of industry, will colloquially refer to the host computer as "the server", so this is considered acceptable in casual conversation. An I. T. professional should know the difference though.
ServerSocket In Java network programming, the ServerSocket class is the class that listens for incoming requests for services. It is connected to a specific port so it knows where to listen. When a request comes in, the request is connected to a Socket object that can then be used to handle the request.
Session Layer (OSI Layer 5) An abstract model of the functions needed for maintaining communication between node between data read/writes. This maintained contact is called a session.
Simple Mail Transfer Protocol (SMTP) The Simple Mail Transfer Protocol is a standard for sending and receiving emails, though many email clients will only use it for sending.
Socket A socket is an internal, software representation of the endpoint for sending and receiving data. Sockets are bound to ports.

Network Vocab

Term Definition
TCP/IP TCP/IP is a common reference to the combined use of Transmission Control Protocol (TCP) and Internet Protocol (IP).
Tim Berners-Lee Sir Timothy John Berners-Lee is the computer scientist known to be the inventor of the World Wide Web (not the Internet). His proposal included HTTP in order to link related documents on different computers all over the world. Now you can see videos of cats doing adorable stuff from the comfort of your bathroom any time you want. If computing had saints, he would be one of them.
Transmission Control Protocol (TCP) The Transmission Control Protocol is an Internet protocol that provides for ordered and error-checked transmission of data between applications.
Transport Layer (OSI Layer 4) An abstract model of the functions needed for moving data to and from application processes on the host computer. It adds ports to the header of data segments headed out.
Universal Resource Identifier (URI) The Universal Resource Identifier is a String that uniquely identifies a resource, typically over a network. Using a shared identifier can simplify communication between applications spread across the network since they can simply refer to an item by name.
Universal Resource Locator (URL) The Universal Resource Locator is a type of URI that specifies the location on a network of a resource. This is often referred to as a web address.
Web Address The common name for URLs.

Widget Starter Code

  • Create a class called Widget. Add it to an appropriate package.

  • Use this code instead of the one on Talon.

  • I removed JavaDoc comments, the toString method, and compareTo method. They're not needed.

public class Widget {
    private int value;
    private String name;

    public Widget(int value, String name) {
        this.value = value;
        this.name = name;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Main Starter Code

  • Create a Main class in the same package as your Widget class.

  • Copy and paste this code. Complete one step at a time as you read the instructions on Talon.

public class Main {
    
    // STEP 5A: Do a Google search for how to generate a String of random letters.
        // Write that code in a separate method here. Cite your source in a comment.
        // Be sure the String is returned in all capital letters.
    // End the method here
    
    public static void main(String[] args) {
        // STEP 1: instantitate an ArrayList of Widgets here
        
        // STEP 3: Create a Supplier Functional Interface. It will take no arguments and return a Widget.
            // STEP 4: Assign a random int value from -10 to 90 to a variable.
            // STEP 5B: Call the random string method here and assign the returned value to a variable. 
            // STEP 6: Instantiate a new Widget object and return it.
        // End the Supplier here
        
        // STEP 8: Create a Predicate Functional Interface to test if a Widget's value is zero or greater here.
            // This will take a single Widget as input, use the .getValue() method, and return a boolean.
            // This code should be written on a single line.
            
        // STEP 10: Create a UnaryOperator Functional Interface to convert the Widget name to all lowercase letters here.
            // This will take a single Widget as input, use both the .getName() and .setName() methods, and return the updated Widget.
            // This code should be written on a single line.
        
        // STEP 13: Create a Consumer Functional Interface to print each Widget as a string on a new line.
            // This will take a single widget as input, use the .getValue() and .getName() methods, and print each Widget.
            // This code should be written on a single line.
            
        // STEP 2: Create a loop that runs 20 times
            // STEP 7: Use the Supplier .get() method to instantitate a Widget object
            // STEP 9: Write an if statement that uses the Predicate .test() method as the condition.
                // STEP 11: If true, use the UnaryOperator .apply() method. Take the resulting Widget and add it to the ArrayList.
            // End the if statement here
        // End the loop here
                
        // STEP 12: Use a .forEach() method to iterate through each widget in the ArrayList.
            // STEP 14: Use the Consumer .accept() method.
        
    }
    
}

Java Week 10 - Multithreading and Java

By Marc Hauschildt

Java Week 10 - Multithreading and Java

  • 5,022