Write before
- Write down the technical concepts in the form of words, clarify the logic and deepen the understanding;
- Write down the knowledge points in the form of a series of articles, and let your mind train deliberately.
- Bring life to technology by telling interesting stories or examples of things that are difficult to understand.
Case of movie tickets
Example of a single thread
We set up a movie theater that opened shortly after the entrance. There was only a ticket point A next to the entrance. Customers watch a movie and need to queue up at the ticket point to buy tickets in turn. After buying the ticket, they check in at the entrance to the movie theater.
The above description is implemented in code and can be as follows:
1. First, set up a category of movie tickets: the main attributes are the ID of the ticket, which showroom, which row and column, the name of the movie being shown, the time of showing and the ticket price.
/** * Understand Multithreaded through Ticket Selling Program--Class of Tickets * * @author zhuhuix * @date 2020-05-12 */ public class Ticket { //id private int ticketId; //Projection Hall private String room; //That's ok private Integer row; //column private Integer col; //Movie Title private String filmName; //Price private BigDecimal price; //Show time private LocalDateTime datetime; private Ticket(){ } public Ticket(int ticketId,String room, Integer row, Integer col, String filmName, BigDecimal price, LocalDateTime datetime) { this.ticketId = ticketId; this.room = room; this.row = row; this.col = col; this.filmName = filmName; this.price = price; this.datetime = datetime; } public int getTicketId() { return ticketId; } public void setTicketId(int ticketId) { this.ticketId = ticketId; } public String getRoom() { return room; } public void setRoom(String room) { this.room = room; } public Integer getRow() { return row; } public void setRow(Integer row) { this.row = row; } public Integer getCol() { return col; } public void setCol(Integer col) { this.col = col; } public String getFilmName() { return filmName; } public void setFilmName(String filmName) { this.filmName = filmName; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public LocalDateTime getDatetime() { return datetime; } public void setDatetime(LocalDateTime datetime) { this.datetime = datetime; } @Override public String toString() { return "Ticket{" + "ticketId=" + ticketId + ", room='" + room + '\'' + ", row=" + row + ", col=" + col + ", filmName='" + filmName + '\'' + ", price=" + price + ", datetime=" + datetime + '}'; } }
2. Second, establish a customer class: the main attributes are ticket ID, movie tickets purchased, and membership methods are ticket purchases.
/** * Understand Multi-threading through Ticket Selling Program--Customer Class * * @author zhuhuix * @date 2020-05-12 */ public class Customer { //Customer id private int customerId; //Film tickets purchased private Ticket ticket; public Customer(int customerId) { this.customerId = customerId; } //Customer Buy Ticket public void buyTicket(Ticket ticket) { this.ticket = ticket; } public int getCustomerId() { return customerId; } public void setCustomerId(int customerId) { this.customerId = customerId; } public Ticket getTicket() { return ticket; } public void setTicket(Ticket ticket) { this.ticket = ticket; } @Override public String toString() { return "Customer{" + "customerId=" + customerId + ", ticket=" + ticket.toString() + '}'; } }
3. Finally, write a main program to generate a list of movie tickets, set the number of customers who come to see the movie, buy tickets in turn, and output the status of ticket purchase.
/** * Understanding multi-threaded programs through ticket sellers--single-threaded programs * * @author zhuhuix * @date 2020-05-12 */ public class TicketSingle { private static final String ROOM = "Central Projection Hall"; private static final int ROW = 10; private static final int COL = 20; private static final String FILM_NAME = "Wolf 3"; private static final BigDecimal PRICE = BigDecimal.valueOf(30); private static List<Ticket> tickets = new ArrayList<>(); private static final int CUSTOMER_COUNT = 250; private static List<Customer> customers = new ArrayList<>(CUSTOMER_COUNT); public static void main(String[] args) { //There are 250 seats in the Central Hall, showing Wolf 3 from 2020-05-12 18:00 at a ticket price of 30 yuan int ticketId=1; for (int row = 1; row <= ROW; row++) { for (int col = 1; col <= COL; col++) { Ticket ticket = new Ticket(ticketId++, ROOM, row, col, FILM_NAME, PRICE, LocalDateTime.of(2020, 5, 10, 18, 00)); tickets.add(ticket); } } Iterator<Ticket> iterator = tickets.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next().toString()); } //Customers go to the ticket point to buy tickets at random Collections.shuffle(tickets); int index = 1; while (tickets.size() > 0 && index <= CUSTOMER_COUNT) { Ticket ticket = tickets.get(tickets.size() - 1); Customer customer = new Customer(index); customer.buyTicket(ticket); customers.add(customer); tickets.remove(ticket); System.out.println(tickets.size() + "," + index); System.out.println(index + "No. 1 customer bought it" + "No." + customer.getTicket().getRow() + "Line, No." + customer.getTicket().getCol() + "Tickets in columns"); index++; try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("The tickets are sold as follows:"); //Status of remaining tickets System.out.println("Remaining votes:" + tickets.size()); Iterator<Ticket> ticketIterator = tickets.iterator(); while (ticketIterator.hasNext()) { System.out.println(ticketIterator.next().toString()); } //Customer purchases System.out.println("Number of people who have bought tickets:" + customers.size()); Iterator<Customer> customerIterator = customers.iterator(); while (customerIterator.hasNext()) { System.out.println(customerIterator.next().toString()); } System.out.println("Number of people who have not bought tickets:" +(CUSTOMER_COUNT- customers.size())); } }
The output of the main program is as follows:
From single threaded to multithreaded
Everything is in order and the program is running well, so let's continue to look down. As the number of viewers increases, the cinema has made changes to the show hall: 1. increase seats; 2. add two ticket windows.That is to say, the program that used to have only one window queued for a single channel has changed, so that both the original and the new ticket points can be allowed to sell tickets at the same time.
Examples of problematic multithreading
Let's start with a simple multi-threaded modification of a single-threaded program: create a multi-threaded class, override the run method, move the customer ticket-buying process to the run method, set up "Ticket A", "Ticket B", "Ticket C" three threads to run at the same time, and remember to change the ArrayList data structure to Vector.The modified procedure is as follows:
/** * Understand Multithreading through Ticket Seller--Multithreading * * @author zhuhuix * @date 2020-05-12 */ public class TicketThread extends Thread { private static final String ROOM = "Central Projection Hall"; private static final int ROW = 20; private static final int COL = 30; private static final String FILM_NAME = "Wolf 3"; private static final BigDecimal PRICE = BigDecimal.valueOf(30); private static List<Ticket> tickets = new Vector<>(); private static final int CUSTOMER_COUNT = 800; private static int customerId = 1; private static List<Customer> customers = new Vector<>(CUSTOMER_COUNT); TicketThread(String name) { super(name); } @Override public void run() { while (tickets.size() > 0 && customerId <= CUSTOMER_COUNT) { Ticket ticket = tickets.get(tickets.size() - 1); ticket.setWindow(Thread.currentThread().getName()); Customer customer = new Customer(customerId); customer.buyTicket(ticket); customers.add(customer); tickets.remove(ticket); System.out.println(tickets.size() + "," + customerId); System.out.println(Thread.currentThread().getName() + ":" + customerId + "No. 1 customer bought it" + "No." + customer.getTicket().getRow() + "Line, No." + customer.getTicket().getCol() + "Tickets in columns"); customerId++; try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { //There are 250 seats in the Central Hall, showing Wolf 3 from 2020-05-12 18:00 at a ticket price of 30 yuan int ticketId = 1; for (int row = 1; row <= ROW; row++) { for (int col = 1; col <= COL; col++) { Ticket ticket = new Ticket(ticketId++, ROOM, row, col, FILM_NAME, PRICE, LocalDateTime.of(2020, 5, 10, 18, 00)); tickets.add(ticket); } } Iterator<Ticket> iterator = tickets.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next().toString()); } //Customers go to the ticket point to buy tickets at random Collections.shuffle(tickets); TicketThread ticketThreadA = new TicketThread("ticket office A"); TicketThread ticketThreadB = new TicketThread("ticket office B"); TicketThread ticketThreadC = new TicketThread("ticket office C"); ticketThreadA.start(); ticketThreadB.start(); ticketThreadC.start(); ticketThreadA.join(); ticketThreadB.join(); ticketThreadC.join(); System.out.println("The tickets are sold as follows:"); //Status of remaining tickets System.out.println("Total Votes:" + ROW * COL + ",Remaining votes:" + tickets.size()); Iterator<Ticket> ticketIterator = tickets.iterator(); while (ticketIterator.hasNext()) { System.out.println(ticketIterator.next().toString()); } //Customer purchases System.out.println("Number of people who have bought tickets:" + customers.size()); Iterator<Customer> customerIterator = customers.iterator(); while (customerIterator.hasNext()) { System.out.println(customerIterator.next().toString()); } System.out.println("Number of people who have not bought tickets:" + (CUSTOMER_COUNT - customers.size())); } }
Run it: There are only 600 tickets in total, and 614 people have bought tickets?The customer who entered the cinema must have complained.
Thread synchronization issues
Let's analyze:
There are only 600 tickets in total, and these 600 tickets are sold in three windows at the same time. The ticket is a shared pool, which is called "shared resource" or "critical resource" in multi-threaded terms. When each thread accesses these resources, make sure that they are synchronized: Ticket A sells a seat in row 10, column 9, and only Ticket A can access the corresponding seat at the same time.Movie tickets, also known as not selling more than one ticket.
How do multiple threads ensure synchronization?By locking!!Locking is a way to control how multiple threads access shared resources. Generally speaking, a lock prevents multiple threads from accessing shared resources at the same time.
Example of ensuring thread synchronization
For a simple illustration that locking ensures multithreaded synchronization, only locking the movie ticket sharing pool is implemented in the following example.
/** * Understand Multithreading through Ticket Seller--Multithreading * * @author zhuhuix * @date 2020-05-12 */ public class TicketThread extends Thread { private static final String ROOM = "Central Projection Hall"; private static final int ROW = 20; private static final int COL = 30; private static final String FILM_NAME = "Wolf 3"; private static final BigDecimal PRICE = BigDecimal.valueOf(30); private volatile static List<Ticket> tickets = new Vector<>(); private static final int CUSTOMER_COUNT = 800; private static int customerId = 1; private volatile static List<Customer> customers = new Vector<>(CUSTOMER_COUNT); TicketThread(String name) { super(name); } @Override public void run() { while (tickets.size() > 0 && customerId <= CUSTOMER_COUNT) { synchronized (TicketThread.class) { //Twice judgments within thread to prevent tickets array overflow if (tickets.size()>0) { Ticket ticket = tickets.get(tickets.size() - 1); ticket.setWindow(Thread.currentThread().getName()); Customer customer = new Customer(customerId); customer.buyTicket(ticket); customers.add(customer); tickets.remove(ticket); System.out.println(tickets.size() + "," + customerId); System.out.println(Thread.currentThread().getName() + ":" + customerId + "No. 1 customer bought it" + "No." + customer.getTicket().getRow() + "Line, No." + customer.getTicket().getCol() + "Tickets in columns"); customerId++; try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public static void main(String[] args) throws InterruptedException { //There are 250 seats in the Central Hall, showing Wolf 3 from 2020-05-12 18:00 at a ticket price of 30 yuan int ticketId = 1; for (int row = 1; row <= ROW; row++) { for (int col = 1; col <= COL; col++) { Ticket ticket = new Ticket(ticketId++, ROOM, row, col, FILM_NAME, PRICE, LocalDateTime.of(2020, 5, 10, 18, 00)); tickets.add(ticket); } } Iterator<Ticket> iterator = tickets.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next().toString()); } //Customers go to the ticket point to buy tickets at random Collections.shuffle(tickets); TicketThread ticketThreadA = new TicketThread("ticket office A"); TicketThread ticketThreadB = new TicketThread("ticket office B"); TicketThread ticketThreadC = new TicketThread("ticket office C"); ticketThreadA.start(); ticketThreadB.start(); ticketThreadC.start(); ticketThreadA.join(); ticketThreadB.join(); ticketThreadC.join(); System.out.println("The tickets are sold as follows:"); //Status of remaining tickets System.out.println("Total Votes:" + ROW * COL + ",Remaining votes:" + tickets.size()); Iterator<Ticket> ticketIterator = tickets.iterator(); while (ticketIterator.hasNext()) { System.out.println(ticketIterator.next().toString()); } //Customer purchases System.out.println("Number of people who have bought tickets:" + customers.size()); Iterator<Customer> customerIterator = customers.iterator(); while (customerIterator.hasNext()) { System.out.println(customerIterator.next().toString()); } System.out.println("Number of people who have not bought tickets:" + (CUSTOMER_COUNT - customers.size())); } }
The operation is as follows:
Tickets are not oversold:
Each window also synchronizes and sells invoices:
The main changes to synchronized code come from:
1. Modify the ticket-selling process with synchronized to achieve mutually exclusive lock, which can be referred to as java multithreading: a deep understanding of synchronized
synchronized (TicketThread.class) { Ticket ticket = tickets.get(tickets.size() - 1); ticket.setWindow(Thread.currentThread().getName()); Customer customer = new Customer(customerId); customer.buyTicket(ticket); customers.add(customer); tickets.remove(ticket); System.out.println(tickets.size() + "," + customerId); System.out.println(Thread.currentThread().getName() + ":" + customerId + "No. 1 customer bought it" + "No." + customer.getTicket().getRow() + "Line, No." + customer.getTicket().getCol() + "Tickets in columns"); customerId++; try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } }
2. Decorate shared resources with volatile to visualize thread access, for reference java multithreading: a deep understanding of volatile
private volatile static List<Ticket> tickets = new Vector<>(); private volatile static List<Customer> customers = new Vector<>(CUSTOMER_COUNT);
Write at the end
All expression of a program is ultimately a logical problem.The core of logic is to think clearly and efficiently.For multi-threaded understanding, you must start to write some routines, so that you can understand the true meaning and apply it to work practice.