Before entering the Practice section, let's briefly introduce the general principles of multi-threaded programming.
[Security]Is the primary principle of multi-threaded programming. If more than two threads access the same object, one thread will corrupt the data of the other thread. This is in violation of the security principle.ProgramYou cannot enter the actual application.
Security assurance can be ensured by designing secure classes and manually controlling programmers. If multiple threads access the same object without compromising security, such classes are thread-safe classes. In Java, for example, the string class is designed as a thread-safe class. If it is not a thread-safe class, the programmer needs to manually control its security when accessing the instances of these classes.
[Feasibility]Is another important principle of multi-threaded programming. If only security is achieved, the program cannot continue to run after a certain point or multiple threads have deadlocks, such a program cannot be used as a real multi-threaded program.
The security and feasibility are relatively conflicting. The higher the security, the lower the program's availability. Comprehensive balance is required.
[High Performance]The purpose of Multithreading is to increase the program running performance. If a multi-thread completes the work, it is not as fast as that of a single thread. Do not use multiple threads.
High-performance programs have the following factors:
Data throughputThe processing capability that can be completed within a certain period of time.
Response Speed, From the time when the request is sent to the time when the response is received.
Capacity, Refers to the number of processed elegant jobs at the same time.
Security and feasibility are necessary. If the two principles are not met, it cannot be called a real multi-threaded program. High performance is the purpose of multi-threaded programming. It can be said that it is a sufficient condition. Otherwise, why is multi-threaded programming used?
[Producer and consumer model]
First, enter the first section of the actual practice with a producer and consumer model.
Who is protected in the producer and consumer models?
Multi-threaded programming is protecting some objects. These objects are "tight resources" and must be used to the maximum extent. This is also the reason for adopting multithreading. In the producer-consumer model, we want to protect the "warehouse". In my example,
It is a table ).
The Mode in my example is completely the producer-consumer mode, but I changed the name. Chef-diners mode. There is only one table in the dining room and a maximum of 10 dishes can be placed. Now there are four chefs cooking, every time you make a good disk, you can put it on the table (the producer puts the product in the warehouse), while 6 diners keep eating (the Consumer consumes the product to illustrate the problem, their food intake is unlimited ).
In general, the cook made a dish in-MS, while the diners had to eat a dish in-MS. When there are ten dishes on the table, no chefs can put them on the table. When there is no plate on the table, all diners have to wait.
We will design this program as follows:
Because we don't know what it is, we call it food:
Class food {}
Then there is the table, because it needs to be put in order and retrieved in order (not two diners can win a third dish at the same time), so we expand the orders list, or you can use aggregation to set an aggregate list as an attribute for the same purpose. In this example, I use
Inheritance: input a maximum value that can be placed from the constructor.
Class table extends java. util. shortlist {int maxsize; Public table (INT maxsize) {This. maxsize = maxsize ;}}
Now we need to add two methods for it. One is how the cook puts food on it, and the other is how the diners take food from the table.
Food: Because a table is served by multiple chefs, the food to be served by the chefs should be synchronized, if there are already ten dishes on the table. All chefs have to wait:
Public synchronized void putfood (Food f) {While (this. size ()> = This. maxsize) {try {This. wait () ;}catch (exception e) {}} this. add (f); policyall ();}
Take food: Same as above. If there is no dish on the table, all diners will wait:
Public synchronized food getfood () {While (this. size () <= 0) {try {This. wait () ;}catch (exception e) {}} food F = (food) This. removefirst (); policyall (); Return F ;}
Chefs:
Since multiple chefs want to put food on one table, the tables they want to operate should be the same object, we passed in the table object from the constructor so that only one table is generated in the main thread.
When a cook is cooking for a certain period of time, I use sleep in the Make method to indicate that he will consume and use a random number of 200 plus 200 to ensure that the time is-MS. Put it on the table after you finish it.
There is a very important issue that must be noted here, that is, the scope of synchronization. Because the competition is a table, all putfood is synchronized, however, we cannot synchronize the time for the chefs to cook their own dishes, because they are made by themselves. Similarly, diners should not synchronize food, but compete only when taking food from the table. Therefore, chefsCodeAs follows:
Class Chef extends thread {table t; random r = new random (12345); Public chef (Table t) {This. T = T;} public void run () {While (true) {food F = make (); T. putfood (f) ;}} Private food make () {try {thread. sleep (200 + R. nextint (200);} catch (exception e) {} return new food ();}}
Similarly, the Code for generating the diners class is as follows:
Class eater extends thread {table t; random r = new random (54321); Public eater (Table t) {This. T = T;} public void run () {While (true) {food F = T. getfood (); eat (f) ;}} private void eat (Food f) {try {thread. sleep (400 + R. nextint (200);} catch (exception e ){}}}
The complete program is here:
Package debug; import Java. util. regEx. *; import Java. util. *; Class food {} class Table extends list {int maxsize; Public table (INT maxsize) {This. maxsize = maxsize;} public synchronized void putfood (Food f) {While (this. size ()> = This. maxsize) {try {This. wait () ;}catch (exception e) {}} this. add (f); policyall ();} public synchronized food getfood () {While (this. size () <= 0) {try {This. wait () ;}catch (exception e) {}} food F = (food) This. removefirst (); policyall (); Return F ;}} class Chef extends thread {table t; string name; random r = new random (12345); Public chef (string name, table t) {This. T = T; this. name = Name;} public void run () {While (true) {food F = make (); system. out. println (name + "put a food:" + F); T. putfood (f) ;}} Private food make () {try {thread. sleep (200 + R. nextint (200);} catch (exception e) {} return new food () ;}} class eater extends thread {table t; string name; random r = new random (54321); Public eater (string name, table t) {This. T = T; this. name = Name;} public void run () {While (true) {food F = T. getfood (); system. out. println (name + "get a food:" + F); eat (f) ;}} private void eat (Food f) {try {thread. sleep (400 + R. nextint (200);} catch (exception e) {}} public class test {public static void main (string [] ARGs) throws exception {table t = new table (10); new chef ("chef1", t ). start (); new chef ("chef2", t ). start (); new chef ("chef3", t ). start (); new chef ("chef4", t ). start (); New eater ("eater1", t ). start (); New eater ("eater2", t ). start (); New eater ("eater3", t ). start (); New eater ("eater4", t ). start (); New eater ("eater5", t ). start (); New eater ("eater6", t ). start ();}}
In this example, we mainly focus on the following aspects:
1. objects to be protected by the synchronization method. In this example, the table is protected. You cannot add or take dishes at the same time.
If we implement the putfood and getfood methods in the chefs and diners, we should do this:
(Take putfood as an example)
Class Chef extends thread {table t; string name; Public chef (string name, table t) {This. T = T; this. name = Name;} public void run () {While (true) {food F = make (); system. out. println (name + "put a food:" + F); putfood (f) ;}} Private food make () {random r = new random (200); try {thread. sleep (200 + R. nextint ();} catch (exception e) {} return new food ();} public void putfood (Food f) {// the method itself cannot be synchronized, because it synchronizes this. that is, the chef instance synchronized (t) {// The instance to be protected is t while (T. size ()> = T. maxsize) {try {T. wait () ;}catch (exception e) {}} T. add (f); T. policyall ();}}}
2. The scope of synchronization is to put and take two methods in this example. The irrelevant work of cooking and food cannot be put in the protected scope.
3. Participants-Volume Ratio
The relationship between the proportion of producers and consumers and the maximum number of dishes that can be placed on a table is an important factor affecting performance. If too many producers are waiting, increase or decrease the number of consumers. Otherwise, increase or decrease the number of consumers.
In addition, if the table has enough capacity, it can greatly improve the program performance. In this case, it can increase the number of producers and consumers at the same time, but when you have enough capacity, you often need enough physical memory.
The go deep into Java column is reprinted from axman, A dev2dev user.