1. What is a design pattern
In software engineering, design pattern is a solution proposed to various common (recurring) problems in software design. This term was introduced into computer science from the field of architectural design in the 1990s by Erich Gamma and others.
Famous 4-person Gang: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (Gof)
Design Pattern: The Basics of Reusable Object-Oriented Software
2. Singleton mode
The class of a singleton object must be guaranteed to have only one instance exist. Many times the entire system only needs to have one global object, which is conducive to our coordination of the overall behavior of the system.
For example: global information configuration
The simplest implementation of singleton mode:
public class Singleton { private Singleton() { System.out.println("Singleton is create"); } private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; }} The uniqueness is determined by the private constructor and static.
Disadvantages: When an instance is generated is difficult to control
Although we know that when the class Singleton is first loaded, an instance is generated.
But if there are other properties in this class
public class Singleton { public static int STATUS=1; private Singleton() { System.out.println("Singleton is create"); } private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; }} When using
System.out.println(Singleton.STATUS);
This example is produced. Maybe you don't want this instance to be generated at this time.
If the system pays special attention to this issue, the implementation method of this singleton is not very good.
The solution to the second singleton mode:
public class Singleton { private Singleton() { System.out.println("Singleton is create"); } private static Singleton instance = null; public static synchronized Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance; }} Let instance be created only when the getInstance() method is called, and thread safety is ensured through synchronized.
This controls when instances are created.
This approach is typical of lazy loading.
But one problem is that performance will have an impact in high concurrency scenarios. Although it will be returned with only one judgment, it will have an impact in the case of high concurrency, because you have to get the synchronized lock.
In order to be efficient, there is a third method:
public class StaticSingleton { private StaticSingleton(){ System.out.println("StaticSingleton is create"); } private static class SingletonHolder { private static StaticSingleton instance = new StaticSingleton(); } public static StaticSingleton getInstance() { return SingletonHolder.instance; }} Since when a class is loaded, its inner class will not be loaded. This ensures that the instance will be generated only when getInstance() is called, the time of generating the instance is controlled, and the delayed loading is achieved.
And synchronized is removed to make performance better and static is used to ensure uniqueness.
3. Invariant mode
After the internal state of a class is created, it will not change during the entire life period.
Unchange mode does not require synchronization
Create an unchanged class:
public final class Product { // Ensure that there is no subclass private final String no; // Private attributes will not be obtained by other objects private final String name; // Final guarantees that the attribute will not be assigned twice private double price; public Product(String no, String name, double price) { // When creating an object, the data must be specified super(); // Because after creation, it cannot be modified this.no = no; this.name = name; this.price = price; } public String getNo() { return no; } public String getName() { return name; } public double getPrice() { return price; }} Cases of unchanging patterns in Java include:
java.lang.String
java.lang.Boolean
java.lang.Byte
java.lang.Character
java.lang.Double
java.lang.Float
java.lang.Integer
java.lang.Long
java.lang.Short
4. Future mode
The core idea is asynchronous calls
Non-asynchronous:
asynchronous:
The first call_return is returned as the task has not been completed yet.
But this returns are similar to an order in shopping, and you can get a result based on this order in the future.
So this Future model means that "future" can be obtained, which means that the order or contract is the "promise" and will give the result in the future.
Simple implementation of Future mode:
What the caller gets is a Data, which may be a FutureData at the beginning, because RealData is slow to build. At some time in the future, RealData can be obtained through FutureData.
Code implementation:
public interface Data { public String getResult (); }public class FutureData implements Data { protected RealData realdata = null; //FutureData is a RealData wrapper protected boolean isReady = false; public synchronized void setRealData(RealData realdata) { if (isReady) { return; } this.realdata = realdata; isReady = true; notifyAll(); //RealData has been injected, notify getResult() } public synchronized String getResult()//Will wait for the RealData construction to complete { while (!isReady) { try { wait(); //Wait all the time to know that RealData is injected} catch (InterruptedException e) { } } return realdata.result; //Implemented by RealData} } public class RealData implements Data { protected final String result; public RealData(String para) { // The construction of RealData may be very slow and requires the user to wait for a long time. Here we use sleep to simulate StringBuffer sb = new StringBuffer(); for (int i = 0; i < 10; i++) { sb.append(para); try { // Use sleep here instead of a very slow operation Thread.sleep(100); } catch (InterruptedException e) { } } result = sb.toString(); } public String getResult() { return result; }}public class Client { public Data request(final String queryStr) { final FutureData future = new FutureData(); new Thread() { public void run() { // RealData is very slow to build, // So RealData in a separate thread realdata = new RealData(queryStr); future.setRealData(realdata); } } }.start(); return future; // FutureData will be returned immediately} } public static void main(String[] args) { Client client = new Client(); // This will be returned immediately because what you get is FutureData instead of RealData Data data = client.request("name"); System.out.println("Request completed"); try { // Here you can use a sleep instead of processing other business logics // In the process of processing these business logic, RealData is created, making full use of the waiting time Thread.sleep(2000); } catch (InterruptedException e) { } // Use real data System.out.println("Data= " + data.getResult()); }There are also many Future mode support in JDK:
Next, use the classes and methods provided by JDK to implement the code just now:
import java.util.concurrent.Callable;public class RealData implements Callable<String> { private String para; public RealData(String para) { this.para = para; } @Override public String call() throws Exception { StringBuffer sb = new StringBuffer(); for (int i = 0; i < 10; i++) { sb.append(para); try { Thread.sleep(100); } catch (InterruptedException e) { } } return sb.toString(); }} import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.FutureTask;public class FutureMain { public static void main(String[] args) throws InterruptedException, ExecutionException { // Construct FutureTask FutureTask<String> future = new FutureTask<String>(new RealData("a")); ExecutorService executor = Executors.newFixedThreadPool(1); // Execute FutureTask, equivalent to client.request("a") in the above example Send a request // Enable the thread here to perform RealData call() and execute executor.submit(future); System.out.println("Request completed"); try { // Additional data operations can still be done here, and sleep can be used instead of processing other business logic Thread.sleep(2000); } catch (InterruptedException e) { } // equivalent to data.getResult (), get the return value of the call() method // If the call() method is not executed at this time, it will still wait for System.out.println("Data= " + future.get()); }} What you need to note here is that FutureTask is a class that has Future function and Runnable function. So it can run again, and finally get it.
Of course, if the real data is not ready when calling future.get(), it will still cause a blocking situation until the data is ready.
Of course there are easier ways:
import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class FutureMain2 { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newFixedThreadPool(1); // Execute FutureTask, equivalent to client.request("a") in the above example Send a request // Open the thread here to perform RealData call() and execute Future<String> future = executor.submit(new RealData("a")); System.out.println("Request completed"); try { // Additional data operations can still be done here, use sleep instead of other business logic processing Thread.sleep(2000); } catch (InterruptedException e) { } // equivalent to data.getResult (), get the return value of the call() method // If the call() method does not execute at this time, System.out.println will still be waiting for System.out.println("Data= " + future.get()); }} Since Callable has a return value, you can directly return the future object.
5. Producer and Consumer
The producer-consumer model is a classic multi-threaded design model. It provides a good solution for collaboration between multiple threads. In the producer-consumer model, there are usually two types of threads, namely several producer threads and several consumer threads. The producer thread is responsible for submitting user requests, while the consumer thread is responsible for specifically handling the tasks submitted by the producer. The producer and the consumer communicate through a shared memory buffer.
I have written an article in the past to implement various methods of using Java to implement producers and consumers, so I won't explain it here.