Отправляйтесь в волнующую экспедицию по кодированию с нашей серией «Java Journeys», предназначенной исключительно для начинающих учеников, стремящихся разгадать магию программирования на Java.
Вот пример проекта Java, который иллюстрирует концепцию синхронизации с использованием синхронизированных методов. В этом примере мы смоделируем банковский счет, где несколько потоков пытаются одновременно снять средства, а синхронизация обеспечивает надлежащий доступ к общим ресурсам.
class BankAccount { private double balance; public BankAccount(double initialBalance) { this.balance = initialBalance; } // Synchronized method to withdraw funds public synchronized void withdraw(double amount) { if (amount <= balance) { System.out.println(Thread.currentThread().getName() + " is withdrawing $" + amount); balance -= amount; } else { System.out.println(Thread.currentThread().getName() + " tried to withdraw $" + amount + " but has insufficient balance."); } } public double getBalance() { return balance; } } class CustomerThread extends Thread { private BankAccount account; public CustomerThread(BankAccount account) { this.account = account; } @Override public void run() { for (int i = 0; i < 5; i++) { double withdrawalAmount = Math.random() * 500; account.withdraw(withdrawalAmount); try { Thread.sleep(100); // Simulating some processing time } catch (InterruptedException e) { e.printStackTrace(); } } } } public class SynchronizedMethodExample { public static void main(String[] args) { BankAccount account = new BankAccount(1000); CustomerThread customer1 = new CustomerThread(account); CustomerThread customer2 = new CustomerThread(account); customer1.setName("Customer 1"); customer2.setName("Customer 2"); customer1.start(); customer2.start(); try { customer1.join(); customer2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final balance: $" + account.getBalance()); } }
В этом примере:
- Класс
BankAccount
представляет банковский счет с синхронизированным методомwithdraw
. - Класс
CustomerThread
имитирует клиентов, снимающих деньги с банковского счета. - Класс
SynchronizedMethodExample
устанавливает клиентские потоки и демонстрирует синхронизацию.
Ключевые моменты, на которые следует обратить внимание:
- Метод
withdraw
помечен как синхронизированный. Это гарантирует, что только один поток может одновременно выполнять этот метод, предотвращая проблемы параллелизма, такие как перерисовка. - Несколько потоков клиентов (
CustomerThread
) пытаются снять средства одновременно. - Метод
join
используется для ожидания завершения обоих клиентских потоков перед печатью окончательного баланса.
В этом примере демонстрируется использование синхронизированных методов для обеспечения надлежащего одновременного доступа к общим ресурсам, поддержания целостности данных и предотвращения условий гонки.
Давайте расширим предыдущий пример, чтобы продемонстрировать синхронизацию с использованием синхронизированных блоков. В этом примере мы будем использовать синхронизированные блоки для управления доступом к общему ресурсу (принтеру) несколькими потоками.
class Printer { // Synchronized block to control access to the printer public void printDocument(String document, String threadName) { synchronized (this) { System.out.println("Printing document: " + document + " by " + threadName); try { Thread.sleep(100); // Simulating printing time } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Document " + document + " printed by " + threadName); } } } class PrintingThread extends Thread { private Printer printer; private String document; public PrintingThread(Printer printer, String document) { this.printer = printer; this.document = document; } @Override public void run() { printer.printDocument(document, Thread.currentThread().getName()); } } public class SynchronizedBlockExample { public static void main(String[] args) { Printer printer = new Printer(); PrintingThread thread1 = new PrintingThread(printer, "Document 1"); PrintingThread thread2 = new PrintingThread(printer, "Document 2"); PrintingThread thread3 = new PrintingThread(printer, "Document 3"); thread1.setName("Thread 1"); thread2.setName("Thread 2"); thread3.setName("Thread 3"); thread1.start(); thread2.start(); thread3.start(); try { thread1.join(); thread2.join(); thread3.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
В этом расширенном примере:
- Класс
Printer
содержит синхронизированный блок, обеспечивающий доступ к принтеру одновременно только одному потоку. - Класс
PrintingThread
имитирует печать документа с помощью принтера. - Класс
SynchronizedBlockExample
настраивает потоки печати и демонстрирует синхронизацию с использованием синхронизированных блоков.
Ключевые моменты, на которые следует обратить внимание:
- Синхронизированный блок в методе
printDocument
блокирует экземпляр объектаPrinter
, обеспечивая эксклюзивный доступ к принтеру для текущего потока. - Несколько потоков
PrintingThread
пытаются распечатать документы одновременно. - Метод
join
используется для ожидания завершения всех потоков до завершения программы.
В этом примере демонстрируется использование синхронизированных блоков для управления доступом к общему ресурсу и предотвращения проблем параллелизма.
Давайте разберем концепции в расширенном примере с использованием синхронизированных блоков в упрощенном виде:
1. Синхронизированный блок. В классе Printer
синхронизированный блок похож на запертую комнату. Только один поток может войти в эту комнату одновременно. Когда поток входит в комнату, он может безопасно использовать принтер, не мешая другим потокам.
2. Общий ресурс. Принтер в нашем примере является общим ресурсом. Это как особая машина, которой одновременно хотят пользоваться несколько человек. Нам нужно убедиться, что только один человек может использовать его одновременно, чтобы их распечатки не перепутались.
3. Тема печати. Каждый PrintingThread
представляет человека, который хочет распечатать документ. Эти люди не могут пользоваться принтером одновременно, потому что могут испортить распечатки друг друга. Итак, они ждут своей очереди, чтобы войти в комнату с принтером (синхронизированный блок) для печати.
4. Эксклюзивный доступ. Когда поток входит в синхронизированный блок, он получает монопольный доступ к принтеру. Это означает, что ни один другой поток не может войти и использовать принтер, пока первый поток не будет выполнен. Это гарантирует, что распечатки не будут перепутаны, а все останется организованным.
5. Смоделированная печать. Чтобы сделать ее более реалистичной, мы моделируем время печати с помощью Thread.sleep(100)
. Это похоже на то, что принтеру нужно время, чтобы распечатать документ. Пока один человек печатает, другие ждут за пределами комнаты с принтером.
6. Присоединение к темам. Метод join
похож на ожидание, пока ваши друзья что-то закончат, прежде чем двигаться дальше. В нашем случае мы ждем, пока все потоки печати закончат свою работу, прежде чем завершится программа. Это помогает нам увидеть все распечатки вместе в конце.
Проще говоря, представьте себе синхронизированный блок как специальную комнату, куда одновременно может войти только один человек (поток), чтобы использовать общий принтер. Это предотвращает хаос и гарантирует правильность распечаток каждого человека.