JavaRush /Blog Java /Random-VI /50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng ...
Roman Beekeeper
Mức độ

50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu. Phần 3

Xuất bản trong nhóm
50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu. Phần 1 Top 50 câu hỏi và câu trả lời phỏng vấn Java Core. Phần 2

Đa luồng

37. Làm cách nào để tạo một luồng (luồng) mới trong Java?

Bằng cách này hay cách khác, việc tạo ra diễn ra thông qua việc sử dụng lớp Thread. Nhưng có thể có những lựa chọn ở đây...
  1. Chúng tôi kế thừa từjava.lang.Thread
  2. Chúng tôi triển khai một giao diện java.lang.Runnablecó đối tượng chấp nhận Threadlớp xây dựng
Hãy nói về từng người trong số họ.

Chúng ta kế thừa từ lớp Thread

Để thực hiện công việc này, trong lớp của chúng tôi, chúng tôi kế thừa từ java.lang.Thread. Nó chứa meth run(), đó chính xác là những gì chúng ta cần. Tất cả vòng đời và logic của luồng mới sẽ nằm trong phương thức này. Đây là một loại mainphương pháp cho một chủ đề mới. Sau đó, tất cả những gì còn lại là tạo một đối tượng của lớp chúng ta và thực thi phương thức start(), phương thức này sẽ tạo một luồng mới và chạy logic được viết trong đó. Hãy xem:
/**
* Пример того, How создавать треды путем наследования {@link Thread} класса.
*/
class ThreadInheritance extends Thread {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance threadInheritance1 = new ThreadInheritance();
       ThreadInheritance threadInheritance2 = new ThreadInheritance();
       ThreadInheritance threadInheritance3 = new ThreadInheritance();
       threadInheritance1.start();
       threadInheritance2.start();
       threadInheritance3.start();
   }
}
Đầu ra của bàn điều khiển sẽ như thế này:

Thread-1
Thread-0
Thread-2
Tức là, ngay cả ở đây chúng ta cũng thấy rằng các luồng được thực thi không phải lần lượt mà do JVM quyết định)

Triển khai giao diện Runnable

Nếu bạn phản đối việc kế thừa và/hoặc đã kế thừa một trong các lớp khác, bạn có thể sử dụng lớp java.lang.Runnable. Ở đây trong lớp của chúng ta, chúng ta triển khai giao diện này và triển khai phương thức run(), giống như trong ví dụ đó. Bạn chỉ cần tạo thêm nhiều đối tượng hơn Thread. Có vẻ như nhiều dòng thì tệ hơn. Nhưng chúng ta biết sự kế thừa có hại như thế nào và tốt hơn hết là nên tránh nó bằng mọi cách ;) Hãy xem:
/**
* Пример того, How создавать треды из интерфейса {@link Runnable}.
* Здесь проще простого - реализуем этот интерфейс и потом передаем в конструктор
* экземпляр реализуемого an object.
*/
class ThreadInheritance implements Runnable {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance runnable1 = new ThreadInheritance();
       ThreadInheritance runnable2 = new ThreadInheritance();
       ThreadInheritance runnable3 = new ThreadInheritance();

       Thread threadRunnable1 = new Thread(runnable1);
       Thread threadRunnable2 = new Thread(runnable2);
       Thread threadRunnable3 = new Thread(runnable3);

       threadRunnable1.start();
       threadRunnable2.start();
       threadRunnable3.start();
   }
}
Và kết quả thực hiện:

Thread-0
Thread-1
Thread-2

38. Sự khác biệt giữa tiến trình và luồng là gì?

50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 3 - 1Có những khác biệt sau đây giữa một tiến trình và một luồng:
  1. Một chương trình đang thực thi được gọi là một tiến trình, trong khi Thread là tập hợp con của một tiến trình.
  2. Các tiến trình là độc lập, trong khi các luồng là tập hợp con của một tiến trình.
  3. Các tiến trình có không gian địa chỉ khác nhau trong bộ nhớ, trong khi các luồng chứa một không gian địa chỉ chung.
  4. Chuyển đổi bối cảnh giữa các luồng nhanh hơn so với các tiến trình.
  5. Giao tiếp giữa các tiến trình chậm hơn và tốn kém hơn so với giao tiếp giữa các luồng.
  6. Mọi thay đổi trong tiến trình cha không ảnh hưởng đến tiến trình con, trong khi những thay đổi trong luồng cha có thể ảnh hưởng đến luồng con.

39. Ưu điểm của đa luồng là gì?

50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 3 - 2
  1. Đa luồng cho phép ứng dụng/chương trình luôn phản hồi đầu vào ngay cả khi nó đang chạy một số tác vụ nền;
  2. Đa luồng cho phép bạn hoàn thành các tác vụ nhanh hơn vì các luồng thực thi độc lập;
  3. Đa luồng cung cấp khả năng sử dụng bộ đệm tốt hơn vì các luồng chia sẻ tài nguyên bộ nhớ chung;
  4. Đa luồng làm giảm số lượng máy chủ cần thiết vì một máy chủ có thể chạy nhiều luồng cùng một lúc.

40. Các trạng thái trong vòng đời của một thread là gì?

50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 3 - 3
  1. Mới: Ở trạng thái này, một đối tượng lớp Threadđược tạo bằng toán tử mới, nhưng luồng không tồn tại. Chuỗi không bắt đầu cho đến khi chúng tôi gọi start().
  2. Runnable: Ở trạng thái này, thread sẵn sàng chạy sau khi gọi phương thức bắt đầu(). Tuy nhiên, nó vẫn chưa được bộ lập lịch luồng chọn.
  3. Đang chạy: Ở trạng thái này, bộ lập lịch luồng chọn một luồng từ trạng thái sẵn sàng và nó chạy.
  4. Đang chờ/Bị chặn: Ở trạng thái này, luồng không chạy nhưng vẫn tồn tại hoặc đang chờ một luồng khác hoàn thành.
  5. Chết/chấm dứt: Khi phương thức thoát, run()luồng ở trạng thái chấm dứt hoặc chết.

41. Có thể bắt đầu một chủ đề hai lần không?

Không, chúng tôi không thể khởi động lại luồng vì khi luồng được khởi động và thực thi, nó sẽ chuyển sang trạng thái Chết. Vì vậy, nếu chúng ta cố chạy luồng hai lần, nó sẽ ném ra một ngoại lệ thời gian chạy " java.lang.IllegalThreadStateException ". Hãy xem:
class DoubleStartThreadExample extends Thread {

   /**
    * Имитируем работу треда
    */
   public void run() {
	// что-то происходит. Для нас не существенно на этом этапе
   }

   /**
    * Запускаем тред дважды
    */
   public static void main(String[] args) {
       DoubleStartThreadExample doubleStartThreadExample = new DoubleStartThreadExample();
       doubleStartThreadExample.start();
       doubleStartThreadExample.start();
   }
}
Ngay khi tác phẩm đạt đến điểm bắt đầu thứ hai của cùng một chủ đề thì sẽ có một ngoại lệ. Hãy tự mình thử nhé ;) thà nhìn một lần còn hơn nghe trăm lần.

42. Điều gì sẽ xảy ra nếu bạn gọi trực tiếp phương thức run() mà không gọi phương thức start()?

Có, run()tất nhiên bạn có thể gọi một phương thức, nhưng điều này sẽ không tạo ra một luồng mới và thực thi nó như một luồng riêng biệt. Trong trường hợp này, nó là một đối tượng đơn giản gọi một phương thức đơn giản. Nếu chúng ta đang nói về phương pháp start()thì đó lại là một vấn đề khác. Bằng cách khởi chạy phương thức này, runtimenó sẽ khởi chạy một phương thức mới và đến lượt nó chạy phương thức của chúng tôi;) Nếu bạn không tin tôi, hãy thử:
class ThreadCallRunExample extends Thread {

   public void run() {
       for (int i = 0; i < 5; i++) {
           System.out.print(i);
       }
   }

   public static void main(String args[]) {
       ThreadCallRunExample runExample1 = new ThreadCallRunExample();
       ThreadCallRunExample runExample2 = new ThreadCallRunExample();

       // просто будут вызваны в потоке main два метода, один за другим.
       runExample1.run();
       runExample2.run();
   }
}
Và đầu ra của bàn điều khiển sẽ như thế này:

0123401234
Có thể thấy rằng không có chủ đề nào được tạo ra. Mọi thứ diễn ra như một lớp học bình thường. Đầu tiên, phương pháp lớp đầu tiên hoạt động, sau đó là phương pháp thứ hai.

43. Chuỗi daemon là gì?

50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 3 - 4Luồng daemon (sau đây gọi tắt là luồng daemon) là luồng thực hiện các tác vụ ở chế độ nền liên quan đến luồng khác. Nghĩa là, công việc của nó là thực hiện các tác vụ phụ chỉ cần được thực hiện cùng với một luồng (chính) khác. Có nhiều luồng daemon chạy tự động, chẳng hạn như Garbage Collector, Finalizer, v.v.

Tại sao Java đóng chuỗi daemon?

Mục đích duy nhất của luồng daemon là nó cung cấp dịch vụ cho luồng người dùng cho tác vụ hỗ trợ nền. Do đó, nếu luồng chính đã hoàn thành thì bộ thực thi sẽ tự động đóng tất cả các luồng daemon của nó.

Các phương thức làm việc trong lớp Thread

Lớp này java.lang.Threadcung cấp hai phương thức để làm việc với trình nền luồng:
  1. public void setDaemon(boolean status)- chỉ ra rằng đây sẽ là một chủ đề daemon. Giá trị mặc định là false, có nghĩa là các luồng không phải daemon sẽ được tạo trừ khi được chỉ định riêng.
  2. public boolean isDaemon()- về cơ bản đây là một getter cho biến daemonmà chúng ta đã đặt bằng phương thức trước đó.
Ví dụ:
class DaemonThreadExample extends Thread {

   public void run() {
       // Проверяет, демон ли этот поток or нет
       if (Thread.currentThread().isDaemon()) {
           System.out.println("daemon thread");
       } else {
           System.out.println("user thread");
       }
   }

   public static void main(String[] args) {
       DaemonThreadExample thread1 = new DaemonThreadExample();
       DaemonThreadExample thread2 = new DaemonThreadExample();
       DaemonThreadExample thread3 = new DaemonThreadExample();

       // теперь thread1 - поток-демон.
       thread1.setDaemon(true);

       System.out.println("демон?.. " + thread1.isDaemon());
       System.out.println("демон?.. " + thread2.isDaemon());
       System.out.println("демон?.. " + thread3.isDaemon());

       thread1.start();
       thread2.start();
       thread3.start();
   }
}
Đầu ra của bảng điều khiển:

демон?.. true
демон?.. false
демон?.. false
daemon thread
user thread
user thread
Từ kết quả đầu ra, chúng ta thấy rằng bên trong chính luồng đó, bằng cách sử dụng một currentThread()phương thức tĩnh, một mặt chúng ta có thể tìm ra đó là luồng nào, mặt khác, nếu chúng ta có tham chiếu đến đối tượng của luồng này, chúng ta có thể tìm ra trực tiếp từ nó. Điều này mang lại sự linh hoạt cần thiết trong cấu hình.

44. Có thể biến một thread thành daemon sau khi nó được tạo không?

KHÔNG. Nếu bạn làm điều này nó sẽ đưa ra một ngoại lệ IllegalThreadStateException. Do đó, chúng ta chỉ có thể tạo một luồng daemon trước khi nó bắt đầu. Ví dụ:
class SetDaemonAfterStartExample extends Thread {

   public void run() {
       System.out.println("Working...");
   }

   public static void main(String[] args) {
       SetDaemonAfterStartExample afterStartExample = new SetDaemonAfterStartExample();
       afterStartExample.start();

       // здесь будет выброшено исключение
       afterStartExample.setDaemon(true);
   }
}
Đầu ra của bảng điều khiển:

Working...
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.setDaemon(Thread.java:1359)
	at SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

45. Móc khóa tắt máy là gì?

Shutdownhook là một luồng được gọi ngầm trước khi JVM (Máy ảo Java) tắt. Vì vậy, chúng ta có thể sử dụng nó để dọn sạch tài nguyên hoặc lưu trạng thái khi Máy ảo Java tắt bình thường hoặc đột ngột. Chúng ta có thể thêm shutdown hookbằng phương pháp sau:
Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
Như trong ví dụ:
/**
* Программа, которая показывает How запустить shutdown hook тред,
* который выполнится аккурат до окончания работы JVM
*/
class ShutdownHookThreadExample extends Thread {

   public void run() {
       System.out.println("shutdown hook задачу выполнил");
   }

   public static void main(String[] args) {

       Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());

       System.out.println("Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.");
       try {
           Thread.sleep(60000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}
Đầu ra của bảng điều khiển:

Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.
shutdown hook задачу выполнил

46. ​​​​Đồng bộ hóa là gì?

Đồng bộ hóa trong Java là khả năng kiểm soát quyền truy cập của nhiều luồng vào bất kỳ tài nguyên được chia sẻ nào. Khi nhiều luồng cố gắng thực hiện cùng một tác vụ, có khả năng xảy ra kết quả sai, do đó, để khắc phục vấn đề này, Java sử dụng tính năng đồng bộ hóa, do đó chỉ một luồng có thể hoạt động tại một thời điểm. Đồng bộ hóa có thể đạt được theo ba cách:
  • Phương pháp đồng bộ hóa
  • Bằng cách đồng bộ hóa một khối cụ thể
  • Đồng bộ hóa tĩnh

Đồng bộ hóa phương pháp

Phương thức được đồng bộ hóa được sử dụng để khóa một đối tượng đối với bất kỳ tài nguyên được chia sẻ nào. Khi một luồng gọi một phương thức được đồng bộ hóa, nó sẽ tự động lấy một khóa trên đối tượng đó và giải phóng nó khi luồng hoàn thành nhiệm vụ của nó. Để nó hoạt động, bạn cần thêm từ khóa được đồng bộ hóa . Hãy xem cách nó hoạt động bằng một ví dụ:
/**
* Пример, где мы синхронизируем метод. То есть добавляем ему слово synchronized.
* Есть два писателя, которые хотят использовать один принтер. Они подготовor свои поэмы
* И конечно же не хотят, чтоб их поэмы перемешались, а хотят, чтоб работа была сделана по * * * очереди для каждого из них
*/
class Printer {

   synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {
       // один an object для двух тредов
       Printer printer  = new Printer();

       // создаем два треда
       Writer1 writer1 = new Writer1(printer);
       Writer2 writer2 = new Writer2(printer);

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

/**
* Писатель номер 1, который пишет свою поэму.
*/
class Writer1 extends Thread {
   Printer printer;

   Writer1(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<string> poem = Arrays.asList("Я ", this.getName(), " Пишу", " Письмо");
       printer.print(poem);
   }

}

/**
* Писатель номер 2, который пишет свою поэму.
*/
class Writer2 extends Thread {
   Printer printer;

   Writer2(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<String> poem = Arrays.asList("Не Я ", this.getName(), " Не пишу", " Не Письмо");
       printer.print(poem);
   }
}
Và đầu ra cho bàn điều khiển:

Я Thread-0 Пишу Письмо
Не Я Thread-1 Не пишу Не Письмо

Khối đồng bộ hóa

Khối được đồng bộ hóa có thể được sử dụng để thực hiện đồng bộ hóa trên bất kỳ tài nguyên phương pháp cụ thể nào. Giả sử rằng trong một phương thức lớn (vâng, vâng, bạn không thể viết những thứ như vậy, nhưng đôi khi điều đó xảy ra), vì một lý do nào đó, bạn chỉ cần đồng bộ hóa một phần nhỏ. Nếu bạn đặt tất cả mã của một phương thức vào một khối được đồng bộ hóa, nó sẽ hoạt động giống như một phương thức được đồng bộ hóa. Cú pháp trông như thế này:
synchronized (“an object для блокировки”) {
   // сам code, который нужно защитить
}
Để không lặp lại ví dụ trước, chúng ta sẽ tạo thread thông qua các lớp ẩn danh - tức là triển khai ngay giao diện Runnable.
/**
* Вот How добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

   void print(List<String> wordsToPrint) {
       synchronized (this) {
           wordsToPrint.forEach(System.out::print);
       }
       System.out.println();
   }

   public static void main(String args[]) {
       // один an object для двух тредов
       Printer printer = new Printer();

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

}
và xuất ra bàn điều khiển

Я Writer1 Пишу Письмо
Не Я Writer2 Не пишу Не Письмо

Đồng bộ hóa tĩnh

Nếu bạn thực hiện đồng bộ hóa một phương thức tĩnh, khóa sẽ ở trên lớp chứ không phải trên đối tượng. Trong ví dụ này, chúng tôi áp dụng từ khóa được đồng bộ hóa cho một phương thức tĩnh để thực hiện đồng bộ hóa tĩnh:
/**
* Вот How добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

   static synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               Printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               Printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}
và đầu ra cho bàn điều khiển:

Не Я Writer2 Не пишу Не Письмо
Я Writer1 Пишу Письмо

47. Biến dễ bay hơi là gì?

Từ khóa volatileđược sử dụng trong lập trình đa luồng để cung cấp sự an toàn cho luồng vì việc sửa đổi một biến có thể thay đổi được hiển thị cho tất cả các luồng khác, do đó, một biến có thể được sử dụng bởi một luồng tại một thời điểm. Sử dụng từ khóa, volatilebạn có thể đảm bảo rằng biến sẽ an toàn cho luồng và sẽ được lưu trữ trong bộ nhớ dùng chung và các luồng sẽ không đưa biến đó vào bộ đệm của chúng. Nó trông như thế nào?
private volatile AtomicInteger count;
Chúng tôi chỉ cần thêm vào biến volatile. Nhưng điều này không có nghĩa là hoàn toàn an toàn cho luồng... Sau cùng, các thao tác có thể không mang tính nguyên tử trên một biến. Nhưng bạn có thể sử dụng Atomiccác lớp thực hiện thao tác một cách nguyên tử, nghĩa là trong một lần thực thi của bộ xử lý. Nhiều lớp như vậy có thể được tìm thấy trong gói java.util.concurrent.atomic.

48. Bế tắc là gì

Bế tắc trong Java là một phần của đa luồng. Bế tắc có thể xảy ra trong trường hợp một luồng đang chờ khóa đối tượng được một luồng khác thu được và một luồng thứ hai đang chờ khóa đối tượng được luồng đầu tiên thu được. Do đó, hai luồng này đợi nhau và sẽ không tiếp tục thực thi mã của chúng. 50 câu hỏi và câu trả lời phỏng vấn cốt lõi về Java hàng đầu.  Phần 3 - 5Hãy xem một Ví dụ trong đó có một lớp triển khai Runnable. Nó chấp nhận hai tài nguyên trong hàm tạo của nó. Bên trong phương thức run(), nó lấy từng khóa một, vì vậy nếu bạn tạo hai đối tượng của lớp này và chuyển tài nguyên theo các thứ tự khác nhau, bạn có thể dễ dàng gặp phải một khóa:
class DeadLock {

   public static void main(String[] args) {
       final Integer r1 = 10;
       final Integer r2 = 15;

       DeadlockThread threadR1R2 = new DeadlockThread(r1, r2);
       DeadlockThread threadR2R1 = new DeadlockThread(r2, r1);

       new Thread(threadR1R2).start();
       new Thread(threadR2R1).start();
   }
}

/**
* Класс, который принимает два ресурса.
*/
class DeadlockThread implements Runnable {

   private final Integer r1;
   private final Integer r2;

   public DeadlockThread(Integer r1, Integer r2) {
       this.r1 = r1;
       this.r2 = r2;
   }

   @Override
   public void run() {
       synchronized (r1) {
           System.out.println(Thread.currentThread().getName() + " захватил ресурс: " + r1);

           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }

           synchronized (r2) {
               System.out.println(Thread.currentThread().getName() + " захватил ресурс: " + r2);
           }
       }
   }
}
Đầu ra của bảng điều khiển:

Первый тред захватил первый ресурс
Второй тред захватывает второй ресурс

49. Làm thế nào để tránh bế tắc?

Dựa trên những gì chúng ta biết bế tắc xảy ra như thế nào, chúng ta có thể rút ra một số kết luận...
  • Như trong ví dụ trên, bế tắc là do các ổ khóa lồng vào nhau. Tức là bên trong một ổ khóa có một hoặc nhiều ổ khóa khác. Bạn có thể tránh điều này theo cách sau - thay vì lồng nhau, bạn cần thêm một phần trừu tượng mới lên trên và đưa khóa lên cấp cao hơn, đồng thời xóa các khóa lồng nhau.
  • Càng chặn nhiều thì nguy cơ bế tắc càng lớn. Vì vậy, mỗi khi thêm một khóa, bạn cần suy nghĩ xem nó có thực sự cần thiết hay không và có thể tránh được việc thêm một khóa mới hay không.
  • Công dụng Thread.join(). Sự bế tắc cũng có thể được thực hiện khi một luồng đang chờ một luồng khác. Để tránh vấn đề này, bạn có thể cân nhắc việc đặt giới hạn thời gian cho join()phương pháp.
  • Nếu chúng ta có một thread thì sẽ không có bế tắc;)

50. Điều kiện cuộc đua là gì?

Nếu trong các cuộc đua thực sự, những chiếc ô tô hoạt động, thì theo thuật ngữ đua xe là đa luồng, các sợi chỉ hoạt động trong các cuộc đua. Nhưng tại sao? Có hai luồng đang chạy và có thể truy cập vào cùng một đối tượng. Và họ có thể cố gắng cập nhật trạng thái cùng một lúc. Cho đến nay mọi thứ đều rõ ràng, phải không? Vì vậy, các luồng hoạt động song song thực sự (nếu có nhiều hơn một lõi trong bộ xử lý) hoặc song song có điều kiện, khi bộ xử lý phân bổ một khoảng thời gian ngắn. Và chúng tôi không thể kiểm soát các quy trình này, vì vậy chúng tôi không thể đảm bảo rằng khi một luồng đọc dữ liệu từ một đối tượng, nó sẽ có thời gian để thay đổi TRƯỚC KHI một số luồng khác thực hiện việc đó. Những vấn đề như thế này xảy ra khi sự kết hợp thử nghiệm và hành động này diễn ra. Nó có nghĩa là gì? Ví dụ: chúng ta có ifmột biểu thức trong phần thân của biểu thức mà chính điều kiện của nó sẽ thay đổi, đó là:
int z = 0;

// проверь
if (z < 5) {
//действуй
   z = z + 5;
}
Vì vậy, có thể xảy ra trường hợp hai luồng đồng thời nhập khối mã này vào thời điểm z vẫn bằng 0 và chúng cùng nhau thay đổi giá trị này. Và cuối cùng chúng ta sẽ không nhận được giá trị mong đợi là 5 mà là 10. Làm thế nào để tránh điều này? Bạn cần khóa trước và sau khi thực hiện. Nghĩa là, để luồng đầu tiên vào khối if, hãy thực hiện tất cả các hành động, thay đổi nó zvà chỉ sau đó mới cho luồng tiếp theo cơ hội thực hiện việc này. Nhưng luồng tiếp theo sẽ không vào khối if, vì znó đã bằng 5:
// получить блокировку для z
if (z < 5) {
   z = z + 5;
}
// выпустить из блокировки z
===================================================

Thay vì đầu ra

Tôi muốn nói lời cảm ơn đến tất cả những người đã đọc đến cuối. Đó là một hành trình dài và bạn đã làm được! Không phải mọi thứ đều có thể rõ ràng. Điều này ổn. Ngay khi tôi bắt đầu học Java, tôi không thể hiểu được biến tĩnh là gì. Nhưng chẳng có gì, tôi ngủ với suy nghĩ này, đọc thêm vài nguồn và cuối cùng cũng hiểu ra. Chuẩn bị cho một cuộc phỏng vấn là một vấn đề mang tính học thuật hơn là thực tế. Vì vậy, trước mỗi cuộc phỏng vấn, bạn cần nhắc lại và làm mới trí nhớ của mình về những thứ mà có thể bạn không thường xuyên sử dụng.

Và như mọi khi, các liên kết hữu ích:

Cảm ơn tất cả các bạn đã đọc, Hẹn gặp lại) Hồ sơ của tôi trên GitHub
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION